From 42a71029480da4f50d2f568fc83cbc848b08139d Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sat, 3 Jun 2023 13:19:44 -0700 Subject: [PATCH] Disallow air wing generation with overfull bases. This also changes the window close button of the air wing configuration dialog to cancel rather than revert and continue, because otherwise there's no way for the user to back out of the dialog without fixing all the overfull bases first. Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2910. --- changelog.md | 2 + qt_ui/main.py | 5 +- qt_ui/windows/AirWingConfigurationDialog.py | 86 +++++++++++++++------ qt_ui/windows/newgame/QNewGameWizard.py | 8 +- 4 files changed, 75 insertions(+), 26 deletions(-) diff --git a/changelog.md b/changelog.md index 263c1508..4f334c32 100644 --- a/changelog.md +++ b/changelog.md @@ -17,6 +17,8 @@ Saves from 7.0.0 are compatible with 7.1.0 * **[Mission Generation]** Added option to prevent scud and V2 sites from firing at the start of the mission. * **[Mission Generation]** Added settings for controlling number of tactical commander, observer, JTAC, and game master slots. * **[Mission Planning]** Per-flight TOT offsets can now be set in the flight details UI. This allows individual flights to be scheduled ahead of or behind the rest of the package. +* **[New Game Wizard]** The air wing configuration dialog will check for and reject overfull airbases before continuing when the new squadron rules are used. +* **[New Game Wizard]** Closing the air wing configuration dialog will now cancel and return to the new game wizard rather than reverting changes and continuing. * **[UI]** Parking capacity of each squadron's base is now shown during air wing configuration to avoid overcrowding bases when beginning the game with full squadrons. ## Fixes diff --git a/qt_ui/main.py b/qt_ui/main.py index 5672b10e..57988d17 100644 --- a/qt_ui/main.py +++ b/qt_ui/main.py @@ -13,7 +13,7 @@ import yaml from PySide6 import QtWidgets from PySide6.QtCore import Qt from PySide6.QtGui import QPixmap -from PySide6.QtWidgets import QApplication, QCheckBox, QSplashScreen +from PySide6.QtWidgets import QApplication, QCheckBox, QSplashScreen, QDialog from dcs.payloads import PayloadDirectories from game import Game, VERSION, logging_config, persistence @@ -357,7 +357,8 @@ def create_game(params: CreateGameParams) -> Game: ) game = generator.generate() if params.show_air_wing_config: - AirWingConfigurationDialog(game, None).exec_() + if AirWingConfigurationDialog(game, None).exec() == QDialog.DialogCode.Rejected: + sys.exit("Aborted air wing configuration") game.begin_turn_0(squadrons_start_full=params.use_new_squadron_rules) return game diff --git a/qt_ui/windows/AirWingConfigurationDialog.py b/qt_ui/windows/AirWingConfigurationDialog.py index 38f29ee2..4203a9ab 100644 --- a/qt_ui/windows/AirWingConfigurationDialog.py +++ b/qt_ui/windows/AirWingConfigurationDialog.py @@ -1,3 +1,4 @@ +import textwrap from collections import defaultdict from typing import Iterable, Iterator, Optional @@ -630,6 +631,32 @@ class AircraftTypeList(QListView): self.update(self.selectionModel().currentIndex()) +def describe_overfull_airbases( + overfull: Iterable[tuple[ControlPoint, int, list[Squadron]]] +) -> str: + string_builder = [] + for ( + control_point, + used_parking, + squadrons, + ) in overfull: + capacity = control_point.total_aircraft_parking + base_description = f"{control_point.name} {used_parking}/{capacity}" + string_builder.append(f"

{base_description}

") + squadron_descriptions = [] + for squadron in squadrons: + squadron_details = ( + f"{squadron.aircraft} {squadron.name} {squadron.max_size} aircraft" + ) + squadron_descriptions.append(f"
  • {squadron_details}
  • ") + string_builder.append(f"") + + if not string_builder: + string_builder.append("All airbases are within parking limits.") + + return "".join(string_builder) + + class OverfullAirbasesDisplay(QGroupBox): def __init__( self, @@ -649,27 +676,9 @@ class OverfullAirbasesDisplay(QGroupBox): self.on_allocation_changed() def on_allocation_changed(self) -> None: - string_builder = [] - for ( - control_point, - used_parking, - squadrons, - ) in self.parking_tracker.iter_overfull(): - capacity = control_point.total_aircraft_parking - base_description = f"{control_point.name} {used_parking}/{capacity}" - string_builder.append(f"

    {base_description}

    ") - squadron_descriptions = [] - for squadron in squadrons: - squadron_details = ( - f"{squadron.aircraft} {squadron.name} {squadron.max_size} aircraft" - ) - squadron_descriptions.append(f"
  • {squadron_details}
  • ") - string_builder.append(f"") - - if not string_builder: - string_builder.append("All airbases are within parking limits.") - - self.label.setText("".join(string_builder)) + self.label.setText( + describe_overfull_airbases(self.parking_tracker.iter_overfull()) + ) class AirWingConfigurationTab(QWidget): @@ -771,6 +780,7 @@ class AirWingConfigurationDialog(QDialog): def __init__(self, game: Game, parent) -> None: super().__init__(parent) + self.game = game self.parking_tracker = AirWingConfigParkingTracker(game) self.setMinimumSize(1024, 768) @@ -821,7 +831,29 @@ class AirWingConfigurationDialog(QDialog): for tab in self.tabs: tab.revert() + def can_continue(self) -> bool: + if not self.game.settings.enable_squadron_aircraft_limits: + return True + + overfull = list(self.parking_tracker.iter_overfull()) + if not overfull: + return True + + description = ( + "

    The following airbases are over capacity:

    " + f"{describe_overfull_airbases(overfull)}" + ) + QMessageBox().critical( + self, + "Cannot continue with overfull bases", + description, + QMessageBox.Ok, + ) + return False + def accept(self) -> None: + if not self.can_continue(): + return for tab in self.tabs: tab.apply() super().accept() @@ -829,8 +861,16 @@ class AirWingConfigurationDialog(QDialog): def reject(self) -> None: result = QMessageBox.information( None, - "Discard changes?", - "Are you sure you want to discard your changes and start the campaign?", + "Abort new game?", + "
    ".join( + textwrap.wrap( + "Are you sure you want to cancel air wing configuration and " + "return to the new game wizard? If you instead want to revert your " + "air wing changes and continue, use the revert and accept buttons " + "below.", + width=55, + ) + ), QMessageBox.Yes, QMessageBox.No, ) diff --git a/qt_ui/windows/newgame/QNewGameWizard.py b/qt_ui/windows/newgame/QNewGameWizard.py index af3b8005..e191890b 100644 --- a/qt_ui/windows/newgame/QNewGameWizard.py +++ b/qt_ui/windows/newgame/QNewGameWizard.py @@ -13,6 +13,7 @@ from PySide6.QtWidgets import ( QTextEdit, QVBoxLayout, QWidget, + QDialog, ) from jinja2 import Environment, FileSystemLoader, select_autoescape @@ -223,7 +224,12 @@ class NewGameWizard(QtWidgets.QWizard): ) self.generatedGame = generator.generate() - AirWingConfigurationDialog(self.generatedGame, self).exec_() + if ( + AirWingConfigurationDialog(self.generatedGame, self).exec() + == QDialog.DialogCode.Rejected + ): + logging.info("Aborted air wing configuration") + return self.generatedGame.begin_turn_0(squadrons_start_full=use_new_squadron_rules)