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)