mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Support replacing squadrons in-place.
This commit is contained in:
parent
c9e4b5eba4
commit
e444761059
@ -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
|
||||
|
||||
@ -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("<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)
|
||||
@ -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(
|
||||
"<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) -> 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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user