diff --git a/changelog.md b/changelog.md
index 422c6e8a..a6a8021f 100644
--- a/changelog.md
+++ b/changelog.md
@@ -18,6 +18,7 @@ Saves from 6.x are not compatible with 7.0.
* **[Modding]** Aircraft task capabilities and preferred aircraft for each task are now moddable in the aircraft unit yaml files. Each aircraft has a weight per task. Higher weights are given higher preference.
* **[New Game Wizard]** Choices for some options will be remembered for the next new game. Not all settings will be preserved, as many are campaign dependent.
* **[New Game Wizard]** Lua plugins can now be set while creating a new game.
+* **[New Game Wizard]** Squadrons can be directly replaced with a preset during air wing configuration rather than needing to remove and create a new squadron.
* **[Squadrons]** Squadron-specific mission capability lists no longer restrict players from assigning missions outside the squadron's preferences.
## Fixes
diff --git a/qt_ui/windows/AirWingConfigurationDialog.py b/qt_ui/windows/AirWingConfigurationDialog.py
index 0232ac53..3ef8f08e 100644
--- a/qt_ui/windows/AirWingConfigurationDialog.py
+++ b/qt_ui/windows/AirWingConfigurationDialog.py
@@ -35,27 +35,22 @@ from game.coalition import Coalition
from game.dcs.aircrafttype import AircraftType
from game.squadrons import AirWing, Pilot, Squadron
from game.squadrons.squadrondef import SquadronDef
-from game.theater import ConflictTheater, ControlPoint
+from game.theater import ControlPoint
from qt_ui.uiconstants import AIRCRAFT_ICONS, ICONS
-class QMissionType:
+class QMissionType(QCheckBox):
def __init__(
self, mission_type: FlightType, allowed: bool, auto_assignable: bool
) -> None:
+ super().__init__()
self.flight_type = mission_type
- self.auto_assignable_checkbox = QCheckBox()
- self.auto_assignable_checkbox.setEnabled(allowed)
- self.auto_assignable_checkbox.setChecked(auto_assignable)
-
- def update_auto_assignable(self, checked: bool) -> None:
- self.auto_assignable_checkbox.setEnabled(checked)
- if not checked:
- self.auto_assignable_checkbox.setChecked(False)
+ self.setEnabled(allowed)
+ self.setChecked(auto_assignable)
@property
def auto_assignable(self) -> bool:
- return self.auto_assignable_checkbox.isChecked()
+ return self.isChecked()
class MissionTypeControls(QGridLayout):
@@ -78,7 +73,7 @@ class MissionTypeControls(QGridLayout):
self.mission_types.append(mission_type)
self.addWidget(QLabel(task.value), i + 1, 0)
- self.addWidget(mission_type.auto_assignable_checkbox, i + 1, 1)
+ self.addWidget(mission_type, i + 1, 1)
@property
def auto_assignable_mission_types(self) -> Iterator[FlightType]:
@@ -86,6 +81,13 @@ class MissionTypeControls(QGridLayout):
if mission_type.auto_assignable:
yield mission_type.flight_type
+ def replace_squadron(self, squadron: Squadron) -> None:
+ self.squadron = squadron
+ for mission_type in self.mission_types:
+ mission_type.setChecked(
+ mission_type.flight_type in self.squadron.auto_assignable_mission_types
+ )
+
class SquadronBaseSelector(QComboBox):
"""A combo box for selecting a squadrons home air base.
@@ -127,8 +129,15 @@ class SquadronBaseSelector(QComboBox):
class SquadronConfigurationBox(QGroupBox):
remove_squadron_signal = Signal(Squadron)
- def __init__(self, squadron: Squadron, theater: ConflictTheater) -> None:
+ def __init__(
+ self,
+ game: Game,
+ coalition: Coalition,
+ squadron: Squadron,
+ ) -> None:
super().__init__()
+ self.game = game
+ self.coalition = coalition
self.squadron = squadron
columns = QHBoxLayout()
@@ -158,7 +167,7 @@ class SquadronConfigurationBox(QGroupBox):
left_column.addWidget(QLabel("Base:"))
self.base_selector = SquadronBaseSelector(
- theater.control_points_for(squadron.player),
+ game.theater.control_points_for(squadron.player),
squadron.location,
squadron.aircraft,
)
@@ -174,20 +183,27 @@ class SquadronConfigurationBox(QGroupBox):
)
left_column.addWidget(player_label)
- players = [p for p in squadron.pilot_pool if p.player]
- for player in players:
- squadron.pilot_pool.remove(player)
- if not squadron.player:
- players = []
- self.player_list = QTextEdit("
".join(p.name for p in players))
+ self.player_list = QTextEdit(
+ "
".join(p.name for p in self.claim_players_from_squadron())
+ )
self.player_list.setAcceptRichText(False)
self.player_list.setEnabled(squadron.player and squadron.aircraft.flyable)
left_column.addWidget(self.player_list)
+
+ button_row = QHBoxLayout()
+ left_column.addLayout(button_row)
+ left_column.addStretch()
+
delete_button = QPushButton("Remove Squadron")
delete_button.setMaximumWidth(140)
delete_button.clicked.connect(self.remove_from_squadron_config)
- left_column.addWidget(delete_button)
- left_column.addStretch()
+ button_row.addWidget(delete_button)
+
+ replace_button = QPushButton("Replace with preset")
+ replace_button.setMaximumWidth(140)
+ replace_button.clicked.connect(self.replace_with_preset)
+ button_row.addWidget(replace_button)
+ button_row.addStretch()
right_column = QVBoxLayout()
self.mission_types = MissionTypeControls(squadron)
@@ -195,9 +211,69 @@ class SquadronConfigurationBox(QGroupBox):
right_column.addStretch()
columns.addLayout(right_column)
+ def bind_data(self) -> None:
+ old_state = self.blockSignals(True)
+ try:
+ self.name_edit.setText(self.squadron.name)
+ self.nickname_edit.setText(self.squadron.nickname)
+ self.base_selector.setCurrentText(self.squadron.location.name)
+ self.player_list.setText(
+ "
".join(p.name for p in self.claim_players_from_squadron())
+ )
+ finally:
+ self.blockSignals(old_state)
+
def remove_from_squadron_config(self) -> None:
self.remove_squadron_signal.emit(self.squadron)
+ def pick_replacement_squadron(self) -> Squadron | None:
+ popup = PresetSquadronSelector(
+ self.squadron.aircraft,
+ self.coalition.air_wing.squadron_defs,
+ )
+ if popup.exec_() != QDialog.Accepted:
+ return None
+
+ selected_def = popup.squadron_def_selector.currentData()
+
+ self.squadron.coalition.air_wing.unclaim_squadron_def(self.squadron)
+ squadron = Squadron.create_from(
+ selected_def,
+ self.squadron.location,
+ self.coalition,
+ self.game,
+ )
+ return squadron
+
+ def claim_players_from_squadron(self) -> list[Pilot]:
+ if not self.squadron.player:
+ return []
+
+ players = [p for p in self.squadron.pilot_pool if p.player]
+ for player in players:
+ self.squadron.pilot_pool.remove(player)
+ return players
+
+ def return_players_to_squadron(self) -> None:
+ if not self.squadron.player:
+ return
+
+ player_names = self.player_list.toPlainText().splitlines()
+ # Prepend player pilots so they get set active first.
+ self.squadron.pilot_pool = [
+ Pilot(n, player=True) for n in player_names
+ ] + self.squadron.pilot_pool
+
+ def replace_with_preset(self) -> None:
+ new_squadron = self.pick_replacement_squadron()
+ if new_squadron is None:
+ # The user canceled the dialog.
+ return
+ self.return_players_to_squadron()
+ self.squadron = new_squadron
+ self.bind_data()
+ self.mission_types.replace_squadron(self.squadron)
+
def reset_title(self) -> None:
self.setTitle(f"{self.name_edit.text()} - {self.squadron.aircraft}")
@@ -213,12 +289,8 @@ class SquadronConfigurationBox(QGroupBox):
if base is None:
raise RuntimeError("Base cannot be none")
self.squadron.assign_to_base(base)
+ self.return_players_to_squadron()
- player_names = self.player_list.toPlainText().splitlines()
- # Prepend player pilots so they get set active first.
- self.squadron.pilot_pool = [
- Pilot(n, player=True) for n in player_names
- ] + self.squadron.pilot_pool
# Also update the auto assignable mission types
self.squadron.set_auto_assignable_mission_types(
set(self.mission_types.auto_assignable_mission_types)
@@ -229,10 +301,16 @@ class SquadronConfigurationBox(QGroupBox):
class SquadronConfigurationLayout(QVBoxLayout):
config_changed = Signal(AircraftType)
- def __init__(self, squadrons: list[Squadron], theater: ConflictTheater) -> None:
+ def __init__(
+ self,
+ game: Game,
+ coalition: Coalition,
+ squadrons: list[Squadron],
+ ) -> None:
super().__init__()
+ self.game = game
+ self.coalition = coalition
self.squadron_configs = []
- self.theater = theater
for squadron in squadrons:
self.add_squadron(squadron)
@@ -253,7 +331,7 @@ class SquadronConfigurationLayout(QVBoxLayout):
return
def add_squadron(self, squadron: Squadron) -> None:
- squadron_config = SquadronConfigurationBox(squadron, self.theater)
+ squadron_config = SquadronConfigurationBox(self.game, self.coalition, squadron)
squadron_config.remove_squadron_signal.connect(self.remove_squadron)
self.squadron_configs.append(squadron_config)
self.addWidget(squadron_config)
@@ -262,12 +340,14 @@ class SquadronConfigurationLayout(QVBoxLayout):
class AircraftSquadronsPage(QWidget):
remove_squadron_page = Signal(AircraftType)
- def __init__(self, squadrons: list[Squadron], theater: ConflictTheater) -> None:
+ def __init__(
+ self, game: Game, coalition: Coalition, squadrons: list[Squadron]
+ ) -> None:
super().__init__()
layout = QVBoxLayout()
self.setLayout(layout)
- self.squadrons_config = SquadronConfigurationLayout(squadrons, theater)
+ self.squadrons_config = SquadronConfigurationLayout(game, coalition, squadrons)
self.squadrons_config.config_changed.connect(self.on_squadron_config_changed)
scrolling_widget = QWidget()
@@ -295,14 +375,18 @@ class AircraftSquadronsPage(QWidget):
class AircraftSquadronsPanel(QStackedLayout):
page_removed = Signal(AircraftType)
- def __init__(self, air_wing: AirWing, theater: ConflictTheater) -> None:
+ def __init__(self, game: Game, coalition: Coalition) -> None:
super().__init__()
- self.air_wing = air_wing
- self.theater = theater
+ self.game = game
+ self.coalition = coalition
self.squadrons_pages: dict[AircraftType, AircraftSquadronsPage] = {}
for aircraft, squadrons in self.air_wing.squadrons.items():
self.new_page_for_type(aircraft, squadrons)
+ @property
+ def air_wing(self) -> AirWing:
+ return self.coalition.air_wing
+
def remove_page_for_type(self, aircraft_type: AircraftType):
page = self.squadrons_pages[aircraft_type]
self.removeWidget(page)
@@ -314,7 +398,7 @@ class AircraftSquadronsPanel(QStackedLayout):
def new_page_for_type(
self, aircraft_type: AircraftType, squadrons: list[Squadron]
) -> None:
- page = AircraftSquadronsPage(squadrons, self.theater)
+ page = AircraftSquadronsPage(self.game, self.coalition, squadrons)
page.remove_squadron_page.connect(self.remove_page_for_type)
self.addWidget(page)
self.squadrons_pages[aircraft_type] = page
@@ -417,7 +501,7 @@ class AirWingConfigurationTab(QWidget):
add_button.clicked.connect(lambda state: self.add_squadron())
layout.addWidget(add_button, 2, 1, 1, 1)
- self.squadrons_panel = AircraftSquadronsPanel(coalition.air_wing, game.theater)
+ self.squadrons_panel = AircraftSquadronsPanel(game, coalition)
self.squadrons_panel.page_removed.connect(self.type_list.remove_aircraft_type)
layout.addLayout(self.squadrons_panel, 1, 3, 2, 1)
@@ -563,15 +647,18 @@ class SquadronDefSelector(QComboBox):
self,
squadron_defs: dict[AircraftType, list[SquadronDef]],
aircraft: Optional[AircraftType],
+ allow_random: bool = True,
) -> None:
super().__init__()
self.setSizeAdjustPolicy(QComboBox.SizeAdjustPolicy.AdjustToContents)
self.squadron_defs = squadron_defs
+ self.allow_random = allow_random
self.set_aircraft_type(aircraft)
def set_aircraft_type(self, aircraft: Optional[AircraftType]):
self.clear()
- self.addItem("None (Random)", None)
+ if self.allow_random:
+ self.addItem("None (Random)", None)
if aircraft and aircraft in self.squadron_defs:
for squadron_def in sorted(
self.squadron_defs[aircraft], key=lambda squadron_def: squadron_def.name
@@ -581,7 +668,7 @@ class SquadronDefSelector(QComboBox):
if squadron_def.nickname:
squadron_name += " (" + squadron_def.nickname + ")"
self.addItem(squadron_name, squadron_def)
- self.setCurrentText("None (Random)")
+ self.setCurrentIndex(0)
class SquadronConfigPopup(QDialog):
@@ -599,8 +686,6 @@ class SquadronConfigPopup(QDialog):
self.column = QVBoxLayout()
self.setLayout(self.column)
- self.bases = bases
-
self.column.addWidget(QLabel("Aircraft:"))
self.aircraft_type_selector = SquadronAircraftTypeSelector(
types, selected_aircraft
@@ -652,3 +737,36 @@ class SquadronConfigPopup(QDialog):
)
self.update_accept_button()
self.update()
+
+
+class PresetSquadronSelector(QDialog):
+ def __init__(
+ self,
+ aircraft: AircraftType,
+ squadron_defs: dict[AircraftType, list[SquadronDef]],
+ ) -> None:
+ super().__init__()
+
+ self.setWindowTitle(f"Choose preset squadron")
+
+ self.column = QVBoxLayout()
+ self.setLayout(self.column)
+
+ self.column.addWidget(QLabel("Preset:"))
+ self.squadron_def_selector = SquadronDefSelector(
+ squadron_defs, aircraft, allow_random=False
+ )
+ self.column.addWidget(self.squadron_def_selector)
+
+ self.column.addStretch()
+
+ self.button_layout = QHBoxLayout()
+ self.column.addLayout(self.button_layout)
+
+ self.accept_button = QPushButton("Accept")
+ self.accept_button.clicked.connect(lambda state: self.accept())
+ self.button_layout.addWidget(self.accept_button)
+
+ self.cancel_button = QPushButton("Cancel")
+ self.cancel_button.clicked.connect(lambda state: self.reject())
+ self.button_layout.addWidget(self.cancel_button)