From a27663e4b68b6a80f11a68bd2607a4b93157b6a6 Mon Sep 17 00:00:00 2001 From: MetalStormGhost <89945461+MetalStormGhost@users.noreply.github.com> Date: Thu, 9 May 2024 13:19:30 +0300 Subject: [PATCH] Default start type for player flights (#303) * Implemented a new option in settings: Default start type for Player flights. * Updated changelog. * Removed unnecessary country parameter. * Restore missing parameter * on_pilot_changed should emit pilots_changed in its finally block, otherwise the start-type isn't updated if you have a single client pilot which you switch to a non-client pilot. Also implemented other changes suggested by @Raffson, such as a more streamlined start_type QComboBox handling and moving the pilots_changed Signal to FlightRosterEditor. * Decouple Signal from QFlighStartType --------- Co-authored-by: Raffson --- changelog.md | 1 + game/ato/flightroster.py | 4 +++ game/ato/iflightroster.py | 5 +++ game/commander/packagebuilder.py | 6 ++++ game/settings/settings.py | 8 +++++ .../windows/mission/flight/QFlightCreator.py | 33 +++++++++++++++++-- .../flight/settings/QFlightSlotEditor.py | 27 ++++++++++++--- .../flight/settings/QFlightStartType.py | 19 +++++++++++ .../settings/QGeneralFlightSettingsTab.py | 13 +++++++- 9 files changed, 108 insertions(+), 8 deletions(-) diff --git a/changelog.md b/changelog.md index 642e3c65..00094acd 100644 --- a/changelog.md +++ b/changelog.md @@ -11,6 +11,7 @@ * **[Campaign Design]** Ability to configure specific carrier names & types in campaign's yaml file * **[Mission Generation]** Ability to inject custom kneeboards * **[Options]** Extend option (so it can be disabled when fixed in DCS) to force air-starts (except for the slots that work) at Ramon Airbase, similar to the Nevatim fix in Retribution 1.3.0 +* **[Options]** New option in Settings: Default start type for Player flights. ## Fixes * **[UI/UX]** A-10A flights can be edited again. diff --git a/game/ato/flightroster.py b/game/ato/flightroster.py index 859b891c..5cff9fe5 100644 --- a/game/ato/flightroster.py +++ b/game/ato/flightroster.py @@ -22,6 +22,10 @@ class FlightRoster(IFlightRoster): def iter_pilots(self) -> Iterator[Pilot | None]: yield from self.pilots + @property + def player_count(self) -> int: + return len([p for p in self.pilots if p is not None and p.player]) + def pilot_at(self, idx: int) -> Pilot | None: return self.pilots[idx] diff --git a/game/ato/iflightroster.py b/game/ato/iflightroster.py index 62ea8c6b..685bfad7 100644 --- a/game/ato/iflightroster.py +++ b/game/ato/iflightroster.py @@ -26,6 +26,11 @@ class IFlightRoster(ABC): def max_size(self) -> int: ... + @property + @abstractmethod + def player_count(self) -> int: + ... + @abstractmethod def resize(self, new_size: int) -> None: ... diff --git a/game/commander/packagebuilder.py b/game/commander/packagebuilder.py index 6f1fafcd..dcc50770 100644 --- a/game/commander/packagebuilder.py +++ b/game/commander/packagebuilder.py @@ -76,6 +76,12 @@ class PackageBuilder: member.assign_tgp_laser_code( self.laser_code_registry.alloc_laser_code() ) + # If this is a client flight, set the start_type again to match the configured default + # https://github.com/dcs-liberation/dcs_liberation/issues/1567 + if flight.roster is not None and flight.roster.player_count > 0: + flight.start_type = ( + squadron.coalition.game.settings.default_start_type_client + ) self.package.add_flight(flight) return True diff --git a/game/settings/settings.py b/game/settings/settings.py index c0dabb26..28146724 100644 --- a/game/settings/settings.py +++ b/game/settings/settings.py @@ -739,6 +739,14 @@ class Settings: "will not be included in automatically planned OCA packages." ), ) + default_start_type_client: StartType = choices_option( + "Default start type for Player flights", + page=MISSION_GENERATOR_PAGE, + section=GAMEPLAY_SECTION, + choices={v.value: v for v in StartType}, + default=StartType.COLD, + detail=("Default start type for flights containing Player/Client slots."), + ) nevatim_parking_fix: bool = boolean_option( "Force air-starts for aircraft at Nevatim and Ramon Airbase inoperable parking slots", page=MISSION_GENERATOR_PAGE, diff --git a/qt_ui/windows/mission/flight/QFlightCreator.py b/qt_ui/windows/mission/flight/QFlightCreator.py index aeee0952..6759f036 100644 --- a/qt_ui/windows/mission/flight/QFlightCreator.py +++ b/qt_ui/windows/mission/flight/QFlightCreator.py @@ -89,8 +89,7 @@ class QFlightCreator(QDialog): roster = None else: roster = FlightRoster( - squadron, - initial_size=self.flight_size_spinner.value(), + squadron, initial_size=self.flight_size_spinner.value() ) self.roster_editor = FlightRosterEditor(squadron, roster) self.flight_size_spinner.valueChanged.connect(self.roster_editor.resize) @@ -100,10 +99,12 @@ class QFlightCreator(QDialog): roster_layout.addWidget(QLabel("Assigned pilots:")) roster_layout.addLayout(self.roster_editor) + self.roster_editor.pilots_changed.connect(self.on_pilot_selected) + # When an off-map spawn overrides the start type to in-flight, we save # the selected type into this value. If a non-off-map spawn is selected # we restore the previous choice. - self.restore_start_type = self.game.settings.default_start_type + self.restore_start_type: Optional[str] = None self.start_type = QComboBox() for start_type in StartType: self.start_type.addItem(start_type.value, start_type) @@ -140,6 +141,8 @@ class QFlightCreator(QDialog): self.setLayout(layout) + self.roster_editor.pilots_changed.emit() + def reject(self) -> None: super().reject() # Clear the roster to return pilots to the pool. @@ -213,6 +216,8 @@ class QFlightCreator(QDialog): ) self.divert.change_aircraft(new_aircraft) + self.roster_editor.pilots_changed.emit() + def on_departure_changed(self, departure: ControlPoint) -> None: if isinstance(departure, OffMapSpawn): previous_type = self.start_type.currentData() @@ -245,6 +250,8 @@ class QFlightCreator(QDialog): ) self.on_departure_changed(squadron.location) + self.roster_editor.pilots_changed.emit() + def update_max_size(self, available: int) -> None: aircraft = self.aircraft_selector.currentData() if aircraft is None: @@ -255,3 +262,23 @@ class QFlightCreator(QDialog): default_size = max(2, available, aircraft.max_group_size) self.flight_size_spinner.setValue(default_size) + + try: + self.roster_editor.pilots_changed.emit() + except AttributeError: + return + + def on_pilot_selected(self): + # Pilot selection detected. If this is a player flight, set start_type + # as configured for players in the settings. + # Otherwise, set the start_type as configured for AI. + # https://github.com/dcs-liberation/dcs_liberation/issues/1567 + + roster = self.roster_editor.roster + + if roster is not None and roster.player_count > 0: + start_type = self.game.settings.default_start_type_client + else: + start_type = self.game.settings.default_start_type + + self.start_type.setCurrentText(start_type.value) diff --git a/qt_ui/windows/mission/flight/settings/QFlightSlotEditor.py b/qt_ui/windows/mission/flight/settings/QFlightSlotEditor.py index bd7f4426..4a153780 100644 --- a/qt_ui/windows/mission/flight/settings/QFlightSlotEditor.py +++ b/qt_ui/windows/mission/flight/settings/QFlightSlotEditor.py @@ -96,11 +96,16 @@ class PilotControls(QHBoxLayout): player_toggled = Signal() def __init__( - self, squadron: Optional[Squadron], roster: Optional[FlightRoster], idx: int + self, + squadron: Optional[Squadron], + roster: Optional[FlightRoster], + idx: int, + pilots_changed: Signal, ) -> None: super().__init__() self.roster = roster self.pilot_index = idx + self.pilots_changed = pilots_changed self.selector = PilotSelector(squadron, roster, idx) self.selector.currentIndexChanged.connect(self.on_pilot_changed) @@ -131,6 +136,8 @@ class PilotControls(QHBoxLayout): pilot.player = checked self.player_toggled.emit() + self.pilots_changed.emit() + def on_pilot_changed(self, index: int) -> None: pilot = self.selector.itemData(index) self.player_checkbox.blockSignals(True) @@ -143,6 +150,10 @@ class PilotControls(QHBoxLayout): if self.roster is not None: self.player_checkbox.setEnabled(self.roster.squadron.aircraft.flyable) self.player_checkbox.blockSignals(False) + # on_pilot_changed should emit pilots_changed in its finally block, + # otherwise the start-type isn't updated if you have a single client + # pilot which you switch to a non-client pilot + self.pilots_changed.emit() def update_available_pilots(self) -> None: self.selector.rebuild() @@ -174,9 +185,12 @@ class PilotControls(QHBoxLayout): class FlightRosterEditor(QVBoxLayout): MAX_PILOTS = 4 + pilots_changed = Signal() def __init__( - self, squadron: Optional[Squadron], roster: Optional[IFlightRoster] + self, + squadron: Optional[Squadron], + roster: Optional[IFlightRoster], ) -> None: super().__init__() self.roster = roster @@ -190,7 +204,7 @@ class FlightRosterEditor(QVBoxLayout): return callback - controls = PilotControls(squadron, roster, pilot_idx) + controls = PilotControls(squadron, roster, pilot_idx, self.pilots_changed) controls.selector.available_pilots_changed.connect( make_reset_callback(pilot_idx) ) @@ -226,7 +240,12 @@ class FlightRosterEditor(QVBoxLayout): class QFlightSlotEditor(QGroupBox): flight_resized = Signal(int) - def __init__(self, package_model: PackageModel, flight: Flight, game: Game): + def __init__( + self, + package_model: PackageModel, + flight: Flight, + game: Game, + ): super().__init__("Slots") self.package_model = package_model self.flight = flight diff --git a/qt_ui/windows/mission/flight/settings/QFlightStartType.py b/qt_ui/windows/mission/flight/settings/QFlightStartType.py index 82c73cf0..7f8351cd 100644 --- a/qt_ui/windows/mission/flight/settings/QFlightStartType.py +++ b/qt_ui/windows/mission/flight/settings/QFlightStartType.py @@ -43,6 +43,25 @@ class QFlightStartType(QGroupBox): ) self.setLayout(self.layout) + def on_pilot_selected(self): + # Pilot selection detected. If this is a player flight, set start_type + # as configured for players in the settings. + # Otherwise, set the start_type as configured for AI. + # https://github.com/dcs-liberation/dcs_liberation/issues/1567 + + if self.flight.roster.player_count > 0: + self.flight.start_type = ( + self.flight.coalition.game.settings.default_start_type_client + ) + else: + self.flight.start_type = ( + self.flight.coalition.game.settings.default_start_type + ) + + self.start_type.setCurrentText(self.flight.start_type.value) + + self.package_model.update_tot() + def _on_start_type_selected(self): selected = self.start_type.currentData() self.flight.start_type = selected diff --git a/qt_ui/windows/mission/flight/settings/QGeneralFlightSettingsTab.py b/qt_ui/windows/mission/flight/settings/QGeneralFlightSettingsTab.py index 5d2484fa..a04ab9ab 100644 --- a/qt_ui/windows/mission/flight/settings/QGeneralFlightSettingsTab.py +++ b/qt_ui/windows/mission/flight/settings/QGeneralFlightSettingsTab.py @@ -38,6 +38,17 @@ class QGeneralFlightSettingsTab(QFrame): self.flight_slot_editor.flight_resized.connect(self.flight_size_changed) for pc in self.flight_slot_editor.roster_editor.pilot_controls: pc.player_toggled.connect(self.on_player_toggle) + pc.player_toggled.connect( + self.flight_slot_editor.roster_editor.pilots_changed + ) + + start_type = QFlightStartType( + package_model, + flight, + ) + + roster = self.flight_slot_editor.roster_editor + roster.pilots_changed.connect(start_type.on_pilot_selected) widgets = [ QFlightTypeTaskInfo(flight), @@ -46,7 +57,7 @@ class QGeneralFlightSettingsTab(QFrame): game.game, package_model, flight, flight_wpt_list ), self.flight_slot_editor, - QFlightStartType(package_model, flight), + start_type, QFlightCustomName(flight), ] layout = QGridLayout()