Support replacing squadrons in-place.

This commit is contained in:
Dan Albert 2023-05-04 20:34:29 -07:00 committed by Raffson
parent 7bbf5d59e4
commit e95a6bf759
No known key found for this signature in database
GPG Key ID: B0402B2C9B764D99
2 changed files with 162 additions and 42 deletions

View File

@ -138,6 +138,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.
* **[Modding]** The `mission_types` field in squadron files has been removed. Squadron task capability is now determined by airframe, and the auto-assignable list has always been overridden by the campaign settings.
* **[Squadrons]** Squadron-specific mission capability lists no longer restrict players from assigning missions outside the squadron's preferences.
* **[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.
## Fixes

View File

@ -36,27 +36,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):
@ -79,7 +74,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]:
@ -87,6 +82,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.
@ -168,8 +170,15 @@ class SquadronLiverySelector(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()
@ -203,7 +212,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,
)
@ -220,20 +229,27 @@ class SquadronConfigurationBox(QGroupBox):
player_label = QLabel(text)
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("<br />".join(p.name for p in players))
self.player_list = QTextEdit(
"<br />".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)
@ -241,9 +257,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(
"<br />".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) -> Optional[Squadron]:
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}")
@ -260,12 +336,8 @@ class SquadronConfigurationBox(QGroupBox):
raise RuntimeError("Base cannot be none")
self.squadron.assign_to_base(base)
self.squadron.livery = self.livery_selector.currentData()
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)
@ -276,10 +348,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)
@ -300,7 +378,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)
@ -309,12 +387,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()
@ -342,14 +422,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)
@ -361,7 +445,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
@ -465,7 +549,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)
@ -484,7 +568,8 @@ class AirWingConfigurationTab(QWidget):
possible_aircrafts = [
aircraft
for aircraft in self.coalition.faction.aircrafts
if any(base.can_operate(aircraft) for base in bases)
if isinstance(aircraft, AircraftType)
and any(base.can_operate(aircraft) for base in bases)
]
popup = SquadronConfigPopup(
@ -611,15 +696,18 @@ class SquadronDefSelector(QComboBox):
self,
squadron_defs: dict[AircraftType, list[SquadronDef]],
aircraft: Optional[AircraftType],
allow_random: bool = True,
) -> None:
super().__init__()
self.setSizeAdjustPolicy(self.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
@ -629,7 +717,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):
@ -647,8 +735,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
@ -700,3 +786,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)