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

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

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

4
.change-monitored Normal file
View File

@ -0,0 +1,4 @@
/assets/*
/scripts/*
/sound/*
MissionGenerator.exe

6
.gitignore vendored
View File

@ -8,7 +8,6 @@ Generator/build
Generator/venv Generator/venv
Generator/dist Generator/dist
Generator/generator.log Generator/generator.log
MissionGenerator.exe
incoming templates/ incoming templates/
Generator/utils/extract units/source.miz Generator/utils/extract units/source.miz
Generator/utils/extract units/units.txt Generator/utils/extract units/units.txt
@ -20,9 +19,10 @@ templates/Imports/downloaded
templates/Forces/user templates/Forces/user
templates/Forces/downloaded templates/Forces/downloaded
config/user-data.yaml config/user-data.yaml
*.exe
config/user-data.yaml config/user-data.yaml
server/user-files/modules/mapscript-sql.py server/user-files/modules/mapscript-sql.py
distribution/ distribution/
MissionOutput/ MissionOutput/
.idea
*.env
RotorOps_setup.exe

View File

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

View File

@ -2,7 +2,7 @@
# Form implementation generated from reading ui file 'MissionGeneratorUI.ui' # Form implementation generated from reading ui file 'MissionGeneratorUI.ui'
# #
# Created by: PyQt5 UI code generator 5.15.4 # Created by: PyQt5 UI code generator 5.15.7
# #
# WARNING: Any manual changes made to this file will be lost when pyuic5 is # 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. # run again. Do not edit this file unless you know what you are doing.
@ -34,20 +34,20 @@ class Ui_MainWindow(object):
self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget") self.centralwidget.setObjectName("centralwidget")
self.logistics_crates_checkBox = QtWidgets.QCheckBox(self.centralwidget) self.logistics_crates_checkBox = QtWidgets.QCheckBox(self.centralwidget)
self.logistics_crates_checkBox.setGeometry(QtCore.QRect(980, 211, 251, 28)) self.logistics_crates_checkBox.setGeometry(QtCore.QRect(980, 231, 251, 28))
font = QtGui.QFont() font = QtGui.QFont()
font.setPointSize(10) font.setPointSize(10)
font.setBold(False) font.setBold(False)
self.logistics_crates_checkBox.setFont(font) self.logistics_crates_checkBox.setFont(font)
self.logistics_crates_checkBox.setChecked(True) self.logistics_crates_checkBox.setChecked(True)
self.logistics_crates_checkBox.setObjectName("logistics_crates_checkBox") self.logistics_crates_checkBox.setObjectName("logistics_crates_checkBox")
self.zone_sams_checkBox = QtWidgets.QCheckBox(self.centralwidget) self.advanced_defenses_checkBox = QtWidgets.QCheckBox(self.centralwidget)
self.zone_sams_checkBox.setGeometry(QtCore.QRect(980, 320, 241, 28)) self.advanced_defenses_checkBox.setGeometry(QtCore.QRect(510, 350, 241, 28))
font = QtGui.QFont() font = QtGui.QFont()
font.setPointSize(10) font.setPointSize(10)
font.setBold(False) font.setBold(False)
self.zone_sams_checkBox.setFont(font) self.advanced_defenses_checkBox.setFont(font)
self.zone_sams_checkBox.setObjectName("zone_sams_checkBox") self.advanced_defenses_checkBox.setObjectName("advanced_defenses_checkBox")
self.red_forces_label = QtWidgets.QLabel(self.centralwidget) self.red_forces_label = QtWidgets.QLabel(self.centralwidget)
self.red_forces_label.setGeometry(QtCore.QRect(470, 80, 171, 27)) self.red_forces_label.setGeometry(QtCore.QRect(470, 80, 171, 27))
font = QtGui.QFont() font = QtGui.QFont()
@ -79,7 +79,7 @@ class Ui_MainWindow(object):
self.description_textBrowser.setObjectName("description_textBrowser") self.description_textBrowser.setObjectName("description_textBrowser")
self.defense_checkBox = QtWidgets.QCheckBox(self.centralwidget) self.defense_checkBox = QtWidgets.QCheckBox(self.centralwidget)
self.defense_checkBox.setEnabled(True) self.defense_checkBox.setEnabled(True)
self.defense_checkBox.setGeometry(QtCore.QRect(470, 130, 156, 28)) self.defense_checkBox.setGeometry(QtCore.QRect(980, 140, 156, 28))
font = QtGui.QFont() font = QtGui.QFont()
font.setPointSize(11) font.setPointSize(11)
font.setBold(False) font.setBold(False)
@ -109,7 +109,7 @@ class Ui_MainWindow(object):
self.redforces_comboBox.setFont(font) self.redforces_comboBox.setFont(font)
self.redforces_comboBox.setObjectName("redforces_comboBox") self.redforces_comboBox.setObjectName("redforces_comboBox")
self.scenario_label_8 = QtWidgets.QLabel(self.centralwidget) self.scenario_label_8 = QtWidgets.QLabel(self.centralwidget)
self.scenario_label_8.setGeometry(QtCore.QRect(570, 220, 271, 24)) self.scenario_label_8.setGeometry(QtCore.QRect(570, 180, 271, 24))
font = QtGui.QFont() font = QtGui.QFont()
font.setPointSize(10) font.setPointSize(10)
font.setBold(False) font.setBold(False)
@ -161,18 +161,21 @@ class Ui_MainWindow(object):
self.scenario_label_4.setAlignment(QtCore.Qt.AlignCenter) self.scenario_label_4.setAlignment(QtCore.Qt.AlignCenter)
self.scenario_label_4.setObjectName("scenario_label_4") self.scenario_label_4.setObjectName("scenario_label_4")
self.version_label = QtWidgets.QLabel(self.centralwidget) self.version_label = QtWidgets.QLabel(self.centralwidget)
self.version_label.setGeometry(QtCore.QRect(1140, 650, 111, 20)) self.version_label.setGeometry(QtCore.QRect(1070, 650, 181, 20))
font = QtGui.QFont()
font.setPointSize(8)
self.version_label.setFont(font)
self.version_label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.version_label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.version_label.setObjectName("version_label") self.version_label.setObjectName("version_label")
self.scenario_label_10 = QtWidgets.QLabel(self.centralwidget) self.scenario_label_10 = QtWidgets.QLabel(self.centralwidget)
self.scenario_label_10.setGeometry(QtCore.QRect(570, 260, 271, 24)) self.scenario_label_10.setGeometry(QtCore.QRect(570, 220, 271, 24))
font = QtGui.QFont() font = QtGui.QFont()
font.setPointSize(10) font.setPointSize(10)
font.setBold(False) font.setBold(False)
self.scenario_label_10.setFont(font) self.scenario_label_10.setFont(font)
self.scenario_label_10.setObjectName("scenario_label_10") self.scenario_label_10.setObjectName("scenario_label_10")
self.e_transport_helos_spinBox = QtWidgets.QSpinBox(self.centralwidget) self.e_transport_helos_spinBox = QtWidgets.QSpinBox(self.centralwidget)
self.e_transport_helos_spinBox.setGeometry(QtCore.QRect(510, 260, 51, 31)) self.e_transport_helos_spinBox.setGeometry(QtCore.QRect(510, 220, 51, 31))
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
@ -187,7 +190,7 @@ class Ui_MainWindow(object):
self.e_transport_helos_spinBox.setProperty("value", 1) self.e_transport_helos_spinBox.setProperty("value", 1)
self.e_transport_helos_spinBox.setObjectName("e_transport_helos_spinBox") self.e_transport_helos_spinBox.setObjectName("e_transport_helos_spinBox")
self.e_attack_planes_spinBox = QtWidgets.QSpinBox(self.centralwidget) self.e_attack_planes_spinBox = QtWidgets.QSpinBox(self.centralwidget)
self.e_attack_planes_spinBox.setGeometry(QtCore.QRect(510, 220, 51, 31)) self.e_attack_planes_spinBox.setGeometry(QtCore.QRect(510, 180, 51, 31))
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
@ -202,7 +205,7 @@ class Ui_MainWindow(object):
self.e_attack_planes_spinBox.setProperty("value", 1) self.e_attack_planes_spinBox.setProperty("value", 1)
self.e_attack_planes_spinBox.setObjectName("e_attack_planes_spinBox") self.e_attack_planes_spinBox.setObjectName("e_attack_planes_spinBox")
self.e_attack_helos_spinBox = QtWidgets.QSpinBox(self.centralwidget) self.e_attack_helos_spinBox = QtWidgets.QSpinBox(self.centralwidget)
self.e_attack_helos_spinBox.setGeometry(QtCore.QRect(510, 180, 51, 31)) self.e_attack_helos_spinBox.setGeometry(QtCore.QRect(510, 140, 51, 31))
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
@ -219,7 +222,7 @@ class Ui_MainWindow(object):
self.e_attack_helos_spinBox.setProperty("value", 1) self.e_attack_helos_spinBox.setProperty("value", 1)
self.e_attack_helos_spinBox.setObjectName("e_attack_helos_spinBox") self.e_attack_helos_spinBox.setObjectName("e_attack_helos_spinBox")
self.scenario_label_7 = QtWidgets.QLabel(self.centralwidget) self.scenario_label_7 = QtWidgets.QLabel(self.centralwidget)
self.scenario_label_7.setGeometry(QtCore.QRect(570, 180, 271, 24)) self.scenario_label_7.setGeometry(QtCore.QRect(570, 140, 271, 24))
font = QtGui.QFont() font = QtGui.QFont()
font.setPointSize(10) font.setPointSize(10)
font.setBold(False) font.setBold(False)
@ -239,7 +242,7 @@ class Ui_MainWindow(object):
self.scenario_label_9.setFont(font) self.scenario_label_9.setFont(font)
self.scenario_label_9.setObjectName("scenario_label_9") self.scenario_label_9.setObjectName("scenario_label_9")
self.awacs_checkBox = QtWidgets.QCheckBox(self.centralwidget) self.awacs_checkBox = QtWidgets.QCheckBox(self.centralwidget)
self.awacs_checkBox.setGeometry(QtCore.QRect(980, 246, 241, 28)) self.awacs_checkBox.setGeometry(QtCore.QRect(980, 266, 241, 28))
font = QtGui.QFont() font = QtGui.QFont()
font.setPointSize(10) font.setPointSize(10)
font.setBold(False) font.setBold(False)
@ -247,7 +250,7 @@ class Ui_MainWindow(object):
self.awacs_checkBox.setChecked(True) self.awacs_checkBox.setChecked(True)
self.awacs_checkBox.setObjectName("awacs_checkBox") self.awacs_checkBox.setObjectName("awacs_checkBox")
self.tankers_checkBox = QtWidgets.QCheckBox(self.centralwidget) self.tankers_checkBox = QtWidgets.QCheckBox(self.centralwidget)
self.tankers_checkBox.setGeometry(QtCore.QRect(980, 282, 241, 28)) self.tankers_checkBox.setGeometry(QtCore.QRect(980, 302, 241, 28))
font = QtGui.QFont() font = QtGui.QFont()
font.setPointSize(10) font.setPointSize(10)
font.setBold(False) font.setBold(False)
@ -277,14 +280,14 @@ class Ui_MainWindow(object):
self.game_status_checkBox.setTristate(False) self.game_status_checkBox.setTristate(False)
self.game_status_checkBox.setObjectName("game_status_checkBox") self.game_status_checkBox.setObjectName("game_status_checkBox")
self.label = QtWidgets.QLabel(self.centralwidget) self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setGeometry(QtCore.QRect(570, 340, 261, 23)) self.label.setGeometry(QtCore.QRect(570, 300, 261, 23))
font = QtGui.QFont() font = QtGui.QFont()
font.setPointSize(10) font.setPointSize(10)
font.setBold(False) font.setBold(False)
self.label.setFont(font) self.label.setFont(font)
self.label.setObjectName("label") self.label.setObjectName("label")
self.inf_spawn_spinBox = QtWidgets.QSpinBox(self.centralwidget) self.inf_spawn_spinBox = QtWidgets.QSpinBox(self.centralwidget)
self.inf_spawn_spinBox.setGeometry(QtCore.QRect(510, 340, 51, 31)) self.inf_spawn_spinBox.setGeometry(QtCore.QRect(510, 300, 51, 31))
font = QtGui.QFont() font = QtGui.QFont()
font.setPointSize(12) font.setPointSize(12)
self.inf_spawn_spinBox.setFont(font) self.inf_spawn_spinBox.setFont(font)
@ -294,7 +297,7 @@ class Ui_MainWindow(object):
self.inf_spawn_spinBox.setProperty("value", 0) self.inf_spawn_spinBox.setProperty("value", 0)
self.inf_spawn_spinBox.setObjectName("inf_spawn_spinBox") self.inf_spawn_spinBox.setObjectName("inf_spawn_spinBox")
self.troop_drop_spinBox = QtWidgets.QSpinBox(self.centralwidget) self.troop_drop_spinBox = QtWidgets.QSpinBox(self.centralwidget)
self.troop_drop_spinBox.setGeometry(QtCore.QRect(510, 300, 51, 31)) self.troop_drop_spinBox.setGeometry(QtCore.QRect(510, 260, 51, 31))
font = QtGui.QFont() font = QtGui.QFont()
font.setPointSize(12) font.setPointSize(12)
self.troop_drop_spinBox.setFont(font) self.troop_drop_spinBox.setFont(font)
@ -312,14 +315,14 @@ class Ui_MainWindow(object):
self.random_weather_checkBox.setTristate(False) self.random_weather_checkBox.setTristate(False)
self.random_weather_checkBox.setObjectName("random_weather_checkBox") self.random_weather_checkBox.setObjectName("random_weather_checkBox")
self.label_3 = QtWidgets.QLabel(self.centralwidget) self.label_3 = QtWidgets.QLabel(self.centralwidget)
self.label_3.setGeometry(QtCore.QRect(570, 300, 281, 23)) self.label_3.setGeometry(QtCore.QRect(570, 260, 281, 23))
font = QtGui.QFont() font = QtGui.QFont()
font.setPointSize(10) font.setPointSize(10)
font.setBold(False) font.setBold(False)
self.label_3.setFont(font) self.label_3.setFont(font)
self.label_3.setObjectName("label_3") self.label_3.setObjectName("label_3")
self.apcs_spawn_checkBox = QtWidgets.QCheckBox(self.centralwidget) self.apcs_spawn_checkBox = QtWidgets.QCheckBox(self.centralwidget)
self.apcs_spawn_checkBox.setGeometry(QtCore.QRect(980, 180, 251, 27)) self.apcs_spawn_checkBox.setGeometry(QtCore.QRect(980, 200, 251, 27))
font = QtGui.QFont() font = QtGui.QFont()
font.setPointSize(10) font.setPointSize(10)
font.setBold(False) font.setBold(False)
@ -563,17 +566,17 @@ class Ui_MainWindow(object):
self.menubar.addAction(self.menuPreferences.menuAction()) self.menubar.addAction(self.menuPreferences.menuAction())
self.retranslateUi(MainWindow) self.retranslateUi(MainWindow)
self.generateButton.clicked.connect(self.action_generateMission.trigger) self.generateButton.clicked.connect(self.action_generateMission.trigger) # type: ignore
self.prevScenario_pushButton.clicked.connect(self.action_prevScenario.trigger) self.prevScenario_pushButton.clicked.connect(self.action_prevScenario.trigger) # type: ignore
self.defense_checkBox.clicked.connect(self.action_defensiveModeChanged.trigger) self.defense_checkBox.clicked.connect(self.action_defensiveModeChanged.trigger) # type: ignore
self.slot_template_comboBox.activated['int'].connect(self.action_slotChanged.trigger) self.slot_template_comboBox.activated['int'].connect(self.action_slotChanged.trigger) # type: ignore
self.scenario_comboBox.currentIndexChanged['int'].connect(self.action_scenarioSelected.trigger) self.scenario_comboBox.currentIndexChanged['int'].connect(self.action_scenarioSelected.trigger) # type: ignore
self.nextScenario_pushButton.clicked.connect(self.action_nextScenario.trigger) self.nextScenario_pushButton.clicked.connect(self.action_nextScenario.trigger) # type: ignore
self.rateButton1.clicked.connect(self.action_rateButton1.trigger) self.rateButton1.clicked.connect(self.action_rateButton1.trigger) # type: ignore
self.rateButton2.clicked.connect(self.action_rateButton2.trigger) self.rateButton2.clicked.connect(self.action_rateButton2.trigger) # type: ignore
self.rateButton3.clicked.connect(self.action_rateButton3.trigger) self.rateButton3.clicked.connect(self.action_rateButton3.trigger) # type: ignore
self.rateButton4.clicked.connect(self.action_rateButton4.trigger) self.rateButton4.clicked.connect(self.action_rateButton4.trigger) # type: ignore
self.rateButton5.clicked.connect(self.action_rateButton5.trigger) self.rateButton5.clicked.connect(self.action_rateButton5.trigger) # type: ignore
QtCore.QMetaObject.connectSlotsByName(MainWindow) QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow): def retranslateUi(self, MainWindow):
@ -581,8 +584,8 @@ class Ui_MainWindow(object):
MainWindow.setWindowTitle(_translate("MainWindow", "RotorOps Mission Generator")) MainWindow.setWindowTitle(_translate("MainWindow", "RotorOps Mission Generator"))
self.logistics_crates_checkBox.setStatusTip(_translate("MainWindow", "Enable a base or FARP near the start position that can spawn CTLD crates for building ground units and air defenses. Sling load the logistics containers to create new logistics sites.")) self.logistics_crates_checkBox.setStatusTip(_translate("MainWindow", "Enable a base or FARP near the start position that can spawn CTLD crates for building ground units and air defenses. Sling load the logistics containers to create new logistics sites."))
self.logistics_crates_checkBox.setText(_translate("MainWindow", "Logistics Base")) self.logistics_crates_checkBox.setText(_translate("MainWindow", "Logistics Base"))
self.zone_sams_checkBox.setStatusTip(_translate("MainWindow", "Inactive conflict zones will be protected by SAMs. When a zone is cleared, SAMs at next active zone will be destroyed. No effect if Blue on defense.")) self.advanced_defenses_checkBox.setStatusTip(_translate("MainWindow", "Each enemy conflict zone spawns a template of AA defenses and radar units that may spawn fighter intercepts for detected aircraft. A good difficulty multiplier for multiplayer."))
self.zone_sams_checkBox.setText(_translate("MainWindow", "Protect Inactive Zones")) self.advanced_defenses_checkBox.setText(_translate("MainWindow", "Enemy Advanced Defenses"))
self.red_forces_label.setText(_translate("MainWindow", "Red Forces:")) self.red_forces_label.setText(_translate("MainWindow", "Red Forces:"))
self.scenario_comboBox.setStatusTip(_translate("MainWindow", "Tip: You can create your own templates that include mission options like kneeboards, briefings, weather, static units, triggers, scripts, etc.")) self.scenario_comboBox.setStatusTip(_translate("MainWindow", "Tip: You can create your own templates that include mission options like kneeboards, briefings, weather, static units, triggers, scripts, etc."))
self.description_textBrowser.setHtml(_translate("MainWindow", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n" self.description_textBrowser.setHtml(_translate("MainWindow", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
@ -624,7 +627,7 @@ class Ui_MainWindow(object):
self.game_status_checkBox.setText(_translate("MainWindow", "Game Status Display")) self.game_status_checkBox.setText(_translate("MainWindow", "Game Status Display"))
self.label.setStatusTip(_translate("MainWindow", "Total number of infantry groups to spawn per game.")) self.label.setStatusTip(_translate("MainWindow", "Total number of infantry groups to spawn per game."))
self.label.setText(_translate("MainWindow", "Infantry Spawns")) self.label.setText(_translate("MainWindow", "Infantry Spawns"))
self.inf_spawn_spinBox.setStatusTip(_translate("MainWindow", "This value is multiplied by the number of spawn zones in the mission template.")) self.inf_spawn_spinBox.setStatusTip(_translate("MainWindow", "Total number of infantry groups to spawn per game."))
self.troop_drop_spinBox.setStatusTip(_translate("MainWindow", "The number of troop drops per transport helicopter flight.")) self.troop_drop_spinBox.setStatusTip(_translate("MainWindow", "The number of troop drops per transport helicopter flight."))
self.random_weather_checkBox.setStatusTip(_translate("MainWindow", "Random weather preset will be applied.")) self.random_weather_checkBox.setStatusTip(_translate("MainWindow", "Random weather preset will be applied."))
self.random_weather_checkBox.setText(_translate("MainWindow", "Random Weather")) self.random_weather_checkBox.setText(_translate("MainWindow", "Random Weather"))

View File

@ -54,7 +54,7 @@
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>980</x> <x>980</x>
<y>211</y> <y>231</y>
<width>251</width> <width>251</width>
<height>28</height> <height>28</height>
</rect> </rect>
@ -75,11 +75,11 @@
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
<widget class="QCheckBox" name="zone_sams_checkBox"> <widget class="QCheckBox" name="advanced_defenses_checkBox">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>980</x> <x>510</x>
<y>320</y> <y>350</y>
<width>241</width> <width>241</width>
<height>28</height> <height>28</height>
</rect> </rect>
@ -91,10 +91,10 @@
</font> </font>
</property> </property>
<property name="statusTip"> <property name="statusTip">
<string>Inactive conflict zones will be protected by SAMs. When a zone is cleared, SAMs at next active zone will be destroyed. No effect if Blue on defense.</string> <string>Each enemy conflict zone spawns a template of AA defenses and radar units that may spawn fighter intercepts for detected aircraft. A good difficulty multiplier for multiplayer.</string>
</property> </property>
<property name="text"> <property name="text">
<string>Protect Inactive Zones</string> <string>Enemy Advanced Defenses</string>
</property> </property>
</widget> </widget>
<widget class="QLabel" name="red_forces_label"> <widget class="QLabel" name="red_forces_label">
@ -190,8 +190,8 @@ p, li { white-space: pre-wrap; }
</property> </property>
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>470</x> <x>980</x>
<y>130</y> <y>140</y>
<width>156</width> <width>156</width>
<height>28</height> <height>28</height>
</rect> </rect>
@ -271,7 +271,7 @@ p, li { white-space: pre-wrap; }
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>570</x> <x>570</x>
<y>220</y> <y>180</y>
<width>271</width> <width>271</width>
<height>24</height> <height>24</height>
</rect> </rect>
@ -421,12 +421,17 @@ p, li { white-space: pre-wrap; }
<widget class="QLabel" name="version_label"> <widget class="QLabel" name="version_label">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>1140</x> <x>1070</x>
<y>650</y> <y>650</y>
<width>111</width> <width>181</width>
<height>20</height> <height>20</height>
</rect> </rect>
</property> </property>
<property name="font">
<font>
<pointsize>8</pointsize>
</font>
</property>
<property name="text"> <property name="text">
<string>Version string</string> <string>Version string</string>
</property> </property>
@ -438,7 +443,7 @@ p, li { white-space: pre-wrap; }
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>570</x> <x>570</x>
<y>260</y> <y>220</y>
<width>271</width> <width>271</width>
<height>24</height> <height>24</height>
</rect> </rect>
@ -460,7 +465,7 @@ p, li { white-space: pre-wrap; }
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>510</x> <x>510</x>
<y>260</y> <y>220</y>
<width>51</width> <width>51</width>
<height>31</height> <height>31</height>
</rect> </rect>
@ -496,7 +501,7 @@ p, li { white-space: pre-wrap; }
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>510</x> <x>510</x>
<y>220</y> <y>180</y>
<width>51</width> <width>51</width>
<height>31</height> <height>31</height>
</rect> </rect>
@ -532,7 +537,7 @@ p, li { white-space: pre-wrap; }
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>510</x> <x>510</x>
<y>180</y> <y>140</y>
<width>51</width> <width>51</width>
<height>31</height> <height>31</height>
</rect> </rect>
@ -574,7 +579,7 @@ p, li { white-space: pre-wrap; }
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>570</x> <x>570</x>
<y>180</y> <y>140</y>
<width>271</width> <width>271</width>
<height>24</height> <height>24</height>
</rect> </rect>
@ -633,7 +638,7 @@ p, li { white-space: pre-wrap; }
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>980</x> <x>980</x>
<y>246</y> <y>266</y>
<width>241</width> <width>241</width>
<height>28</height> <height>28</height>
</rect> </rect>
@ -658,7 +663,7 @@ p, li { white-space: pre-wrap; }
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>980</x> <x>980</x>
<y>282</y> <y>302</y>
<width>241</width> <width>241</width>
<height>28</height> <height>28</height>
</rect> </rect>
@ -758,7 +763,7 @@ p, li { white-space: pre-wrap; }
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>570</x> <x>570</x>
<y>340</y> <y>300</y>
<width>261</width> <width>261</width>
<height>23</height> <height>23</height>
</rect> </rect>
@ -780,7 +785,7 @@ p, li { white-space: pre-wrap; }
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>510</x> <x>510</x>
<y>340</y> <y>300</y>
<width>51</width> <width>51</width>
<height>31</height> <height>31</height>
</rect> </rect>
@ -791,7 +796,7 @@ p, li { white-space: pre-wrap; }
</font> </font>
</property> </property>
<property name="statusTip"> <property name="statusTip">
<string>This value is multiplied by the number of spawn zones in the mission template.</string> <string>Total number of infantry groups to spawn per game.</string>
</property> </property>
<property name="buttonSymbols"> <property name="buttonSymbols">
<enum>QAbstractSpinBox::PlusMinus</enum> <enum>QAbstractSpinBox::PlusMinus</enum>
@ -810,7 +815,7 @@ p, li { white-space: pre-wrap; }
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>510</x> <x>510</x>
<y>300</y> <y>260</y>
<width>51</width> <width>51</width>
<height>31</height> <height>31</height>
</rect> </rect>
@ -867,7 +872,7 @@ p, li { white-space: pre-wrap; }
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>570</x> <x>570</x>
<y>300</y> <y>260</y>
<width>281</width> <width>281</width>
<height>23</height> <height>23</height>
</rect> </rect>
@ -889,7 +894,7 @@ p, li { white-space: pre-wrap; }
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>980</x> <x>980</x>
<y>180</y> <y>200</y>
<width>251</width> <width>251</width>
<height>27</height> <height>27</height>
</rect> </rect>

75
Generator/README.md Normal file
View File

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

View File

@ -29,7 +29,9 @@ def triggerSetup(rops, options):
"RotorOps.zone_status_display = " + lb("game_display") + "\n\n" + "RotorOps.zone_status_display = " + lb("game_display") + "\n\n" +
"RotorOps.inf_spawn_messages = true\n\n" + "RotorOps.inf_spawn_messages = true\n\n" +
"RotorOps.inf_spawns_total = " + lb("inf_spawn_qty") + "\n\n" + "RotorOps.inf_spawns_total = " + lb("inf_spawn_qty") + "\n\n" +
"RotorOps.apcs_spawn_infantry = " + lb("apc_spawns_inf") + " \n\n") "RotorOps.apcs_spawn_infantry = " + lb("apc_spawns_inf") + " \n\n" +
"RotorOps.fighter_min_detection_alt = 609\n\n" +
"RotorOps.fighter_max_active = 2\n\n")
if not options["smoke_pickup_zones"]: if not options["smoke_pickup_zones"]:
script = script + 'RotorOps.pickup_zone_smoke = "none"\n\n' script = script + 'RotorOps.pickup_zone_smoke = "none"\n\n'
trig.actions.append(dcs.action.DoScript(dcs.action.String((script)))) trig.actions.append(dcs.action.DoScript(dcs.action.String((script))))
@ -58,6 +60,20 @@ def triggerSetup(rops, options):
trig.actions.append(dcs.action.DoScript(dcs.action.String("RotorOps.startConflict(100)"))) trig.actions.append(dcs.action.DoScript(dcs.action.String("RotorOps.startConflict(100)")))
rops.m.triggerrules.triggers.append(trig) rops.m.triggerrules.triggers.append(trig)
if options["rotorops_server"]:
trig = dcs.triggers.TriggerOnce(comment="RotorOps Set Up Server")
trig.rules.append(dcs.condition.TimeAfter(4))
trig.actions.append(dcs.action.DoScriptFile(rops.scripts["RotorOpsServer.lua"]))
# Slot block the zone spawns if SSB is available
trig.actions.append(dcs.action.SetFlagValue('SSB', 100))
for c_zone in rops.conflict_zones:
for group in rops.all_zones[c_zone].player_helo_spawns:
trig.actions.append(dcs.action.SetFlagValue(group.name, 100))
rops.m.triggerrules.triggers.append(trig)
# Add generic zone-based triggers # Add generic zone-based triggers
for index, zone_name in enumerate(rops.conflict_zones): for index, zone_name in enumerate(rops.conflict_zones):
z_active_trig = dcs.triggers.TriggerOnce(comment=zone_name + " Active") z_active_trig = dcs.triggers.TriggerOnce(comment=zone_name + " Active")
@ -74,14 +90,14 @@ def triggerSetup(rops, options):
# dcs.action.DoScript(dcs.action.String("ctld.createRadioBeaconAtZone('" + c_zone + "','blue', 1440,'" + c_zone + "')"))) # dcs.action.DoScript(dcs.action.String("ctld.createRadioBeaconAtZone('" + c_zone + "','blue', 1440,'" + c_zone + "')")))
# rops.m.triggerrules.triggers.append(trig) # rops.m.triggerrules.triggers.append(trig)
# Zone protection SAMs # # Zone protection SAMs
if options["zone_protect_sams"]: # if options["zone_protect_sams"]:
for index, zone_name in enumerate(rops.conflict_zones): # for index, zone_name in enumerate(rops.conflict_zones):
z_sams_trig = dcs.triggers.TriggerOnce(comment="Deactivate " + zone_name + " SAMs") # z_sams_trig = dcs.triggers.TriggerOnce(comment="Deactivate " + zone_name + " SAMs")
z_sams_trig.rules.append(dcs.condition.FlagEquals(game_flag, index + 1)) # z_sams_trig.rules.append(dcs.condition.FlagEquals(game_flag, index + 1))
z_sams_trig.actions.append(dcs.action.DoScript( # z_sams_trig.actions.append(dcs.action.DoScript(
dcs.action.String("Group.destroy(Group.getByName('Static " + zone_name + " Protection SAM'))"))) # dcs.action.String("Group.destroy(Group.getByName('" + zone_name + " Protect Static'))")))
rops.m.triggerrules.triggers.append(z_sams_trig) # rops.m.triggerrules.triggers.append(z_sams_trig)
# Deactivate zone FARPs and player slots in defensive mode: # Deactivate zone FARPs and player slots in defensive mode:
# this will also deactivate players already in the air. # this will also deactivate players already in the air.
@ -107,11 +123,10 @@ def triggerSetup(rops, options):
z_farps_trig.rules.append(dcs.condition.FlagEquals(game_flag, index + 1)) z_farps_trig.rules.append(dcs.condition.FlagEquals(game_flag, index + 1))
z_farps_trig.actions.append( z_farps_trig.actions.append(
dcs.action.ActivateGroup(rops.m.country(jtf_blue).find_group(previous_zone + " FARP Static").id)) dcs.action.ActivateGroup(rops.m.country(jtf_blue).find_group(previous_zone + " FARP Static").id))
# Activate late-activated helicopters at FARPs. Doesn't work consistently # Activate late-activated helicopters at FARPs if SSB slot blocking script is available
# for group in rops.all_zones[previous_zone].player_helo_spawns: for group in rops.all_zones[previous_zone].player_helo_spawns:
# z_farps_trig.actions.append( z_farps_trig.actions.append(
# dcs.action.ActivateGroup( dcs.action.SetFlagValue(group.name, 0))
# group.id))
z_farps_trig.actions.append(dcs.action.DoScript(dcs.action.String( z_farps_trig.actions.append(dcs.action.DoScript(dcs.action.String(
"RotorOps.farpEstablished(" + str(index) + ", '" + previous_zone + "_FARP')"))) "RotorOps.farpEstablished(" + str(index) + ", '" + previous_zone + "_FARP')")))
rops.m.triggerrules.triggers.append(z_farps_trig) rops.m.triggerrules.triggers.append(z_farps_trig)
@ -130,11 +145,10 @@ def triggerSetup(rops, options):
"--The 100 flag indicates which zone is active. The 111 flag value is the percentage of staged units remaining"))) "--The 100 flag indicates which zone is active. The 111 flag value is the percentage of staged units remaining")))
z_farps_trig.actions.append( z_farps_trig.actions.append(
dcs.action.ActivateGroup(rops.m.country(jtf_blue).find_group(previous_zone + " FARP Static").id)) dcs.action.ActivateGroup(rops.m.country(jtf_blue).find_group(previous_zone + " FARP Static").id))
# Activate late-activated helicopters at FARPs. Doesn't work consistently # Activate late-activated helicopters at FARPs if SSB slot blocking script is available
# for group in rops.all_zones[previous_zone].player_helo_spawns: for group in rops.all_zones[previous_zone].player_helo_spawns:
# z_farps_trig.actions.append( z_farps_trig.actions.append(
# dcs.action.ActivateGroup( dcs.action.SetFlagValue(group.name, 0))
# group.id))
z_farps_trig.actions.append(dcs.action.DoScript(dcs.action.String( z_farps_trig.actions.append(dcs.action.DoScript(dcs.action.String(
"RotorOps.farpEstablished(" + str(index) + ", '" + previous_zone + "_FARP')"))) "RotorOps.farpEstablished(" + str(index) + ", '" + previous_zone + "_FARP')")))
rops.m.triggerrules.triggers.append(z_farps_trig) rops.m.triggerrules.triggers.append(z_farps_trig)
@ -178,10 +192,18 @@ def triggerSetup(rops, options):
dcs.action.String("RotorOps.spawnTranspHelos(8," + str(options["transport_drop_qty"]) + ")"))) dcs.action.String("RotorOps.spawnTranspHelos(8," + str(options["transport_drop_qty"]) + ")")))
rops.m.triggerrules.triggers.append(z_weak_trig) rops.m.triggerrules.triggers.append(z_weak_trig)
# Add enemy CAP spawn trigger
cap_trig = dcs.triggers.TriggerContinious(comment="Spawn Enemy CAP")
cap_trig.rules.append(dcs.condition.TimeAfter(10))
cap_trig.rules.append(dcs.condition.Predicate(dcs.action.String("return RotorOps.predSpawnRedCap()")))
cap_trig.actions.append(dcs.action.DoScript(dcs.action.String("RotorOps.deployFighters()")))
rops.m.triggerrules.triggers.append(cap_trig)
# Add game won/lost triggers # Add game won/lost triggers
# Add game won triggers # Add game won triggers
mission_end_delay = 1200
trig = dcs.triggers.TriggerOnce(comment="RotorOps Conflict WON") trig = dcs.triggers.TriggerOnce(comment="RotorOps Conflict WON")
trig.rules.append(dcs.condition.FlagEquals(game_flag, 99)) trig.rules.append(dcs.condition.FlagEquals(game_flag, 99))
trig.actions.append( trig.actions.append(
@ -189,6 +211,17 @@ def triggerSetup(rops, options):
if options["end_trigger"] is not False: if options["end_trigger"] is not False:
trig.actions.append( trig.actions.append(
dcs.action.DoScript(dcs.action.String("RotorOps.gameMsg(RotorOps.gameMsgs.success)"))) dcs.action.DoScript(dcs.action.String("RotorOps.gameMsg(RotorOps.gameMsgs.success)")))
trig.actions.append(dcs.action.DoScript(dcs.action.String(
"timer.scheduleFunction(function()trigger.action.setUserFlag('mission_end', 2) end, {}, timer.getTime() + " + str(
mission_end_delay) + ")")))
trig.actions.append(dcs.action.MessageToAll(dcs.action.String("Time to RTB. Mission will end soon."), mission_end_delay))
mission_end_trigger = dcs.triggers.TriggerOnce(comment="End the mission")
mission_end_trigger.rules.append(dcs.condition.FlagEquals("mission_end", 2))
mission_end_trigger.actions.append(dcs.action.EndMission(text=dcs.action.String("Blue forces won!")))
rops.m.triggerrules.triggers.append(mission_end_trigger)
rops.m.triggerrules.triggers.append(trig) rops.m.triggerrules.triggers.append(trig)
# Add game lost triggers # Add game lost triggers
@ -198,4 +231,14 @@ def triggerSetup(rops, options):
dcs.action.DoScript(dcs.action.String("---Add an action you want to happen when the game is LOST"))) dcs.action.DoScript(dcs.action.String("---Add an action you want to happen when the game is LOST")))
if options["end_trigger"] is not False: if options["end_trigger"] is not False:
trig.actions.append(dcs.action.DoScript(dcs.action.String("RotorOps.gameMsg(RotorOps.gameMsgs.failure)"))) trig.actions.append(dcs.action.DoScript(dcs.action.String("RotorOps.gameMsg(RotorOps.gameMsgs.failure)")))
trig.actions.append(dcs.action.DoScript(dcs.action.String(
"timer.scheduleFunction(function()trigger.action.setUserFlag('mission_end', 1) end, {}, timer.getTime() + " + str(
mission_end_delay) + ")")))
trig.actions.append(
dcs.action.MessageToAll(dcs.action.String("Time to RTB. Mission will end soon."), mission_end_delay))
mission_end_trigger = dcs.triggers.TriggerOnce(comment="End the mission")
mission_end_trigger.rules.append(dcs.condition.FlagEquals("mission_end", 1))
mission_end_trigger.actions.append(dcs.action.EndMission(text=dcs.action.String("Red forces won!")))
rops.m.triggerrules.triggers.append(mission_end_trigger)
rops.m.triggerrules.triggers.append(trig) rops.m.triggerrules.triggers.append(trig)

View File

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

View File

@ -32,6 +32,10 @@ class RotorOpsMission:
self.res_map = {} self.res_map = {}
self.config = None # not used self.config = None # not used
self.imports = None self.imports = None
self.red_zones = {}
self.blue_zones = {}
self.primary_e_airport = None
class RotorOpsZone: class RotorOpsZone:
def __init__(self, name: str, flag: int, position: dcs.point, size: int): def __init__(self, name: str, flag: int, position: dcs.point, size: int):
@ -132,7 +136,22 @@ class RotorOpsMission:
window.statusBar().showMessage("Loading scenario mission", 10000) window.statusBar().showMessage("Loading scenario mission", 10000)
self.m.load_file(options["scenario_file"]) # self.m.load_file(options["scenario_file"])
# Bypass trig, triggrules, and triggers. Then load triggers
# manually. We want to get our zones from the template mission, but leave the existing trigger actions and
# conditions the same, since pydcs cannot yet handle some of them well.
self.m.load_file(options["scenario_file"], True)
self.m.triggers.load_from_dict(self.m.bypassed_triggers)
# Create some 'empty' triggerrules so that we can maintain indexing when we merge dictionaries on save
for rule in self.m.bypassed_trigrules:
trig = dcs.triggers.TriggerOnce(comment="Empty " + str(rule))
trig.rules.append(dcs.condition.TimeAfter(1))
trig.actions.append(dcs.action.DoScript(dcs.action.String("Filler " + str(rule))))
self.m.triggerrules.triggers.append(trig)
# Add countries if they're missing # Add countries if they're missing
if not self.m.country(jtf_red): if not self.m.country(jtf_red):
self.m.coalition.get("red").add_country(dcs.countries.CombinedJointTaskForcesRed()) self.m.coalition.get("red").add_country(dcs.countries.CombinedJointTaskForcesRed())
@ -200,11 +219,11 @@ class RotorOpsMission:
elif zone.name.rfind("SPAWN") >= 0: elif zone.name.rfind("SPAWN") >= 0:
self.addZone(self.spawn_zones, self.RotorOpsZone(zone.name, None, zone.position, zone.radius)) self.addZone(self.spawn_zones, self.RotorOpsZone(zone.name, None, zone.position, zone.radius))
blue_zones = self.staging_zones self.blue_zones = self.staging_zones
red_zones = self.conflict_zones self.red_zones = self.conflict_zones
if options["defending"]: if options["defending"]:
blue_zones = self.conflict_zones self.blue_zones = self.conflict_zones
red_zones = self.staging_zones self.red_zones = self.staging_zones
# swap airport sides # swap airport sides
self.swapSides(options) self.swapSides(options)
@ -214,119 +233,10 @@ class RotorOpsMission:
if options["player_hotstart"]: if options["player_hotstart"]:
start_type = dcs.mission.StartType.Warm start_type = dcs.mission.StartType.Warm
# Adds vehicles as a single group (for easy late activation), and helicopters if enabled in settings
# def addZoneFARP(_zone_name, country, file):
#
# farp_flag = self.m.find_group(_zone_name)
#
# if farp_flag:
# farp_position = farp_flag.units[0].position
# farp_heading = farp_flag.units[0].heading
# else:
# farp_position = self.all_zones[_zone_name].position
# farp_heading = 0
#
# # Add the basic invisible farp object
# farp = self.m.farp(self.m.country(country), _zone_name + " FARP", farp_position,
# hidden=False, dead=False,
# farp_type=dcs.unit.InvisibleFARP)
#
# # Use alternate template file if it has been defined in scenario config
# if options["zone_farp_file"]:
#
# for i in imports:
# if i.filename.removesuffix('.miz') == options["zone_farp_file"]:
# file = i.path
# # if multiple files found, we want the latest file to override the first
#
# i = ImportObjects(file)
# i.anchorByGroupName("ANCHOR")
# farp_group = i.copyVehiclesAsGroup(self.m, country, _zone_name + " FARP Static", farp_position,
# farp_heading)
# # Add client helicopters
# if options["farp_spawns"]:
# helicopter_groups = i.copyHelicopters(self.m, jtf_blue, "ZONE " + _zone_name + " EMPTY ", farp_position, farp_heading)
# for group in helicopter_groups:
# self.all_zones[_zone_name].player_helo_spawns.append(group)
#
# return farp_group
# # Adds statics, vehicles, and helicopters. Late activation is not possible for zone_name in self.red_zones:
# def addLogisticsZone(_zone_name, country, file, config_name, helicopters=False):
# flag = self.m.find_group(_zone_name)
# if flag:
# position = flag.units[0].position
# heading = flag.units[0].heading
# else:
# position = self.all_zones[_zone_name].position
# heading = 0
#
# # Use alternate template file if it has been defined in scenario config
# if options[config_name]:
#
# for i in imports:
# if i.filename.removesuffix('.miz') == options[config_name]:
# file = i.path
# # if multiple files found, we want the latest file to override the first
#
# # Import statics and vehicles
# i = ImportObjects(file)
# i.anchorByGroupName("ANCHOR")
# i.copyStatics(self.m, country, _zone_name + " Logistics Zone",
# position, heading)
# i.copyVehicles(self.m, country, _zone_name + " Logistics Zone",
# position, heading)
#
# # Add client helicopters
# if helicopters:
# helicopter_groups = i.copyHelicopters(self.m, jtf_blue, "ZONE " + _zone_name + " EMPTY ", position,
# heading)
# for group in helicopter_groups:
# self.all_zones[_zone_name].player_helo_spawns.append(group)
# Adds statics, vehicles, and helicopters (if enabled). Late activation is not possible.
# def addDefensiveFARP(_zone_name, country, file):
#
# farp_flag = self.m.find_group(_zone_name)
#
# if farp_flag:
# farp_position = farp_flag.units[0].position
# farp_heading = farp_flag.units[0].heading
# else:
# farp_position = self.all_zones[_zone_name].position
# farp_heading = 0
#
# # Add the basic invisible farp object
# farp = self.m.farp(self.m.country(country), _zone_name + " FARP", farp_position,
# hidden=False, dead=False,
# farp_type=dcs.unit.InvisibleFARP)
#
# # Use alternate template file if it has been defined in scenario config
# if options["defensive_farp_file"]:
#
# for i in imports:
# if i.filename.removesuffix('.miz') == options["defensive_farp_file"]:
# file = i.path
# # if multiple files found, we want the latest file to override the first
#
# # Import statics and vehicles
# i = ImportObjects(file)
# i.anchorByGroupName("ANCHOR")
# i.copyStatics(self.m, country, _zone_name + " Logistics Zone",
# farp_position, farp_heading)
# i.copyVehicles(self.m, country, _zone_name + " Logistics Zone",
# farp_position, farp_heading)
#
# # Import player helicopters
# if options["farp_spawns"]:
# helicopter_groups = i.copyHelicopters(self.m, jtf_blue, "ZONE " + _zone_name + " EMPTY ", farp_position,
# farp_heading)
# for group in helicopter_groups:
# self.all_zones[_zone_name].player_helo_spawns.append(group)
for zone_name in red_zones:
if red_forces["vehicles"]: if red_forces["vehicles"]:
self.addGroundGroups(red_zones[zone_name], self.m.country(jtf_red), red_forces["vehicles"], self.addGroundGroups(self.red_zones[zone_name], self.m.country(jtf_red), red_forces["vehicles"],
options["red_quantity"]) options["red_quantity"])
if options["zone_farps"] != "farp_never" and not options["defending"]: if options["zone_farps"] != "farp_never" and not options["defending"]:
@ -340,8 +250,8 @@ class RotorOpsMission:
file=activated_farp, file=activated_farp,
config_name="zone_farp_file", config_name="zone_farp_file",
copy_helicopters=helicopters, copy_helicopters=helicopters,
helicopters_name="ZONE " + zone_name + " EMPTY", helicopters_name="ZONE " + zone_name,
heli_start_type=dcs.mission.StartType.Cold, heli_start_type=None,
copy_vehicles=True, copy_vehicles=True,
vehicles_name=zone_name + " FARP Static", vehicles_name=zone_name + " FARP Static",
copy_statics=False, copy_statics=False,
@ -352,34 +262,21 @@ class RotorOpsMission:
) )
vehicle_group.late_activation = True vehicle_group.late_activation = True
# For SAMs: Add vehicles as a single group (for easy late activation)
if options["zone_protect_sams"]: if options["advanced_defenses"]:
sam_group = self.addZoneBase(options, zone_name, jtf_red, sam_group = self.addZoneBase(options, zone_name, jtf_red,
file=zone_protect, file=zone_protect,
config_name="zone_protect_file", config_name="zone_protect_file",
copy_vehicles=True, copy_vehicles=True,
vehicles_name=zone_name + " Protect Static", vehicles_name=zone_name + " Defense Static",
vehicles_single_group=True vehicles_single_group=False
) )
# farp_flag = self.m.find_group(zone_name)
#
# if farp_flag:
# farp_position = farp_flag.units[0].position
# farp_heading = farp_flag.units[0].heading
# else:
# farp_position = self.all_zones[zone_name].position
# farp_heading = 0
#
# i = ImportObjects(zone_protect)
# i.anchorByGroupName("ANCHOR")
# farp_group = i.copyVehiclesAsGroup(self.m, jtf_red, "Static " + zone_name + " Protection SAM",
# farp_position,
# farp_heading)
# Populate Blue zones with ground units # Populate Blue zones with ground units
for i, zone_name in enumerate(blue_zones): for i, zone_name in enumerate(self.blue_zones):
if blue_forces["vehicles"]: if blue_forces["vehicles"]:
self.addGroundGroups(blue_zones[zone_name], self.m.country(jtf_blue), blue_forces["vehicles"], self.addGroundGroups(self.blue_zones[zone_name], self.m.country(jtf_blue), blue_forces["vehicles"],
options["blue_quantity"]) options["blue_quantity"])
# Add blue zone FARPS (not late activated) for defensive mode # Add blue zone FARPS (not late activated) for defensive mode
@ -389,7 +286,7 @@ class RotorOpsMission:
if options["farp_spawns"]: if options["farp_spawns"]:
helicopters = True helicopters = True
if options["crates"] and i == len(blue_zones) - 1: if options["crates"] and i == len(self.blue_zones) - 1:
# add a logistics zone to the last conflict zone # add a logistics zone to the last conflict zone
# addLogisticsZone(zone_name, jtf_blue, logistics_farp, "logistics_farp_file", helicopters) # addLogisticsZone(zone_name, jtf_blue, logistics_farp, "logistics_farp_file", helicopters)
self.addZoneBase(options, zone_name, jtf_blue, self.addZoneBase(options, zone_name, jtf_blue,
@ -397,7 +294,7 @@ class RotorOpsMission:
config_name="logistics_farp_file", config_name="logistics_farp_file",
copy_helicopters=helicopters, copy_helicopters=helicopters,
helicopters_name="ZONE " + zone_name + " LOGISTICS", helicopters_name="ZONE " + zone_name + " LOGISTICS",
heli_start_type=start_type, heli_start_type=None,
copy_vehicles=True, copy_vehicles=True,
vehicles_name=zone_name + " Logistics FARP", vehicles_name=zone_name + " Logistics FARP",
copy_statics=True, copy_statics=True,
@ -413,7 +310,7 @@ class RotorOpsMission:
config_name="defensive_farp_file", config_name="defensive_farp_file",
copy_helicopters=helicopters, copy_helicopters=helicopters,
helicopters_name="ZONE " + zone_name + " EMPTY", helicopters_name="ZONE " + zone_name + " EMPTY",
heli_start_type=dcs.mission.StartType.Cold, heli_start_type=None,
copy_vehicles=True, copy_vehicles=True,
vehicles_name=zone_name + " Defensive FARP", vehicles_name=zone_name + " Defensive FARP",
copy_statics=True, copy_statics=True,
@ -443,7 +340,9 @@ class RotorOpsMission:
# Add player slots # Add player slots
window.statusBar().showMessage("Adding flights to mission...", 10000) window.statusBar().showMessage("Adding flights to mission...", 10000)
if options["slots"] != "Locked to Scenario" and options["slots"] != "None": if options["slots"] == "Locked to Scenario" or options["slots"] == "None":
pass
else:
self.addPlayerHelos(options) self.addPlayerHelos(options)
# Add AI Flights # Add AI Flights
@ -482,11 +381,15 @@ class RotorOpsMission:
self.m.weather.wind_at_8000.direction = (wind_dir + random.randrange(-90, 90) - 180) % 360 self.m.weather.wind_at_8000.direction = (wind_dir + random.randrange(-90, 90) - 180) % 360
self.m.weather.wind_at_8000.speed = wind_speed + random.randrange(-1, 10) self.m.weather.wind_at_8000.speed = wind_speed + random.randrange(-1, 10)
self.m.weather.halo.preset = dcs.weather.Halo.Preset.Auto
self.m.weather.halo.crystals = None
logger.info("Cloud preset = " + cloud_preset.ui_name + ", ground windspeed = " + str( logger.info("Cloud preset = " + cloud_preset.ui_name + ", ground windspeed = " + str(
self.m.weather.wind_at_ground.speed)) self.m.weather.wind_at_ground.speed))
if options["time"] != "Default Time": if options["time"] != "Default Time":
self.m.random_daytime(options["time"].lower()) self.m.random_daytime(options["time"].lower())
print("Time set to " + options["time"])
# Save the mission file # Save the mission file
window.statusBar().showMessage("Saving mission...", 10000) window.statusBar().showMessage("Saving mission...", 10000)
@ -496,12 +399,22 @@ class RotorOpsMission:
output_dir = directories.output # default dir output_dir = directories.output # default dir
os.chdir(output_dir) os.chdir(output_dir)
output_filename = options["scenario_name"] + " " + time.strftime('%a%H%M%S') + '.miz' output_filename = options["scenario_name"] + " " + time.strftime('%a%H%M%S') + '.miz'
# dcs.mission.save will use the bypassed trig, trigrules, and triggers. Our goal is to leave the trigrules and
# trig from the source mission untouched. See comments in self.m.load_file above
#merge dictionaries
self.m.bypassed_trig = self.m.triggerrules.trig() | self.m.bypassed_trig
self.m.bypassed_trigrules = self.m.triggerrules.trigrules() | self.m.bypassed_trigrules
self.m.bypassed_triggers = self.m.triggers.dict()
success = self.m.save(output_filename) success = self.m.save(output_filename)
return {"success": success, "filename": output_filename, "directory": output_dir} # let the UI know the result return {"success": success, "filename": output_filename, "directory": output_dir} # let the UI know the result
# Use the ImportObjects class to place farps and bases # Use the ImportObjects class to place farps and bases
def addZoneBase(self, options, _zone_name, country, file, config_name=None, copy_helicopters=False, def addZoneBase(self, options, _zone_name, country, file, config_name=None, copy_helicopters=False,
helicopters_name="", heli_start_type=dcs.mission.StartType.Cold, helicopters_name="", heli_start_type=None,
copy_vehicles=False, vehicles_name="", copy_statics=False, statics_names="", copy_vehicles=False, vehicles_name="", copy_statics=False, statics_names="",
vehicles_single_group=False, trigger_name=None, trigger_radius=110, farp=True): vehicles_single_group=False, trigger_name=None, trigger_radius=110, farp=True):
@ -598,7 +511,7 @@ class RotorOpsMission:
if len(airport.free_parking_slots(aircraft)) >= group_size: if len(airport.free_parking_slots(aircraft)) >= group_size:
if not (aircraft.id in dcs.planes.plane_map and ( if not (aircraft.id in dcs.planes.plane_map and (
len(airport.runways) == 0 or airport.runways[0].ils is None)): len(airport.runways) == 0 or not hasattr(airport.runways[0], "ils"))):
return airport return airport
if alt_airports: if alt_airports:
@ -703,7 +616,7 @@ class RotorOpsMission:
for helicopter in dcs.helicopters.helicopter_map: for helicopter in dcs.helicopters.helicopter_map:
if helicopter == options["slots"]: if helicopter == options["slots"]:
client_helos = [dcs.helicopters.helicopter_map[ client_helos = [dcs.helicopters.helicopter_map[
helicopter]] # if out ui slot option matches a specific helicopter type name helicopter]] # if our ui slot option matches a specific helicopter type name
# get loadouts from miz file and put into a simple dict # get loadouts from miz file and put into a simple dict
default_loadouts = {} default_loadouts = {}
@ -757,6 +670,8 @@ class RotorOpsMission:
helotype = None helotype = None
if helicopter_id in dcs.helicopters.helicopter_map: if helicopter_id in dcs.helicopters.helicopter_map:
helotype = dcs.helicopters.helicopter_map[helicopter_id] helotype = dcs.helicopters.helicopter_map[helicopter_id]
elif helicopter_id in dcs.planes.plane_map:
helotype = dcs.planes.plane_map[helicopter_id]
else: else:
continue continue
if carrier: if carrier:
@ -846,7 +761,7 @@ class RotorOpsMission:
heading = enemy_heading + random.randrange(70, 110) heading = enemy_heading + random.randrange(70, 110)
race_dist = random.randrange(40 * 1000, 80 * 1000) race_dist = random.randrange(40 * 1000, 80 * 1000)
center_pt = dcs.mapping.point_from_heading(friendly_pt.x, friendly_pt.y, center_pt = dcs.mapping.point_from_heading(friendly_pt.x, friendly_pt.y,
enemy_heading - random.randrange(140, 220), 10000) enemy_heading - random.randrange(140, 220), 20000)
pt1 = dcs.mapping.point_from_heading(center_pt[0], center_pt[1], enemy_heading - 90, pt1 = dcs.mapping.point_from_heading(center_pt[0], center_pt[1], enemy_heading - 90,
random.randrange(20 * 1000, 40 * 1000)) random.randrange(20 * 1000, 40 * 1000))
return dcs.mapping.Point(pt1[0], pt1[1], terrain), heading, race_dist return dcs.mapping.Point(pt1[0], pt1[1], terrain), heading, race_dist
@ -857,6 +772,7 @@ class RotorOpsMission:
friendly_airports, primary_f_airport = self.getCoalitionAirports("blue") friendly_airports, primary_f_airport = self.getCoalitionAirports("blue")
enemy_airports, primary_e_airport = self.getCoalitionAirports("red") enemy_airports, primary_e_airport = self.getCoalitionAirports("red")
# find enemy carriers and farps # find enemy carriers and farps
carrier = self.m.country(jtf_red).find_ship_group(name="HELO_CARRIER") carrier = self.m.country(jtf_red).find_ship_group(name="HELO_CARRIER")
if not carrier: if not carrier:
@ -876,6 +792,27 @@ class RotorOpsMission:
primary_f_airport.position.y primary_f_airport.position.y
) )
self.primary_e_airport = primary_e_airport
self.m.triggers.add_triggerzone(primary_e_airport.position, 1500, hidden=True, name="RED_AIRBASE")
if options["red_cap"]:
scenario_red_cap_spawn_zone = None
for zone in self.m.triggers.zones():
if zone.name == "RED_CAP_SPAWN":
scenario_red_cap_spawn_zone = True
if not scenario_red_cap_spawn_zone:
e_cap_spawn_point = primary_e_airport.position.point_from_heading(e_airport_heading, 100000)
self.m.triggers.add_triggerzone(e_cap_spawn_point, 30000, hidden=True, name="RED_CAP_SPAWN")
if options["blue_cap"]:
scenario_blue_cap_spawn_zone = None
for zone in self.m.triggers.zones():
if zone.name == "BLUE_CAP_SPAWN":
scenario_blue_cap_spawn_zone = True
if not scenario_blue_cap_spawn_zone:
f_cap_spawn_point = primary_f_airport.position.point_from_heading(e_airport_heading + 180, 100000)
self.m.triggers.add_triggerzone(f_cap_spawn_point, 30000, hidden=True, name="BLUE_CAP_SPAWN")
if options["f_awacs"]: if options["f_awacs"]:
awacs_name = "AWACS" awacs_name = "AWACS"
awacs_freq = 266 awacs_freq = 266
@ -962,6 +899,7 @@ class RotorOpsMission:
t1_freq) + ".00 " + t1_tac + "\n" + t2_name + " " + str(t2_freq) + ".00 " + t2_tac + "\n\n" t1_freq) + ".00 " + t1_tac + "\n" + t2_name + " " + str(t2_freq) + ".00 " + t2_tac + "\n\n"
self.m.set_description_text(briefing) self.m.set_description_text(briefing)
def zone_attack(fg, airport): def zone_attack(fg, airport):
fg.set_skill(dcs.unit.Skill.High) fg.set_skill(dcs.unit.Skill.High)
fg.late_activation = True fg.late_activation = True
@ -1093,6 +1031,139 @@ class RotorOpsMission:
unit.pylons = source_helo.pylons unit.pylons = source_helo.pylons
unit.livery_id = source_helo.livery_id unit.livery_id = source_helo.livery_id
if False:
for i in range(1,4):
randzone = random.choice(list(self.red_zones))
pt2 = self.red_zones[randzone].position
source_plane = None
if red_forces["fighter_planes"]:
source_group = random.choice(red_forces["fighter_planes"])
source_plane = source_group.units[0]
plane_type = source_plane.unit_type
group_size = random.randrange(1, 2)
else:
group_size = random.randrange(1, 2)
plane_type = random.choice(RotorOpsUnits.e_attack_helos)
airport = self.getParking(primary_e_airport, plane_type, enemy_airports, group_size)
enemy_cap = self.m.patrol_flight(airport=airport,
name="Enemy CAP " + str(i),
country=combinedJointTaskForcesRed,
patrol_type=plane_type,
pos1=primary_e_airport.position,
pos2=pt2,
altitude=3000,
group_size=group_size,
max_engage_distance=40 * 1000
)
# enemy_cap.points[0].tasks[0] = dcs.task.EngageTargets(max_engage_distance, [dcs.task.Targets.All.Air.Planes])
for unit in enemy_cap.units:
unit.skill = dcs.unit.Skill.Random
if source_plane:
for unit in enemy_cap.units:
unit.pylons = source_plane.pylons
unit.livery_id = source_plane.livery_id
if options["red_cap"]:
if red_forces["fighter_planes"]:
for fighter_plane_group in red_forces["fighter_planes"]:
source_group = random.choice(red_forces["fighter_planes"])
source_plane = source_group.units[0]
plane_type = source_plane.unit_type
enemy_cap = self.m.flight_group(
country=combinedJointTaskForcesRed,
name="RED CAP",
aircraft_type=plane_type,
airport=None,
maintask=dcs.task.CAP,
group_size=1,
position=e_cap_spawn_point,
altitude=5000,
speed=300
)
enemy_cap.late_activation = True
for unit in enemy_cap.units:
unit.skill = dcs.unit.Skill.Random
unit.pylons = source_plane.pylons
unit.livery_id = source_plane.livery_id
else:
plane_type = random.choice(RotorOpsUnits.e_fighter_planes)
enemy_cap = self.m.flight_group(
country=combinedJointTaskForcesRed,
name="RED CAP",
aircraft_type=plane_type,
airport=None,
maintask=dcs.task.CAP,
group_size=1,
position=e_cap_spawn_point,
altitude=5000,
speed=300
)
enemy_cap.late_activation = True
for unit in enemy_cap.units:
unit.skill = dcs.unit.Skill.Random
if options["blue_cap"]:
if blue_forces["fighter_planes"]:
for fighter_plane_group in blue_forces["fighter_planes"]:
source_group = random.choice(blue_forces["fighter_planes"])
source_plane = source_group.units[0]
plane_type = source_plane.unit_type
friendly_cap = self.m.flight_group(
country=combinedJointTaskForcesBlue,
name="BLUE CAP",
aircraft_type=plane_type,
airport=None,
maintask=dcs.task.CAP,
group_size=1,
position=f_cap_spawn_point,
altitude=5000,
speed=300
)
friendly_cap.late_activation = True
for unit in friendly_cap.units:
unit.skill = dcs.unit.Skill.Random
unit.pylons = source_plane.pylons
unit.livery_id = source_plane.livery_id
else:
plane_type = random.choice(RotorOpsUnits.f_fighter_planes)
friendly_cap = self.m.flight_group(
country=combinedJointTaskForcesBlue,
name="BLUE CAP",
aircraft_type=plane_type,
airport=None,
maintask=dcs.task.CAP,
group_size=1,
position=f_cap_spawn_point,
altitude=5000,
speed=300
)
friendly_cap.late_activation = True
for unit in friendly_cap.units:
unit.skill = dcs.unit.Skill.Random
def importObjects(self, data): def importObjects(self, data):
imports = data["objects"]["imports"] imports = data["objects"]["imports"]

View File

@ -12,6 +12,7 @@ client_helos = [
player_helos = [ player_helos = [
dcs.helicopters.AH_64D_BLK_II, dcs.helicopters.AH_64D_BLK_II,
dcs.helicopters.Ka_50, dcs.helicopters.Ka_50,
dcs.helicopters.Ka_50_3,
dcs.helicopters.Mi_8MT, dcs.helicopters.Mi_8MT,
dcs.helicopters.Mi_24P, dcs.helicopters.Mi_24P,
dcs.helicopters.SA342M, dcs.helicopters.SA342M,
@ -20,6 +21,7 @@ player_helos = [
dcs.helicopters.SA342Mistral, dcs.helicopters.SA342Mistral,
dcs.helicopters.UH_1H, dcs.helicopters.UH_1H,
aircraftMods.UH_60L, aircraftMods.UH_60L,
dcs.planes.AV8BNA,
] ]
e_attack_helos = [ e_attack_helos = [
@ -39,6 +41,14 @@ e_attack_planes = [
dcs.planes.A_10C, dcs.planes.A_10C,
] ]
e_fighter_planes = [
dcs.planes.Su_27,
]
f_fighter_planes = [
dcs.planes.FA_18C_hornet,
]
e_zone_sams = [ e_zone_sams = [
dcs.vehicles.AirDefence.Strela_10M3, dcs.vehicles.AirDefence.Strela_10M3,
] ]

View File

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

13
Generator/build.bat Normal file
View File

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

Binary file not shown.

View File

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

8
Generator/version.py Normal file
View File

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

BIN
MissionGenerator.exe Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

145
release_script.py Normal file
View File

@ -0,0 +1,145 @@
import datetime
import ftplib
import os
import sys
import json
import yaml
from Generator import version
if __name__ == "__main__":
# change working directory to the directory of this
os.chdir(os.path.dirname(os.path.abspath(__file__)))
#get unix epoch seconds string via datetime
timestamp = str(int(datetime.datetime.now().timestamp()))
updatescript_bkup = "updatescript.ini.bkup" + timestamp
versioncheck_bkup = "versioncheck.yaml.bkup" + timestamp
if not os.getenv("FTP_SERVER") or not os.getenv("FTP_USERNAME") or not os.getenv("FTP_PASSWORD"):
print("FTP_SERVER, FTP_USERNAME, and FTP_PASSWORD environment variables must be set.")
sys.exit(1)
# connect to the update server
ftp = ftplib.FTP(os.getenv("FTP_SERVER"))
# login to the update server
try:
ftp.login(os.getenv("FTP_USERNAME"), os.getenv("FTP_PASSWORD"))
except ftplib.error_perm:
print("Login failed. Check your username and password.")
sys.exit(1)
except ftplib.error_temp:
print("Login failed due to temp error. Check your connection settings.")
sys.exit(1)
# list files in the current directory on the update server
print("Files in the current remote directory: ")
files = ftp.nlst()
print(files)
#download the updatescript.ini file
with open("updatescript.ini", "wb") as f:
ftp.retrbinary("RETR updatescript.ini", f.write)
with open(updatescript_bkup, "wb") as f:
ftp.retrbinary("RETR updatescript.ini", f.write)
#download the versioncheck.yaml file
with open("versioncheck.yaml", "wb") as f:
ftp.retrbinary("RETR versioncheck.yaml", f.write)
with open(versioncheck_bkup, "wb") as f:
ftp.retrbinary("RETR versioncheck.yaml", f.write)
# load yaml file and check previous version
with open('versioncheck.yaml') as f:
data = yaml.load(f, Loader=yaml.FullLoader)
# get version from yaml file
version_from_file = data['version']
if version_from_file == version.version_string:
# throw error
print("Version is the same as the current version. Increment the version number in version.py and try again.")
sys.exit(1)
changed_files = json.loads(os.getenv("changed_files"))
if changed_files:
print("Changed files: " + str(changed_files))
# open updatescript.ini for read/write
with open("updatescript.ini", "r") as f:
file_text = f.read()
# get the contents of the first block starting with 'releases{' and ending with '}'
releases_str = file_text[file_text.find('releases{') + 9:file_text.find('}')].strip()
# split the releases into a list of releases
# remove spaces
releases_str = releases_str.replace(" ", "")
releases = releases_str.split('\n')
for r in releases:
if r == version.version_string:
print("Version already exists in updatescript.ini")
sys.exit(1)
with open("updatescript.ini", "w") as f:
#add the newest release to the bottom of the list
releases.append(version.version_string)
# remove text before first '}'
file_text = file_text[file_text.find('}') + 1:]
# write the releases block back to the file
f.write('releases{\n ' + '\n '.join(releases) + '\n}\n')
#write the old releases back to the file
f.write(file_text)
# write the new release to the file
f.write("\nrelease:" + version.version_string + "{")
for file in changed_files:
remote_path = 'continuous/' + file
subdir = None
#if file has a subdir, get the subdir
if file.rfind("/") != -1:
subdir = file[:file.rfind("/")]
file = file[file.rfind("/") + 1:]
f.write("\n DownloadFile:" + remote_path)
f.write("," + subdir + "/")
else:
f.write("\n DownloadFile:" + remote_path)
f.write("\n}")
f.close()
# create new versioncheck.yaml file
with open('versioncheck.yaml', 'w') as f:
f.write("title: \"Update Available\"\n")
f.write("description: \"UPDATE AVAILABLE: Please run the included updater utility (RotorOps_updater.exe) to get the latest version.\"" + "\n")
f.write("version: \"" + version.version_string + "\"\n")
#upload the new files to the update server
files = [updatescript_bkup, versioncheck_bkup, 'updatescript.ini', 'versioncheck.yaml']
for file in files:
ftp.storbinary("STOR " + file, open(file, "rb"))
print("Uploaded " + file)
ftp.quit()

View File

@ -1,5 +1,5 @@
RotorOps = {} RotorOps = {}
RotorOps.version = "1.3.0" RotorOps.version = "1.3.3"
local debug = true local debug = true
@ -40,7 +40,7 @@ RotorOps.pickup_zone_smoke = "blue"
RotorOps.ai_task_by_name = true --allow tasking all groups that include key strings in their group names eg 'Patrol' RotorOps.ai_task_by_name = true --allow tasking all groups that include key strings in their group names eg 'Patrol'
RotorOps.ai_task_by_name_scheduler = true --continually search active groups for key strings and ai tasking RotorOps.ai_task_by_name_scheduler = true --continually search active groups for key strings and ai tasking
RotorOps.patrol_task_string = 'patrol' --default string to search group names for the patrol task. requires ai_task_by_name RotorOps.patrol_task_string = 'patrol' --default string to search group names for the patrol task. requires ai_task_by_name
RotorOps.aggressive_task_string = 'aggressive' --default string to search group names for the patrol task. requires ai_task_by_name RotorOps.aggressive_task_string = 'aggressive' --default string to search group names for the aggressive task. requires ai_task_by_name
RotorOps.move_to_active_task_string = "activezone" --default string to search group names for the move to active zone task. requires ai_task_by_name RotorOps.move_to_active_task_string = "activezone" --default string to search group names for the move to active zone task. requires ai_task_by_name
RotorOps.shift_task_string = "shift" RotorOps.shift_task_string = "shift"
RotorOps.guard_task_string = "guard" RotorOps.guard_task_string = "guard"
@ -49,6 +49,10 @@ RotorOps.guard_task_string = "guard"
RotorOps.defending_vehicles_behavior = "shift" --available options: 'none', 'patrol', 'shift' RotorOps.defending_vehicles_behavior = "shift" --available options: 'none', 'patrol', 'shift'
RotorOps.farp_pickups = true --allow ctld troop pickup at FARPs RotorOps.farp_pickups = true --allow ctld troop pickup at FARPs
RotorOps.enable_staging_pickzones = true RotorOps.enable_staging_pickzones = true
RotorOps.persistent_tasking = false --prevent the script from restasking in a loop --might help with odd movement patterns between zones
--RotorOps settings that are safe to change only in the script config option in the scenario config file
RotorOps.draw_conflict_zones = true
---[[END OF OPTIONS]]--- ---[[END OF OPTIONS]]---
@ -71,6 +75,13 @@ RotorOps.ai_tasks = {}
RotorOps.defending = false RotorOps.defending = false
RotorOps.staged_units_flag = 111 -- shows a percentage of the units found in the staging zone when the game starts. you can also use 'ROPS_ATTACKERS' for readability RotorOps.staged_units_flag = 111 -- shows a percentage of the units found in the staging zone when the game starts. you can also use 'ROPS_ATTACKERS' for readability
--fighter variables
local fighters_by_detected_unitname = {}
RotorOps.fighter_radar_unit_string = 'FIGHTER_DEPLOYMENT' --any unit capable of detecting aircraft by radar can be used as a detection source to spawn intercept fighters, if this string is in the unit name
RotorOps.fighter_min_detection_alt = 609 --aircraft below this agl altitude (meters) will not be 'detected' by radar units.
RotorOps.fighter_max_detection_dist = 7000 --default max range from radar to target in order for intercept fighters to spawn (you can also set range for individual radar sources via unit name)
RotorOps.fighter_max_active = 2 --total maximum active deployed fighters, shared between red/blue
trigger.action.outText("ROTOR OPS STARTED: "..RotorOps.version, 5) trigger.action.outText("ROTOR OPS STARTED: "..RotorOps.version, 5)
env.info("ROTOR OPS STARTED: "..RotorOps.version) env.info("ROTOR OPS STARTED: "..RotorOps.version)
@ -88,6 +99,7 @@ local cooldown = {
["attack_helo_msg"] = 0, ["attack_helo_msg"] = 0,
["attack_plane_msg"] = 0, ["attack_plane_msg"] = 0,
["trans_helo_msg"] = 0, ["trans_helo_msg"] = 0,
["e_fighters_inbound_msg"] = 0,
} }
local zone_defenders_flags = { local zone_defenders_flags = {
'ROPS_A_DEFENDERS', 'ROPS_A_DEFENDERS',
@ -202,6 +214,9 @@ RotorOps.gameMsgs = {
transp_helos_toff = { transp_helos_toff = {
{'ENEMY TRANSPORT HELICOPTERS INBOUND!', 'enemy_chopper_inbound.ogg'}, {'ENEMY TRANSPORT HELICOPTERS INBOUND!', 'enemy_chopper_inbound.ogg'},
}, },
enemy_fighters_inbound = {
{'ENEMY FIGHTERS INBOUND!', 'enemy_fighters_inbound.ogg'},
},
} }
@ -289,16 +304,19 @@ ctld.addCallback(function(_args)
local unit = _args.unit local unit = _args.unit
local picked_troops = _args.onboard local picked_troops = _args.onboard
local dropped_troops = _args.unloaded local dropped_troops = _args.unloaded
--trigger.action.outText("dbg: ".. mist.utils.tableShow(_args), 5) --env.info("ctld callback: ".. mist.utils.tableShow(_args))
if action == "load_troops" or action == "extract_troops" then if action == "load_troops" or action == "extract_troops" then
trigger.action.outSoundForGroup(unit:getGroup():getID() , sound_effects.troop_pickup[math.random(1, #sound_effects.troop_pickup)]) trigger.action.outSoundForGroup(unit:getGroup():getID() , sound_effects.troop_pickup[math.random(1, #sound_effects.troop_pickup)])
elseif action == "unload_troops_zone" or action == "dropped_troops" then elseif action == "unload_troops_zone" or action == "dropped_troops" then
trigger.action.outSoundForGroup(unit:getGroup():getID() , sound_effects.troop_dropoff[math.random(1, #sound_effects.troop_dropoff)]) trigger.action.outSoundForGroup(unit:getGroup():getID() , sound_effects.troop_dropoff[math.random(1, #sound_effects.troop_dropoff)])
if RotorOps.isUnitInZone(unit, RotorOps.active_zone) then if RotorOps.isUnitInZone(unit, RotorOps.active_zone) then
local id = timer.scheduleFunction(RotorOps.gameMsgHandler, RotorOps.gameMsgs.friendly_troops_dropped, timer.getTime() + 6) --allow some extra time so we don't step on the player's troop/unload sound effects local id = timer.scheduleFunction(RotorOps.gameMsgHandler, RotorOps.gameMsgs.friendly_troops_dropped, timer.getTime() + 6) --allow some extra time so we don't step on the player's troop/unload sound effects
end end
if dropped_troops.jtac == true then if dropped_troops.jtac == true then
local id = timer.scheduleFunction(RotorOps.gameMsgHandler, RotorOps.gameMsgs.jtac, timer.getTime() + 6) --allow some extra time so we don't step on the player's troop/unload sound effects local id = timer.scheduleFunction(RotorOps.gameMsgHandler, RotorOps.gameMsgs.jtac, timer.getTime() + 12) --allow some extra time so we don't step on the player's troop/unload sound effects
end end
end end
@ -529,9 +547,9 @@ function RotorOps.deployTroops(quantity, target_group, announce)
debugMsg("DeployTroops on group: "..target_group_obj:getName()) debugMsg("DeployTroops on group: "..target_group_obj:getName())
local valid_unit = RotorOps.getValidUnitFromGroup(target_group_obj) local valid_unit = RotorOps.getValidUnitFromGroup(target_group_obj)
if not valid_unit then return end if not valid_unit then return end
local coalition = valid_unit:getCoalition() local coal = valid_unit:getCoalition()
local side = "red" local side = "red"
if coalition == 2 then side = "blue" end if coal == 2 then side = "blue" end
local point = valid_unit:getPoint() local point = valid_unit:getPoint()
ctld.spawnGroupAtPoint(side, quantity, point, 1000) ctld.spawnGroupAtPoint(side, quantity, point, 1000)
@ -831,11 +849,11 @@ function RotorOps.shiftPosition(vars)
local search_radius = vars.radius or 100 local search_radius = vars.radius or 100
local inner_radius = 50 --minimum distance to move for randpointincircle local inner_radius = 50 --minimum distance to move for randpointincircle
local first_valid_unit local first_valid_unit
if grp:isExist() ~= true then return end if grp and grp:isExist() ~= true then return end
local start_point = vars.point local start_point = vars.point
if not start_point then if not start_point then
env.info("RotorOps: No point provided, getting current position.") --env.info("RotorOps: No point provided, getting current position.")
for index, unit in pairs(grp:getUnits()) do for index, unit in pairs(grp:getUnits()) do
if unit:isExist() == true then if unit:isExist() == true then
first_valid_unit = unit first_valid_unit = unit
@ -865,7 +883,7 @@ function RotorOps.shiftPosition(vars)
if mist.isTerrainValid(rand_point, {'LAND', 'ROAD'}) == true then if mist.isTerrainValid(rand_point, {'LAND', 'ROAD'}) == true then
path[#path + 1] = mist.ground.buildWP(rand_point, formation, 5) path[#path + 1] = mist.ground.buildWP(rand_point, formation, 5)
env.info("point is valid, adding as waypoint with formation: " .. formation) --env.info("point is valid, adding as waypoint with formation: " .. formation)
break break
end end
@ -881,11 +899,11 @@ function RotorOps.guardPosition(vars)
local grp = vars.grp local grp = vars.grp
local search_radius = vars.radius or 100 local search_radius = vars.radius or 100
local first_valid_unit local first_valid_unit
if grp:isExist() ~= true then return end if not grp or grp:isExist() ~= true then return end
local start_point = vars.point local start_point = vars.point
if not start_point then if not start_point then
env.info("RotorOps: No point provided, getting current position.") --env.info("RotorOps: No point provided, getting current position.")
for index, unit in pairs(grp:getUnits()) do for index, unit in pairs(grp:getUnits()) do
if unit:isExist() == true then if unit:isExist() == true then
first_valid_unit = unit first_valid_unit = unit
@ -897,13 +915,12 @@ function RotorOps.guardPosition(vars)
start_point = first_valid_unit:getPoint() start_point = first_valid_unit:getPoint()
end end
local object_vol_thresh = 0 local object_vol_thresh = 0
local max_waypoints = 1
local foundUnits = {} local foundUnits = {}
local volS = { local volS = {
id = world.VolumeType.SPHERE, id = world.VolumeType.SPHERE,
params = { params = {
point = grp:getUnit(1):getPoint(), --check if exists, maybe itterate through grp point = start_point,
radius = search_radius radius = search_radius
} }
} }
@ -927,9 +944,10 @@ function RotorOps.guardPosition(vars)
--world.searchObjects(Object.Category.BASE, volS, ifFound) --world.searchObjects(Object.Category.BASE, volS, ifFound)
if #foundUnits > 0 then if #foundUnits > 0 then
local path = {} local path = {}
path[1] = mist.ground.buildWP(start_point, '', 5) --path[1] = mist.ground.buildWP(RotorOps.getValidUnitFromGroup(grp):getPoint(), '', 2)
path[1] = mist.ground.buildWP(RotorOps.getValidUnitFromGroup(grp):getPoint(), '', 2)
local rand_index = math.random(1,#foundUnits) local rand_index = math.random(1,#foundUnits)
path[#path + 1] = mist.ground.buildWP(foundUnits[rand_index]:getPoint(), '', 3) path[#path + 1] = mist.ground.buildWP(foundUnits[rand_index]:getPoint(), '', 2)
mist.goRoute(grp, path) mist.goRoute(grp, path)
end end
end end
@ -963,6 +981,9 @@ function RotorOps.aiExecute(vars)
local last_task = vars.last_task local last_task = vars.last_task
local last_zone = vars.last_zone local last_zone = vars.last_zone
local group_name = vars.group_name local group_name = vars.group_name
if not vars.group_name or not tableHasKey(RotorOps.ai_tasks, group_name) then
return
end
local task = RotorOps.ai_tasks[group_name].ai_task local task = RotorOps.ai_tasks[group_name].ai_task
local zone = RotorOps.ai_tasks[group_name].zone local zone = RotorOps.ai_tasks[group_name].zone
local point = RotorOps.ai_tasks[group_name].point local point = RotorOps.ai_tasks[group_name].point
@ -999,17 +1020,13 @@ function RotorOps.aiExecute(vars)
local should_update = true local should_update = true
-- if task == last_task then if RotorOps.persistent_tasking and task == last_task then
-- should_update = false if task == "move_to_active_zone" or task == "move_to_zone" then
-- end if same_zone then
-- should_update = false
-- if same_zone then end
-- should_update = false end
-- end end
--
-- if task == "patrol" then
-- should_update = true
-- end
if should_update then --check to make sure we don't have the same task if should_update then --check to make sure we don't have the same task
@ -1225,7 +1242,6 @@ function RotorOps.assessUnitsInZone(var)
percent_staged_remain = math.floor((#staged_units_remaining / #RotorOps.staged_units) * 100) percent_staged_remain = math.floor((#staged_units_remaining / #RotorOps.staged_units) * 100)
trigger.action.setUserFlag(RotorOps.staged_units_flag, percent_staged_remain) trigger.action.setUserFlag(RotorOps.staged_units_flag, percent_staged_remain)
trigger.action.setUserFlag('ROPS_ATTACKERS', percent_staged_remain) trigger.action.setUserFlag('ROPS_ATTACKERS', percent_staged_remain)
debugMsg("Staged units remaining percent: "..percent_staged_remain.."%")
--is the game finished? --is the game finished?
@ -1316,7 +1332,7 @@ function RotorOps.assessUnitsInZone(var)
end end
RotorOps.inf_spawns_avail = RotorOps.inf_spawns_avail - 1 RotorOps.inf_spawns_avail = RotorOps.inf_spawns_avail - 1
env.info("ROTOR OPS: Spawned infantry. "..RotorOps.inf_spawns_avail.." spawns remaining in "..zone) env.info("ROTOR OPS: Attempting to spawn infantry. "..RotorOps.inf_spawns_avail.." spawns remaining in "..zone)
end end
end end
@ -1377,7 +1393,7 @@ function RotorOps.drawZones() --this could use a lot of work, we should use tri
do do
local point = trigger.misc.getZone(zone.name).point local point = trigger.misc.getZone(zone.name).point
local radius = trigger.misc.getZone(zone.name).radius local radius = trigger.misc.getZone(zone.name).radius
local coalition = -1 local coal = -1
local id = index --this must be UNIQUE! local id = index --this must be UNIQUE!
local color = {1, 1, 1, 0.5} local color = {1, 1, 1, 0.5}
local fill_color = {1, 1, 1, 0.1} local fill_color = {1, 1, 1, 0.1}
@ -1392,11 +1408,14 @@ function RotorOps.drawZones() --this could use a lot of work, we should use tri
fill_color = {1, 0, 0, 0.05} fill_color = {1, 0, 0, 0.05}
end end
if previous_point ~= nill then if previous_point ~= nill then
--trigger.action.lineToAll(coalition, id + 200, point, previous_point, color, line_type) --trigger.action.lineToAll(coal, id + 200, point, previous_point, color, line_type)
end end
previous_point = point previous_point = point
trigger.action.circleToAll(coalition, id, point, radius, color, fill_color, line_type)
trigger.action.textToAll(coalition, id + 100, point, color, text_fill_color, font_size, read_only, text) if RotorOps.draw_conflict_zones == true then
trigger.action.circleToAll(coal, id, point, radius, color, fill_color, line_type)
trigger.action.textToAll(coal, id + 100, point, color, text_fill_color, font_size, read_only, text)
end
end end
for index, cpz in pairs(ctld.pickupZones) do for index, cpz in pairs(ctld.pickupZones) do
@ -1407,14 +1426,14 @@ function RotorOps.drawZones() --this could use a lot of work, we should use tri
local ctld_zone_status = cpz[4] local ctld_zone_status = cpz[4]
local point = pickup_zone.point local point = pickup_zone.point
local radius = pickup_zone.radius local radius = pickup_zone.radius
local coalition = -1 local coal = -1
local id = index + 150 --this must be UNIQUE! local id = index + 150 --this must be UNIQUE!
local color = {1, 1, 1, 0.5} local color = {1, 1, 1, 0.5}
local fill_color = {0, 0.8, 0, 0.1} local fill_color = {0, 0.8, 0, 0.1}
local line_type = 5 --1 Solid 2 Dashed 3 Dotted 4 Dot Dash 5 Long Dash 6 Two Dash local line_type = 5 --1 Solid 2 Dashed 3 Dotted 4 Dot Dash 5 Long Dash 6 Two Dash
if ctld_zone_status == 'yes' or ctld_zone_status == 1 then if ctld_zone_status == 'yes' or ctld_zone_status == 1 then
env.info("pickup zone is active, drawing it to the map") env.info("pickup zone is active, drawing it to the map")
trigger.action.circleToAll(coalition, id, point, radius, color, fill_color, line_type) trigger.action.circleToAll(coal, id, point, radius, color, fill_color, line_type)
end end
end end
end end
@ -1429,14 +1448,14 @@ function RotorOps.drawZones() --this could use a lot of work, we should use tri
-- local ctld_zone_status = c_zone[4] -- local ctld_zone_status = c_zone[4]
-- local point = trigger.misc.getZone(pickup_zone).point -- local point = trigger.misc.getZone(pickup_zone).point
-- local radius = trigger.misc.getZone(pickup_zone).radius -- local radius = trigger.misc.getZone(pickup_zone).radius
-- local coalition = -1 -- local coal = -1
-- local id = index + 150 --this must be UNIQUE! -- local id = index + 150 --this must be UNIQUE!
-- local color = {1, 1, 1, 0.5} -- local color = {1, 1, 1, 0.5}
-- local fill_color = {0, 0.8, 0, 0.1} -- local fill_color = {0, 0.8, 0, 0.1}
-- local line_type = 5 --1 Solid 2 Dashed 3 Dotted 4 Dot Dash 5 Long Dash 6 Two Dash -- local line_type = 5 --1 Solid 2 Dashed 3 Dotted 4 Dot Dash 5 Long Dash 6 Two Dash
-- if ctld_zone_status == 'yes' or ctld_zone_status == 1 then -- if ctld_zone_status == 'yes' or ctld_zone_status == 1 then
-- --debugMsg("draw the pickup zone") -- --debugMsg("draw the pickup zone")
-- trigger.action.circleToAll(coalition, id, point, radius, color, fill_color, line_type) -- trigger.action.circleToAll(coal, id, point, radius, color, fill_color, line_type)
-- end -- end
-- end -- end
-- end -- end
@ -1548,8 +1567,8 @@ function RotorOps.setupCTLD()
ctld.pickupZones[#ctld.pickupZones + 1] = { "BRAVO_FARP", RotorOps.pickup_zone_smoke, -1, "no", 0 } ctld.pickupZones[#ctld.pickupZones + 1] = { "BRAVO_FARP", RotorOps.pickup_zone_smoke, -1, "no", 0 }
ctld.pickupZones[#ctld.pickupZones + 1] = { "CHARLIE_FARP", RotorOps.pickup_zone_smoke, -1, "no", 0 } ctld.pickupZones[#ctld.pickupZones + 1] = { "CHARLIE_FARP", RotorOps.pickup_zone_smoke, -1, "no", 0 }
ctld.pickupZones[#ctld.pickupZones + 1] = { "DELTA_FARP", RotorOps.pickup_zone_smoke, -1, "no", 0 } ctld.pickupZones[#ctld.pickupZones + 1] = { "DELTA_FARP", RotorOps.pickup_zone_smoke, -1, "no", 0 }
ctld.pickupZones[#ctld.pickupZones + 1] = { "HELO_CARRIER", "none", -1, "no", 0 } ctld.pickupZones[#ctld.pickupZones + 1] = { "HELO_CARRIER", "none", -1, "yes", 0 }
ctld.pickupZones[#ctld.pickupZones + 1] = { "HELO_CARRIER_1", "none", -1, "no", 0 } ctld.pickupZones[#ctld.pickupZones + 1] = { "HELO_CARRIER_1", "none", -1, "yes", 0 }
ctld.pickupZones[#ctld.pickupZones + 1] = { "troops1", RotorOps.pickup_zone_smoke, -1, "yes", 0 } ctld.pickupZones[#ctld.pickupZones + 1] = { "troops1", RotorOps.pickup_zone_smoke, -1, "yes", 0 }
ctld.pickupZones[#ctld.pickupZones + 1] = { "troops2", RotorOps.pickup_zone_smoke, -1, "yes", 0 } ctld.pickupZones[#ctld.pickupZones + 1] = { "troops2", RotorOps.pickup_zone_smoke, -1, "yes", 0 }
ctld.pickupZones[#ctld.pickupZones + 1] = { "troops3", RotorOps.pickup_zone_smoke, -1, "yes", 0 } ctld.pickupZones[#ctld.pickupZones + 1] = { "troops3", RotorOps.pickup_zone_smoke, -1, "yes", 0 }
@ -1742,7 +1761,7 @@ function RotorOps.taskByName()
end end
end end
if RotorOps.ai_task_by_name_scheduler then if RotorOps.ai_task_by_name_scheduler then
local timer_id = timer.scheduleFunction(RotorOps.taskByName, nil, timer.getTime() + 120) local timer_id = timer.scheduleFunction(RotorOps.taskByName, nil, timer.getTime() + 500)
end end
end end
@ -1874,7 +1893,7 @@ function RotorOps.spawnTranspHelos(troops, max_drops)
gp.route.points[#gp.route.points + 1] = mist.heli.buildWP(initial_point, 'flyover', 100, 400, 'agl') gp.route.points[#gp.route.points + 1] = mist.heli.buildWP(initial_point, 'flyover', 100, 400, 'agl')
gp.clone = true gp.clone = true
local new_group_data = mist.dynAdd(gp) --returns a mist group data table local new_group_data = mist.dynAdd(gp) --returns a mist group data table
debugTable(new_group_data) --debugTable(new_group_data)
-- local new_group = Group.getByName(new_group_data.groupName) -- local new_group = Group.getByName(new_group_data.groupName)
-- local grp_controller = new_group:getController() --controller for aircraft can be group or unit level -- local grp_controller = new_group:getController() --controller for aircraft can be group or unit level
-- grp_controller:setOption(AI.Option.Air.id.REACTION_ON_THREAT , AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE) -- grp_controller:setOption(AI.Option.Air.id.REACTION_ON_THREAT , AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE)
@ -1885,6 +1904,273 @@ function RotorOps.spawnTranspHelos(troops, max_drops)
end end
function RotorOps.spawnCapToZone(_target_zone, _spawn_zone, coal)
local target_zone = _target_zone
if not target_zone then
target_zone = RotorOps.getEnemyZones()[math.random(1, #RotorOps.getEnemyZones())]
end
local zone_point = trigger.misc.getZone(target_zone).point
RotorOps.spawnCap(zone_point, _spawn_zone, coal)
end
RotorOps.fighter_red_source_string = "RED CAP"
RotorOps.fighter_blue_source_string = "BLUE CAP"
RotorOps.fighter_engagement_dist = 20
function RotorOps.spawnCap(destination_point, _spawn_zone, coal)
local red_zone_string = "RED_CAP_SPAWN"
local blue_zone_string = "BLUE_CAP_SPAWN"
local coal_zone_string = nil
if not coal or coal == 0 then
return
end
if coal == 1 then
coal_zone_string = red_zone_string
source_group_string = RotorOps.fighter_red_source_string
end
if coal == 2 then
coal_zone_string = blue_zone_string
source_group_string = RotorOps.fighter_blue_source_string
end
local spawn_zone = _spawn_zone
if not _spawn_zone then
local spawn_zones = {}
for zone, zoneobj in pairs(mist.DBs.zonesByName) do
if string.find(zone, coal_zone_string) then
spawn_zones[#spawn_zones + 1] = zone
--env.info("found cap spawn zone: " .. zone)
end
end
if #spawn_zones < 1 then
return
end
spawn_zone = spawn_zones[math.random(1, #spawn_zones)]
end
local spawn_point = mist.getRandomPointInZone(spawn_zone)
local altitude = math.random(2000,6000)
local speed = 300
--pick a template group at random for the source
fighter_groups = {} --stores group names of template groups
for uName, uData in pairs(mist.DBs.groupsByName) do
if string.find(uName, source_group_string) then
fighter_groups[#fighter_groups + 1] = uName
end
end
if #fighter_groups < 1 then
return
end
fighter_group_name = fighter_groups[math.random(1, #fighter_groups)]
local group = Group.getByName(fighter_group_name)
if not group then
return
end
local gp = mist.getGroupData(fighter_group_name)
--debugTable(gp)
gp.units[1].alt = altitude
gp.units[1].speed = speed
gp.units[1].x = spawn_point.x
gp.units[1].y = spawn_point.y
gp.units[1].heading = mist.utils.getHeadingPoints(spawn_point, destination_point)
local engage = {
id = 'EngageTargets',
params = {
maxDist = RotorOps.fighter_engagement_dist,
maxDistEnabled = true,
targetTypes = { [1] = "Air" },
}
}
local orbit = {
id = 'Orbit',
params = {
pattern = 'Race-Track',
}
}
gp.route = {points = {}}
-- gp.route[1] = mist.fixedWing.buildWP(random_airbase:getPoint())
-- gp.route[1].type = "TakeOffParking"
-- gp.route[1].action = "From Parking Area"
-- gp.route[1].airdromeId = airbase_id
gp.route.points[1] = mist.fixedWing.buildWP(spawn_point, 'turning point', speed, altitude, 'baro')
gp.route.points[1].task = {}
gp.route.points[1].task.id = 'ComboTask'
gp.route.points[1].task.params = {}
gp.route.points[1].task.params.tasks = {}
gp.route.points[1].task.params.tasks[1] = {number = 1, id = 'ControlledTask', enabled = true, params = {task = engage}}
gp.route.points[1].task.params.tasks[2] = {number = 2, id = 'ControlledTask', enabled = true, params = {task = orbit}}
gp.route.points[2] = mist.fixedWing.buildWP(destination_point, 'turning point', speed, altitude, 'baro')
gp.clone = true
local new_group_data = mist.dynAdd(gp) --returns a mist group data table
--debugTable(new_group_data)
local new_group = Group.getByName(new_group_data.name)
if new_group then
env.info("RotorOps spawned CAP: "..new_group_data.name)
else
env.error("RotorOps tried to spawn CAP but something went wrong.")
return
end
local grp_controller = new_group:getController() --controller for aircraft can be group or unit level
grp_controller:setOption(AI.Option.Air.id.REACTION_ON_THREAT , AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE)
grp_controller:setOption(AI.Option.Air.id.FLARE_USING , AI.Option.Air.val.FLARE_USING.WHEN_FLYING_NEAR_ENEMIES)
grp_controller:setOption(AI.Option.Air.id.ROE , AI.Option.Air.val.ROE.OPEN_FIRE_WEAPON_FREE)
grp_controller:setOption(AI.Option.Air.id.RADAR_USING, AI.Option.Air.val.RADAR_USING.FOR_SEARCH_IF_REQUIRED)
return new_group_data.name
end
function RotorOps.deployFighters()
local function spawn(dest_point, target_unit, coal)
fighter = RotorOps.spawnCap(dest_point, nil, coal)
if fighter and #fighters_by_detected_unitname < RotorOps.fighter_max_active then
_spawn_time = RotorOps.getTime()
fighters_by_detected_unitname[target_unit] = {
name = fighter,
spawn_time = _spawn_time,
rtb_time = math.random(_spawn_time + (15 * 60), _spawn_time + (25 * 60)),
respawn_time = math.random(_spawn_time + (5 * 60), _spawn_time + (15 * 60)),
}
if ((RotorOps.getTime() - cooldown["e_fighters_inbound_msg"]) > 90) then
RotorOps.gameMsg(RotorOps.gameMsgs.enemy_fighters_inbound)
cooldown["e_fighters_inbound_msg"] = RotorOps.getTime()
end
--debugTable(fighters_by_detected_unitname)
env.info(target_unit .. " was detected and we spawned a new fighter group: " .. fighter)
end
end
local function rtb(group_name)
local grp = Group.getByName(group_name)
if grp then
local coal_airbases = coalition.getAirbases(grp:getCoalition())
--debugTable(coal_airbases)
random_airbase = coal_airbases[math.random(1, #coal_airbases)]
local airbase_pos = mist.utils.makeVec2(random_airbase:getPoint())
local airbase_id = random_airbase:getID()
local rtb = {
id = 'Mission',
params = {
route = {
points = {
[1] = {
alt = 2000,
alt_type = "RADIO",
speed = 300,
x = airbase_pos.x,
y = airbase_pos.y,
aerodromeId = airbase_id,
type = "Land",
action = "Landing",
}
}
}
}
}
grp:getController():setTask(rtb)
env.info(group_name .. " is RTB to ".. random_airbase:getName())
end
end
--fighter respawning and rtb
for target_name, fighter_group_data in pairs(fighters_by_detected_unitname) do
local group = Group.getByName(fighter_group_data.name)
if group then --if group alive
if fighter_group_data.rtb_time < RotorOps.getTime() then
env.info(fighter_group_data.name .. " is RTB. Removing from table.")
rtb(fighter_group_data.name)
fighters_by_detected_unitname[target_name] = nil
end
else --if group dead
if fighter_group_data.respawn_time < RotorOps.getTime() then
env.info(fighter_group_data.name .. " has hit respawn_time limit. Removing from table to allow another group to spawn.")
fighters_by_detected_unitname[target_name] = nil
end
end
end
for uName, uData in pairs(mist.DBs.unitsByName) do
local str_index = string.find(uName, RotorOps.fighter_radar_unit_string)
if str_index then
--trigger.action.outText("Found radar unit: " .. uData.unitName, 2)
local radar_unit = Unit.getByName(uData.unitName)
local max_distance = RotorOps.fighter_max_detection_dist
local dist_str = string.sub(uName, str_index + #RotorOps.fighter_radar_unit_string + 1)
if #dist_str > 3 then
--env.info("RotorOps: Radar unit name has the max detection distance property:".. dist_str)
local dist = tonumber(dist_str)
if dist and dist > 0 then
max_distance = dist
end
end
if radar_unit and radar_unit:getLife() > 0 then
--trigger.action.outText(uData.unitName .. " is searching for targets. life=" .. radar_unit:getLife(), 2)
raw_detected_units = radar_unit:getController():getDetectedTargets(Controller.Detection.RADAR)
if raw_detected_units then
for i, target in pairs(raw_detected_units) do
--debugTable(target)
if target.object and target.object:getCategory() == Object.Category.UNIT then
local detected_unitname = target.object:getName()
local target_pos = target.object:getPosition().p
local target_distance = mist.utils.get2DDist(radar_unit:getPosition().p, target_pos)
local terrain_height = land.getHeight({x = target_pos.x, y = target_pos.z})
local target_agl = target_pos.y - terrain_height
env.info(uData.unitName .. "detected " .. detected_unitname .. " at " .. target_distance .. " agl:" .. target_agl)
if target_distance <= max_distance and target_agl >= RotorOps.fighter_min_detection_alt then
env.info('RotorOps: ' .. uData.unitName .. " has detected "..detected_unitname .. "at agl=" .. target_agl .. " distance=" .. target_distance)
if tableHasKey(fighters_by_detected_unitname, detected_unitname) then
--trigger.action.outText(detected_unitname .. " already in table with " .. fighters_by_detected_unitname[detected_unitname], 2)
else
spawn(target_pos, detected_unitname, radar_unit:getCoalition())
end
end
end --end if target.object
end --end of raw_detected targets loop
end
end --end of radar_unit
end
end --end of all units by name loop
end
--- USEFUL PUBLIC 'LUA PREDICATE' FUNCTIONS FOR MISSION EDITOR TRIGGERS (don't forget that DCS lua predicate functions should 'return' these function calls) --- USEFUL PUBLIC 'LUA PREDICATE' FUNCTIONS FOR MISSION EDITOR TRIGGERS (don't forget that DCS lua predicate functions should 'return' these function calls)
@ -1928,3 +2214,8 @@ function RotorOps.predPlayerInZone(zone_name)
end end
end end
--determine if enemy CAP is needed
function RotorOps.predSpawnRedCap()
return true
end

View File

@ -0,0 +1,68 @@
RotorOpsServer = {}
RotorOpsServer.version = "0.3"
trigger.action.outText("ROTOROPS SERVER SCRIPT: "..RotorOpsServer.version, 5)
env.info("ROTOROPS SERVER SCRIPT STARTED: "..RotorOpsServer.version)
--For SpecialK's DCSServerBot
RotorOpsServer.dcsbot = {}
RotorOpsServer.dcsbot.enabled = true
RotorOpsServer.dcsbot.points = {}
RotorOpsServer.dcsbot.points.troop_drop = 6
RotorOpsServer.dcsbot.points.unpack = 5
RotorOpsServer.dcsbot.points.rearm_repair = 3
--Mission Ending
RotorOpsServer.time_to_end = 600
function RotorOpsServer.endMission(secs)
if secs then
RotorOpsServer.time_to_end = secs
end
local function countdown()
local minutes = math.floor(RotorOpsServer.time_to_end / 60)
local seconds = RotorOpsServer.time_to_end - (minutes * 60) --handle as string
if seconds < 10 then
seconds = "0" .. seconds
end
trigger.action.outText("RTB now. Mission will end in "..minutes..":"..seconds, 2, true)
RotorOpsServer.time_to_end = RotorOpsServer.time_to_end - 1
if RotorOpsServer.time_to_end <= 0 then
trigger.action.setUserFlag('mission_end', 2)
else
timer.scheduleFunction(countdown, {}, timer.getTime() + 1)
end
end
countdown()
end
function RotorOpsServer.registerCtldCallbacks()
ctld.addCallback(function(_args)
local action = _args.action
local unit = _args.unit
local picked_troops = _args.onboard
local dropped_troops = _args.unloaded
--env.info("ctld callback: ".. mist.utils.tableShow(_args))
local playername = unit:getPlayerName()
if RotorOpsServer.dcsbot.enabled and dcsbot and playername then
if action == "unload_troops_zone" or action == "dropped_troops" then
if RotorOps.isUnitInZone(unit, RotorOps.active_zone) then
env.info('RotorOpsServer: adding points (unload troops in active zone) for ' ..playername)
net.send_chat(playername .. " dropped troops into the active zone. [" .. RotorOpsServer.dcsbot.points.troop_drop .. " points]")
dcsbot.addUserPoints(playername, RotorOpsServer.dcsbot.points.troop_drop)
end
elseif action == "rearm" or action == "repair" then
env.info('RotorOpsServer: adding points (rearm/repair) for ' ..playername)
net.send_chat(playername .. " repaired/rearmed our defenses. [" .. RotorOpsServer.dcsbot.points.rearm_repair .. " points]")
dcsbot.addUserPoints(playername, RotorOpsServer.dcsbot.points.rearm_repair)
elseif action == "unpack" then
env.info('RotorOpsServer: adding points (unpack) for ' ..playername)
net.send_chat(playername .. " unpacked ground units. [" .. RotorOpsServer.dcsbot.points.unpack .. " points]")
dcsbot.addUserPoints(playername, RotorOpsServer.dcsbot.points.unpack)
end
end
end)
end
RotorOpsServer.registerCtldCallbacks()

View File

@ -1,66 +0,0 @@
# A script for creating the modules map file
import os
import yaml
print("Current dir: " + os.getcwd())
modules = []
module_folders = next(os.walk('.'))[1]
for folder in module_folders:
valid_module = False
module_filenames = []
module = {}
print("searching folder: " + folder)
for filename in os.listdir(folder):
module_filenames.append(filename)
# assume the yaml file is our scenario configuration file
if filename.endswith(".yaml"):
#print("found config file: " + filename)
stream = file(os.path.join(folder, filename), 'r')
config = yaml.load(stream)
#print("Config file yaml: " + str(config))
if 'name' in config:
print("Config file has name: " + config['name'])
valid_module = True
module['name'] = config['name']
if valid_module:
print("Populating module attributes for " + folder)
module['id'] = folder
module['dist'] = 'add'
module['path'] = 'templates\Scenarios\downloaded'
module['files'] = module_filenames
if 'version' in config:
module['version'] = config['version']
else:
module['version'] = 1
if 'requires' in config:
module['requires'] = config['requires']
else:
module['requires'] = 1
modules.append(module)
print("Found modules: " + str(len(modules)))
if len(modules) > 0:
modulemap = {}
#print(str(modules))
for m in modules:
print("adding module: " + m["id"])
modulemap[m['id']] = m
with open('module-map.yaml', 'w') as mapfile:
print("Creating map file...")
yaml.dump(modulemap, mapfile)
print("Success.")

Binary file not shown.

File diff suppressed because one or more lines are too long