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.
This commit is contained in:
Dan Albert 2023-06-03 13:19:44 -07:00
parent d271ff17c2
commit 42a7102948
4 changed files with 75 additions and 26 deletions

View File

@ -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

View File

@ -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

View File

@ -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"<p><strong>{base_description}</strong></p>")
squadron_descriptions = []
for squadron in squadrons:
squadron_details = (
f"{squadron.aircraft} {squadron.name} {squadron.max_size} aircraft"
)
squadron_descriptions.append(f"<li>{squadron_details}</li>")
string_builder.append(f"<ul>{''.join(squadron_descriptions)}</ul>")
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"<p><strong>{base_description}</strong></p>")
squadron_descriptions = []
for squadron in squadrons:
squadron_details = (
f"{squadron.aircraft} {squadron.name} {squadron.max_size} aircraft"
)
squadron_descriptions.append(f"<li>{squadron_details}</li>")
string_builder.append(f"<ul>{''.join(squadron_descriptions)}</ul>")
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 = (
"<p>The following airbases are over capacity:</p>"
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?",
"<br />".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,
)

View File

@ -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)