diff --git a/changelog.md b/changelog.md index 9daa566a..2234e9ff 100644 --- a/changelog.md +++ b/changelog.md @@ -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]** 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. - +* **[ATO]** Allow planning as OPFOR +* ## Fixes * **[UI/UX]** A-10A flights can be edited again. * **[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 - # 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. diff --git a/game/settings/settings.py b/game/settings/settings.py index 28146724..92eb8351 100644 --- a/game/settings/settings.py +++ b/game/settings/settings.py @@ -1036,7 +1036,6 @@ class Settings: # Cheating. Not using auto settings because the same page also has buttons which do # not alter settings. - show_red_ato: bool = False enable_frontline_cheats: bool = False enable_base_capture_cheat: bool = False enable_transfer_cheat: bool = False diff --git a/qt_ui/dialogs.py b/qt_ui/dialogs.py index 92064768..8b13b954 100644 --- a/qt_ui/dialogs.py +++ b/qt_ui/dialogs.py @@ -37,7 +37,7 @@ class Dialog: def open_new_package_dialog(cls, mission_target: MissionTarget, parent=None): """Opens the dialog to create a new package with the given target.""" 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() diff --git a/qt_ui/models.py b/qt_ui/models.py index 42576253..2ead249d 100644 --- a/qt_ui/models.py +++ b/qt_ui/models.py @@ -547,6 +547,7 @@ class GameModel: else: self.ato_model = AtoModel(self, self.game.blue.ato) self.red_ato_model = AtoModel(self, self.game.red.ato) + self.is_ownfor = False # For UI purposes self.allocated_freqs: list[RadioFrequency] = list() diff --git a/qt_ui/widgets/ato.py b/qt_ui/widgets/ato.py index 620ff659..48a6c07b 100644 --- a/qt_ui/widgets/ato.py +++ b/qt_ui/widgets/ato.py @@ -23,12 +23,14 @@ from PySide6.QtWidgets import ( QSplitter, QVBoxLayout, QMessageBox, + QCheckBox, ) from game.ato.flight import Flight from game.ato.package import Package from game.server import EventStream from game.sim import GameUpdateEvents +from .QLabeledWidget import QLabeledWidget from ..delegates import TwoColumnRowDelegate from ..models import AtoModel, GameModel, NullListModel, PackageModel @@ -484,8 +486,20 @@ class QAirTaskingOrderPanel(QSplitter): def __init__(self, game_model: GameModel) -> None: super().__init__(Qt.Orientation.Vertical) + self.game_model = game_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.current_changed.connect(self.on_package_change) self.addWidget(self.package_panel) @@ -500,3 +514,17 @@ class QAirTaskingOrderPanel(QSplitter): self.flight_panel.set_package(self.ato_model.get_package_model(index)) else: 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 diff --git a/qt_ui/widgets/combos/QFlightTypeComboBox.py b/qt_ui/widgets/combos/QFlightTypeComboBox.py index f6f7e28a..b2ee30dd 100644 --- a/qt_ui/widgets/combos/QFlightTypeComboBox.py +++ b/qt_ui/widgets/combos/QFlightTypeComboBox.py @@ -11,12 +11,16 @@ class QFlightTypeComboBox(QComboBox): """Combo box for selecting a flight task type.""" def __init__( - self, theater: ConflictTheater, target: MissionTarget, settings: Settings + self, + theater: ConflictTheater, + target: MissionTarget, + settings: Settings, + is_ownfor: bool, ) -> None: super().__init__() self.theater = theater 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( "ctld" ): diff --git a/qt_ui/windows/GameUpdateSignal.py b/qt_ui/windows/GameUpdateSignal.py index daeb0725..f5739dc1 100644 --- a/qt_ui/windows/GameUpdateSignal.py +++ b/qt_ui/windows/GameUpdateSignal.py @@ -15,6 +15,7 @@ class GameUpdateSignal(QObject): budgetupdated = Signal(Game) game_state_changed = Signal(TurnState) debriefingReceived = Signal(Debriefing) + ato_changed = Signal() game_loaded = Signal(Game) @@ -41,6 +42,9 @@ class GameUpdateSignal(QObject): # noinspection PyUnresolvedReferences self.game_state_changed.emit(state) + def atoChanged(self) -> None: + self.ato_changed.emit() + @staticmethod def get_instance() -> GameUpdateSignal: return GameUpdateSignal.instance diff --git a/qt_ui/windows/mission/QAutoCreateDialog.py b/qt_ui/windows/mission/QAutoCreateDialog.py index 95fcb7dd..3792c6c4 100644 --- a/qt_ui/windows/mission/QAutoCreateDialog.py +++ b/qt_ui/windows/mission/QAutoCreateDialog.py @@ -31,11 +31,14 @@ def _spinbox_template() -> QSpinBox: 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) self.game = game self.package_model = model self.package = model.package + self.is_ownfor = is_ownfor self.setMinimumSize(300, 400) self.setWindowTitle( @@ -159,7 +162,7 @@ class QAutoCreateDialog(QDialog): FlightType.BAI, 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: self.primary_combobox.addItem(mt.value, mt) self.primary_combobox.setCurrentIndex(0) @@ -172,15 +175,16 @@ class QAutoCreateDialog(QDialog): return cb 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() - 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) return cb def _load_aircraft_types(self): 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_type.addItem(ac.variant_id, ac) @@ -217,7 +221,7 @@ class QAutoCreateDialog(QDialog): with tracer.trace(f"Auto-plan package"): pm = ProposedMission(self.package.target, pf, asap=True) pff = PackageFulfiller( - self.game.coalition_for(True), + self.game.coalition_for(self.is_ownfor), self.game.theater, self.game.db.flights, self.game.settings, diff --git a/qt_ui/windows/mission/QPackageDialog.py b/qt_ui/windows/mission/QPackageDialog.py index 75412f92..30d430a8 100644 --- a/qt_ui/windows/mission/QPackageDialog.py +++ b/qt_ui/windows/mission/QPackageDialog.py @@ -198,7 +198,10 @@ class QPackageDialog(QDialog): def on_add_flight(self) -> None: """Opens the new flight dialog.""" 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.show() @@ -235,7 +238,10 @@ class QPackageDialog(QDialog): def on_auto_create(self) -> None: """Opens the new flight dialog.""" 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: for f in self.package_model.package.flights: @@ -275,7 +281,7 @@ class QNewPackageDialog(QPackageDialog): """ def __init__( - self, game_model: GameModel, model: AtoModel, target: MissionTarget, parent=None + self, game_model: GameModel, target: MissionTarget, parent=None ) -> None: super().__init__( game_model, @@ -284,7 +290,9 @@ class QNewPackageDialog(QPackageDialog): ), 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 # assigned to it, but it is not a part of the ATO until the user saves it. diff --git a/qt_ui/windows/mission/flight/QFlightCreator.py b/qt_ui/windows/mission/flight/QFlightCreator.py index 80cea6c7..6143c868 100644 --- a/qt_ui/windows/mission/flight/QFlightCreator.py +++ b/qt_ui/windows/mission/flight/QFlightCreator.py @@ -33,7 +33,9 @@ from qt_ui.windows.mission.flight.settings.QFlightSlotEditor import FlightRoster class QFlightCreator(QDialog): 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) self.setMinimumWidth(400) @@ -50,23 +52,22 @@ class QFlightCreator(QDialog): layout = QVBoxLayout() 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.currentIndexChanged.connect(self.on_task_changed) 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.game.blue.air_wing.best_available_aircrafts_for( - self.task_selector.currentData() - ) + self.air_wing.best_available_aircrafts_for(self.task_selector.currentData()) ) self.aircraft_selector.setCurrentIndex(0) self.aircraft_selector.currentIndexChanged.connect(self.on_aircraft_changed) layout.addLayout(QLabeledWidget("Aircraft:", self.aircraft_selector)) self.squadron_selector = SquadronSelector( - self.game.air_wing_for(player=True), + self.air_wing, self.task_selector.currentData(), self.aircraft_selector.currentData(), ) @@ -74,7 +75,7 @@ class QFlightCreator(QDialog): layout.addLayout(QLabeledWidget("Squadron:", self.squadron_selector)) 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(), "None", ) @@ -235,7 +236,7 @@ class QFlightCreator(QDialog): def on_task_changed(self, index: int) -> None: task = self.task_selector.itemData(index) 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()) diff --git a/qt_ui/windows/settings/QSettingsWindow.py b/qt_ui/windows/settings/QSettingsWindow.py index 8e44f809..6d4525b2 100644 --- a/qt_ui/windows/settings/QSettingsWindow.py +++ b/qt_ui/windows/settings/QSettingsWindow.py @@ -54,13 +54,6 @@ class CheatSettingsBox(QGroupBox): self.main_layout = QVBoxLayout() 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 self.frontline_cheat_checkbox = QCheckBox() 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) - @property - def show_red_ato(self) -> bool: - return self.red_ato_checkbox.isChecked() - @property def show_frontline_cheat(self) -> bool: return self.frontline_cheat_checkbox.isChecked() @@ -496,7 +485,6 @@ class QSettingsWidget(QtWidgets.QWizardPage, SettingsContainer): def applySettings(self): if self.updating_ui: 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_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(): 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.settings.enable_base_capture_cheat )