Babysteps towards full OPFOR control

This commit is contained in:
Raffson 2024-05-11 23:04:36 +02:00
parent b61f625828
commit f203a5cf7a
No known key found for this signature in database
GPG Key ID: B0402B2C9B764D99
11 changed files with 73 additions and 37 deletions

View File

@ -13,13 +13,13 @@
* **[Options]** Extend option (so it can be disabled when fixed in DCS) to force air-starts (except for the slots that work) at Ramon Airbase, similar to the Nevatim fix in Retribution 1.3.0 * **[Options]** Extend option (so it can be disabled when fixed in DCS) to force air-starts (except for the slots that work) at Ramon Airbase, similar to the Nevatim fix in Retribution 1.3.0
* **[Options]** New option in Settings: Default start type for Player flights. * **[Options]** New option in Settings: Default start type for Player flights.
* **[AirWing]** Expose OPFOR Squadrons, giving the ability to change liveries, auto-assignable mission types & an easy way to retrieve debug information. * **[AirWing]** Expose OPFOR Squadrons, giving the ability to change liveries, auto-assignable mission types & an easy way to retrieve debug information.
* **[ATO]** Allow planning as OPFOR
*
## Fixes ## Fixes
* **[UI/UX]** A-10A flights can be edited again. * **[UI/UX]** A-10A flights can be edited again.
* **[Mission Generation]** IADS bug sometimes triggering "no skynet usable units" error during mission generation * **[Mission Generation]** IADS bug sometimes triggering "no skynet usable units" error during mission generation
* **[New Game Wizard]** Campaign errors show a dialog again and avoid CTDs * **[New Game Wizard]** Campaign errors show a dialog again and avoid CTDs
# Retribution v1.3.1 # Retribution v1.3.1
#### Note: Re-save your missions in DCS' Mission Editor to avoid possible crashes due to datalink (usually the case when F-16C blk50s are used) when hosting missions on a dedicated server. #### Note: Re-save your missions in DCS' Mission Editor to avoid possible crashes due to datalink (usually the case when F-16C blk50s are used) when hosting missions on a dedicated server.

View File

@ -1036,7 +1036,6 @@ class Settings:
# Cheating. Not using auto settings because the same page also has buttons which do # Cheating. Not using auto settings because the same page also has buttons which do
# not alter settings. # not alter settings.
show_red_ato: bool = False
enable_frontline_cheats: bool = False enable_frontline_cheats: bool = False
enable_base_capture_cheat: bool = False enable_base_capture_cheat: bool = False
enable_transfer_cheat: bool = False enable_transfer_cheat: bool = False

View File

@ -37,7 +37,7 @@ class Dialog:
def open_new_package_dialog(cls, mission_target: MissionTarget, parent=None): def open_new_package_dialog(cls, mission_target: MissionTarget, parent=None):
"""Opens the dialog to create a new package with the given target.""" """Opens the dialog to create a new package with the given target."""
cls.new_package_dialog = QNewPackageDialog( cls.new_package_dialog = QNewPackageDialog(
cls.game_model, cls.game_model.ato_model, mission_target, parent=parent cls.game_model, mission_target, parent=parent
) )
cls.new_package_dialog.show() cls.new_package_dialog.show()

View File

@ -547,6 +547,7 @@ class GameModel:
else: else:
self.ato_model = AtoModel(self, self.game.blue.ato) self.ato_model = AtoModel(self, self.game.blue.ato)
self.red_ato_model = AtoModel(self, self.game.red.ato) self.red_ato_model = AtoModel(self, self.game.red.ato)
self.is_ownfor = False
# For UI purposes # For UI purposes
self.allocated_freqs: list[RadioFrequency] = list() self.allocated_freqs: list[RadioFrequency] = list()

View File

@ -23,12 +23,14 @@ from PySide6.QtWidgets import (
QSplitter, QSplitter,
QVBoxLayout, QVBoxLayout,
QMessageBox, QMessageBox,
QCheckBox,
) )
from game.ato.flight import Flight from game.ato.flight import Flight
from game.ato.package import Package from game.ato.package import Package
from game.server import EventStream from game.server import EventStream
from game.sim import GameUpdateEvents from game.sim import GameUpdateEvents
from .QLabeledWidget import QLabeledWidget
from ..delegates import TwoColumnRowDelegate from ..delegates import TwoColumnRowDelegate
from ..models import AtoModel, GameModel, NullListModel, PackageModel from ..models import AtoModel, GameModel, NullListModel, PackageModel
@ -484,8 +486,20 @@ class QAirTaskingOrderPanel(QSplitter):
def __init__(self, game_model: GameModel) -> None: def __init__(self, game_model: GameModel) -> None:
super().__init__(Qt.Orientation.Vertical) super().__init__(Qt.Orientation.Vertical)
self.game_model = game_model
self.ato_model = game_model.ato_model self.ato_model = game_model.ato_model
# ATO
self.red_ato_checkbox = QCheckBox()
self.red_ato_checkbox.toggled.connect(self.on_ato_changed)
self.red_ato_labeled = QLabeledWidget(
"Show/Plan OPFOR's ATO: ", self.red_ato_checkbox
)
self.ato_group_box = QGroupBox("ATO")
self.ato_group_box.setLayout(self.red_ato_labeled)
self.addWidget(self.ato_group_box)
self.package_panel = QPackagePanel(game_model, self.ato_model) self.package_panel = QPackagePanel(game_model, self.ato_model)
self.package_panel.current_changed.connect(self.on_package_change) self.package_panel.current_changed.connect(self.on_package_change)
self.addWidget(self.package_panel) self.addWidget(self.package_panel)
@ -500,3 +514,17 @@ class QAirTaskingOrderPanel(QSplitter):
self.flight_panel.set_package(self.ato_model.get_package_model(index)) self.flight_panel.set_package(self.ato_model.get_package_model(index))
else: else:
self.flight_panel.set_package(None) self.flight_panel.set_package(None)
def on_ato_changed(self) -> None:
opfor = self.red_ato_checkbox.isChecked()
ato_model = (
self.game_model.red_ato_model if opfor else self.game_model.ato_model
)
ato_model.layoutChanged.connect(self.package_panel.on_current_changed)
self.ato_model = ato_model
self.package_panel.ato_model = ato_model
self.package_panel.package_list.ato_model = ato_model
self.package_panel.package_list.setModel(ato_model)
self.package_panel.current_changed.connect(self.on_package_change)
self.flight_panel.flight_list.set_package(None)
self.game_model.is_ownfor = not opfor

View File

@ -11,12 +11,16 @@ class QFlightTypeComboBox(QComboBox):
"""Combo box for selecting a flight task type.""" """Combo box for selecting a flight task type."""
def __init__( def __init__(
self, theater: ConflictTheater, target: MissionTarget, settings: Settings self,
theater: ConflictTheater,
target: MissionTarget,
settings: Settings,
is_ownfor: bool,
) -> None: ) -> None:
super().__init__() super().__init__()
self.theater = theater self.theater = theater
self.target = target self.target = target
for mission_type in self.target.mission_types(for_player=True): for mission_type in self.target.mission_types(for_player=is_ownfor):
if mission_type == FlightType.AIR_ASSAULT and not settings.plugin_option( if mission_type == FlightType.AIR_ASSAULT and not settings.plugin_option(
"ctld" "ctld"
): ):

View File

@ -15,6 +15,7 @@ class GameUpdateSignal(QObject):
budgetupdated = Signal(Game) budgetupdated = Signal(Game)
game_state_changed = Signal(TurnState) game_state_changed = Signal(TurnState)
debriefingReceived = Signal(Debriefing) debriefingReceived = Signal(Debriefing)
ato_changed = Signal()
game_loaded = Signal(Game) game_loaded = Signal(Game)
@ -41,6 +42,9 @@ class GameUpdateSignal(QObject):
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
self.game_state_changed.emit(state) self.game_state_changed.emit(state)
def atoChanged(self) -> None:
self.ato_changed.emit()
@staticmethod @staticmethod
def get_instance() -> GameUpdateSignal: def get_instance() -> GameUpdateSignal:
return GameUpdateSignal.instance return GameUpdateSignal.instance

View File

@ -31,11 +31,14 @@ def _spinbox_template() -> QSpinBox:
class QAutoCreateDialog(QDialog): class QAutoCreateDialog(QDialog):
def __init__(self, game: Game, model: PackageModel, parent=None) -> None: def __init__(
self, game: Game, model: PackageModel, is_ownfor: bool, parent=None
) -> None:
super().__init__(parent) super().__init__(parent)
self.game = game self.game = game
self.package_model = model self.package_model = model
self.package = model.package self.package = model.package
self.is_ownfor = is_ownfor
self.setMinimumSize(300, 400) self.setMinimumSize(300, 400)
self.setWindowTitle( self.setWindowTitle(
@ -159,7 +162,7 @@ class QAutoCreateDialog(QDialog):
FlightType.BAI, FlightType.BAI,
FlightType.CAS, FlightType.CAS,
} }
for mt in self.package.target.mission_types(True): for mt in self.package.target.mission_types(self.is_ownfor):
if mt in primary_tasks: if mt in primary_tasks:
self.primary_combobox.addItem(mt.value, mt) self.primary_combobox.addItem(mt.value, mt)
self.primary_combobox.setCurrentIndex(0) self.primary_combobox.setCurrentIndex(0)
@ -172,15 +175,16 @@ class QAutoCreateDialog(QDialog):
return cb return cb
def _create_type_selector(self, flight_type: FlightType) -> QComboBox: def _create_type_selector(self, flight_type: FlightType) -> QComboBox:
airwing = self.game.blue.air_wing air_wing = self.game.blue.air_wing if self.is_ownfor else self.game.red.air_wing
cb = QComboBox() cb = QComboBox()
for ac in airwing.best_available_aircrafts_for(flight_type): for ac in air_wing.best_available_aircrafts_for(flight_type):
cb.addItem(ac.variant_id, ac) cb.addItem(ac.variant_id, ac)
return cb return cb
def _load_aircraft_types(self): def _load_aircraft_types(self):
self.primary_type.clear() self.primary_type.clear()
for ac in self.game.blue.air_wing.best_available_aircrafts_for( air_wing = self.game.blue.air_wing if self.is_ownfor else self.game.red.air_wing
for ac in air_wing.best_available_aircrafts_for(
self.primary_combobox.currentData() self.primary_combobox.currentData()
): ):
self.primary_type.addItem(ac.variant_id, ac) self.primary_type.addItem(ac.variant_id, ac)
@ -217,7 +221,7 @@ class QAutoCreateDialog(QDialog):
with tracer.trace(f"Auto-plan package"): with tracer.trace(f"Auto-plan package"):
pm = ProposedMission(self.package.target, pf, asap=True) pm = ProposedMission(self.package.target, pf, asap=True)
pff = PackageFulfiller( pff = PackageFulfiller(
self.game.coalition_for(True), self.game.coalition_for(self.is_ownfor),
self.game.theater, self.game.theater,
self.game.db.flights, self.game.db.flights,
self.game.settings, self.game.settings,

View File

@ -198,7 +198,10 @@ class QPackageDialog(QDialog):
def on_add_flight(self) -> None: def on_add_flight(self) -> None:
"""Opens the new flight dialog.""" """Opens the new flight dialog."""
self.add_flight_dialog = QFlightCreator( self.add_flight_dialog = QFlightCreator(
self.game, self.package_model.package, parent=self.window() self.game,
self.package_model.package,
is_ownfor=self.game_model.is_ownfor,
parent=self.window(),
) )
self.add_flight_dialog.created.connect(self.add_flight) self.add_flight_dialog.created.connect(self.add_flight)
self.add_flight_dialog.show() self.add_flight_dialog.show()
@ -235,7 +238,10 @@ class QPackageDialog(QDialog):
def on_auto_create(self) -> None: def on_auto_create(self) -> None:
"""Opens the new flight dialog.""" """Opens the new flight dialog."""
auto_create_dialog = QAutoCreateDialog( auto_create_dialog = QAutoCreateDialog(
self.game, self.package_model, parent=self.window() self.game,
self.package_model,
self.game_model.is_ownfor,
parent=self.window(),
) )
if auto_create_dialog.exec_() == QDialog.DialogCode.Accepted: if auto_create_dialog.exec_() == QDialog.DialogCode.Accepted:
for f in self.package_model.package.flights: for f in self.package_model.package.flights:
@ -275,7 +281,7 @@ class QNewPackageDialog(QPackageDialog):
""" """
def __init__( def __init__(
self, game_model: GameModel, model: AtoModel, target: MissionTarget, parent=None self, game_model: GameModel, target: MissionTarget, parent=None
) -> None: ) -> None:
super().__init__( super().__init__(
game_model, game_model,
@ -284,7 +290,9 @@ class QNewPackageDialog(QPackageDialog):
), ),
parent=parent, parent=parent,
) )
self.ato_model = model self.ato_model = (
game_model.ato_model if game_model.is_ownfor else game_model.red_ato_model
)
# In the *new* package dialog, a package has been created and may have aircraft # In the *new* package dialog, a package has been created and may have aircraft
# assigned to it, but it is not a part of the ATO until the user saves it. # assigned to it, but it is not a part of the ATO until the user saves it.

View File

@ -33,7 +33,9 @@ from qt_ui.windows.mission.flight.settings.QFlightSlotEditor import FlightRoster
class QFlightCreator(QDialog): class QFlightCreator(QDialog):
created = Signal(Flight) created = Signal(Flight)
def __init__(self, game: Game, package: Package, parent=None) -> None: def __init__(
self, game: Game, package: Package, is_ownfor: bool, parent=None
) -> None:
super().__init__(parent=parent) super().__init__(parent=parent)
self.setMinimumWidth(400) self.setMinimumWidth(400)
@ -50,23 +52,22 @@ class QFlightCreator(QDialog):
layout = QVBoxLayout() layout = QVBoxLayout()
self.task_selector = QFlightTypeComboBox( self.task_selector = QFlightTypeComboBox(
self.game.theater, package.target, self.game.settings self.game.theater, package.target, self.game.settings, is_ownfor
) )
self.task_selector.setCurrentIndex(0) self.task_selector.setCurrentIndex(0)
self.task_selector.currentIndexChanged.connect(self.on_task_changed) self.task_selector.currentIndexChanged.connect(self.on_task_changed)
layout.addLayout(QLabeledWidget("Task:", self.task_selector)) layout.addLayout(QLabeledWidget("Task:", self.task_selector))
self.air_wing = self.game.blue.air_wing if is_ownfor else self.game.red.air_wing
self.aircraft_selector = QAircraftTypeSelector( self.aircraft_selector = QAircraftTypeSelector(
self.game.blue.air_wing.best_available_aircrafts_for( self.air_wing.best_available_aircrafts_for(self.task_selector.currentData())
self.task_selector.currentData()
)
) )
self.aircraft_selector.setCurrentIndex(0) self.aircraft_selector.setCurrentIndex(0)
self.aircraft_selector.currentIndexChanged.connect(self.on_aircraft_changed) self.aircraft_selector.currentIndexChanged.connect(self.on_aircraft_changed)
layout.addLayout(QLabeledWidget("Aircraft:", self.aircraft_selector)) layout.addLayout(QLabeledWidget("Aircraft:", self.aircraft_selector))
self.squadron_selector = SquadronSelector( self.squadron_selector = SquadronSelector(
self.game.air_wing_for(player=True), self.air_wing,
self.task_selector.currentData(), self.task_selector.currentData(),
self.aircraft_selector.currentData(), self.aircraft_selector.currentData(),
) )
@ -74,7 +75,7 @@ class QFlightCreator(QDialog):
layout.addLayout(QLabeledWidget("Squadron:", self.squadron_selector)) layout.addLayout(QLabeledWidget("Squadron:", self.squadron_selector))
self.divert = QArrivalAirfieldSelector( self.divert = QArrivalAirfieldSelector(
[cp for cp in game.theater.controlpoints if cp.captured], [cp for cp in game.theater.controlpoints if cp.captured == is_ownfor],
self.aircraft_selector.currentData(), self.aircraft_selector.currentData(),
"None", "None",
) )
@ -235,7 +236,7 @@ class QFlightCreator(QDialog):
def on_task_changed(self, index: int) -> None: def on_task_changed(self, index: int) -> None:
task = self.task_selector.itemData(index) task = self.task_selector.itemData(index)
self.aircraft_selector.update_items( self.aircraft_selector.update_items(
self.game.blue.air_wing.best_available_aircrafts_for(task) self.air_wing.best_available_aircrafts_for(task)
) )
self.squadron_selector.update_items(task, self.aircraft_selector.currentData()) self.squadron_selector.update_items(task, self.aircraft_selector.currentData())

View File

@ -54,13 +54,6 @@ class CheatSettingsBox(QGroupBox):
self.main_layout = QVBoxLayout() self.main_layout = QVBoxLayout()
self.setLayout(self.main_layout) self.setLayout(self.main_layout)
# ATO
self.red_ato_checkbox = QCheckBox()
self.red_ato_checkbox.setChecked(sc.settings.show_red_ato)
self.red_ato_checkbox.toggled.connect(apply_settings)
self.red_ato = QLabeledWidget("Show Red ATO:", self.red_ato_checkbox)
self.main_layout.addLayout(self.red_ato)
# Frontline # Frontline
self.frontline_cheat_checkbox = QCheckBox() self.frontline_cheat_checkbox = QCheckBox()
self.frontline_cheat_checkbox.setChecked(sc.settings.enable_frontline_cheats) self.frontline_cheat_checkbox.setChecked(sc.settings.enable_frontline_cheats)
@ -122,10 +115,6 @@ class CheatSettingsBox(QGroupBox):
) )
self.main_layout.addLayout(self.redfor_buysell_cheat) self.main_layout.addLayout(self.redfor_buysell_cheat)
@property
def show_red_ato(self) -> bool:
return self.red_ato_checkbox.isChecked()
@property @property
def show_frontline_cheat(self) -> bool: def show_frontline_cheat(self) -> bool:
return self.frontline_cheat_checkbox.isChecked() return self.frontline_cheat_checkbox.isChecked()
@ -496,7 +485,6 @@ class QSettingsWidget(QtWidgets.QWizardPage, SettingsContainer):
def applySettings(self): def applySettings(self):
if self.updating_ui: if self.updating_ui:
return return
self.settings.show_red_ato = self.cheat_options.show_red_ato
self.settings.enable_frontline_cheats = self.cheat_options.show_frontline_cheat self.settings.enable_frontline_cheats = self.cheat_options.show_frontline_cheat
self.settings.enable_base_capture_cheat = ( self.settings.enable_base_capture_cheat = (
self.cheat_options.show_base_capture_cheat self.cheat_options.show_base_capture_cheat
@ -525,7 +513,6 @@ class QSettingsWidget(QtWidgets.QWizardPage, SettingsContainer):
for p in self.pages.values(): for p in self.pages.values():
p.update_from_settings() p.update_from_settings()
self.cheat_options.red_ato_checkbox.setChecked(self.settings.show_red_ato)
self.cheat_options.base_capture_cheat_checkbox.setChecked( self.cheat_options.base_capture_cheat_checkbox.setChecked(
self.settings.enable_base_capture_cheat self.settings.enable_base_capture_cheat
) )