diff --git a/changelog.md b/changelog.md index 5dbf3d54..9e34c673 100644 --- a/changelog.md +++ b/changelog.md @@ -17,6 +17,7 @@ * **[ATO]** Allow planning as OPFOR * **[Campaign Design]** Support for Kola map by Orbx * **[UI]** Zoom level retained when switching campaigns +* **[UX]** Allow changing squadrons in flight's edit dialog ## Fixes * **[UI/UX]** A-10A flights can be edited again diff --git a/qt_ui/windows/mission/QEditFlightDialog.py b/qt_ui/windows/mission/QEditFlightDialog.py index ab191a85..806ce2aa 100644 --- a/qt_ui/windows/mission/QEditFlightDialog.py +++ b/qt_ui/windows/mission/QEditFlightDialog.py @@ -26,6 +26,8 @@ class QEditFlightDialog(QDialog): self.game_model = game_model self.flight = flight + self.package_model = package_model + self.events = GameUpdateEvents() self.setWindowTitle("Edit flight") self.setWindowIcon(EVENT_ICONS["strike"]) @@ -34,11 +36,24 @@ class QEditFlightDialog(QDialog): layout = QVBoxLayout() self.flight_planner = QFlightPlanner(package_model, flight, game_model) + self.flight_planner.squadron_changed.connect(self.on_squadron_change) layout.addWidget(self.flight_planner) self.setLayout(layout) self.finished.connect(self.on_close) - def on_close(self, _result) -> None: - EventStream.put_nowait(GameUpdateEvents().update_flight(self.flight)) + def on_squadron_change(self, flight: Flight): + self.events = GameUpdateEvents().delete_flight(self.flight) + self.events = self.events.new_flight(flight) + self.game_model.ato_model.client_slots_changed.emit() + self.flight = flight + self.reject() + new_dialog = QEditFlightDialog( + self.game_model, self.package_model, flight, self.parent() + ) + new_dialog.show() + + def on_close(self, _result) -> None: + self.events = self.events.update_flight(self.flight) + EventStream.put_nowait(self.events) self.game_model.ato_model.client_slots_changed.emit() diff --git a/qt_ui/windows/mission/flight/QFlightPlanner.py b/qt_ui/windows/mission/flight/QFlightPlanner.py index 362c41e7..d04fe412 100644 --- a/qt_ui/windows/mission/flight/QFlightPlanner.py +++ b/qt_ui/windows/mission/flight/QFlightPlanner.py @@ -1,3 +1,4 @@ +from PySide6.QtCore import Signal from PySide6.QtWidgets import QTabWidget from game.ato.flight import Flight @@ -10,6 +11,8 @@ from qt_ui.windows.mission.flight.waypoints.QFlightWaypointTab import QFlightWay class QFlightPlanner(QTabWidget): + squadron_changed = Signal(Flight) + def __init__(self, package_model: PackageModel, flight: Flight, gm: GameModel): super().__init__() @@ -28,6 +31,7 @@ class QFlightPlanner(QTabWidget): self.general_settings_tab.flight_size_changed.connect( self.payload_tab.resize_for_flight ) + self.general_settings_tab.squadron_changed.connect(self.squadron_changed) self.addTab(self.general_settings_tab, "General Flight settings") self.addTab(self.payload_tab, "Payload") self.addTab(self.waypoint_tab, "Waypoints") diff --git a/qt_ui/windows/mission/flight/settings/QFlightSlotEditor.py b/qt_ui/windows/mission/flight/settings/QFlightSlotEditor.py index 4a153780..827d01b7 100644 --- a/qt_ui/windows/mission/flight/settings/QFlightSlotEditor.py +++ b/qt_ui/windows/mission/flight/settings/QFlightSlotEditor.py @@ -11,14 +11,21 @@ from PySide6.QtWidgets import ( QHBoxLayout, QCheckBox, QVBoxLayout, + QPushButton, + QDialog, + QWidget, ) from game import Game +from game.ato.closestairfields import ClosestAirfields from game.ato.flight import Flight from game.ato.flightroster import FlightRoster from game.ato.iflightroster import IFlightRoster +from game.dcs.aircrafttype import AircraftType from game.squadrons import Squadron from game.squadrons.pilot import Pilot +from game.theater import ControlPoint, OffMapSpawn +from game.utils import nautical_miles from qt_ui.models import PackageModel @@ -237,8 +244,49 @@ class FlightRosterEditor(QVBoxLayout): controls.replace(squadron, new_roster) +class QSquadronSelector(QDialog): + def __init__(self, flight: Flight, parent: Optional[QWidget] = None): + super().__init__(parent) + self.flight = flight + self.parent = parent + self.init() + + def init(self): + vbox = QVBoxLayout() + self.setLayout(vbox) + + self.selector = QComboBox() + air_wing = self.flight.coalition.air_wing + for squadron in air_wing.best_squadrons_for( + self.flight.package.target, + self.flight.flight_type, + self.flight.roster.max_size, + self.flight.is_helo, + True, + ignore_range=True, + ): + if squadron is self.flight.squadron: + continue + self.selector.addItem( + f"{squadron.name} - {squadron.aircraft.variant_id}", squadron + ) + + vbox.addWidget(self.selector) + + hbox = QHBoxLayout() + accept = QPushButton("Accept") + accept.clicked.connect(self.accept) + hbox.addWidget(accept) + cancel = QPushButton("Cancel") + cancel.clicked.connect(self.reject) + hbox.addWidget(cancel) + + vbox.addLayout(hbox) + + class QFlightSlotEditor(QGroupBox): flight_resized = Signal(int) + squadron_changed = Signal(Flight) def __init__( self, @@ -250,6 +298,10 @@ class QFlightSlotEditor(QGroupBox): self.package_model = package_model self.flight = flight self.game = game + self.closest_airfields = ClosestAirfields( + flight.package.target, + list(game.theater.control_points_for(self.flight.coalition.player)), + ) available = self.flight.squadron.untasked_aircraft max_count = self.flight.count + available if max_count > 4: @@ -268,7 +320,12 @@ class QFlightSlotEditor(QGroupBox): layout.addWidget(self.aircraft_count_spinner, 0, 1) layout.addWidget(QLabel("Squadron:"), 1, 0) - layout.addWidget(QLabel(str(self.flight.squadron)), 1, 1) + hbox = QHBoxLayout() + hbox.addWidget(QLabel(str(self.flight.squadron))) + squadron_btn = QPushButton("Change Squadron") + squadron_btn.clicked.connect(self._change_squadron) + hbox.addWidget(squadron_btn) + layout.addLayout(hbox, 1, 1) layout.addWidget(QLabel("Assigned pilots:"), 2, 0) self.roster_editor = FlightRosterEditor(flight.squadron, flight.roster) @@ -276,6 +333,46 @@ class QFlightSlotEditor(QGroupBox): self.setLayout(layout) + def _change_squadron(self): + dialog = QSquadronSelector(self.flight) + if dialog.exec(): + squadron: Optional[Squadron] = dialog.selector.currentData() + if not squadron: + return + flight = Flight( + self.package_model.package, + squadron, + self.flight.count, + self.flight.flight_type, + self.flight.start_type, + self._find_divert_field(squadron.aircraft, squadron.location), + frequency=self.flight.frequency, + cargo=self.flight.cargo, + channel=self.flight.tacan, + callsign_tcn=self.flight.tcn_name, + ) + self.package_model.add_flight(flight) + self.package_model.delete_flight(self.flight) + self.squadron_changed.emit(flight) + + def _find_divert_field( + self, aircraft: AircraftType, arrival: ControlPoint + ) -> Optional[ControlPoint]: + divert_limit = nautical_miles(150) + for airfield in self.closest_airfields.operational_airfields_within( + divert_limit + ): + if airfield.captured != self.flight.coalition.player: + continue + if airfield == arrival: + continue + if not airfield.can_operate(aircraft): + continue + if isinstance(airfield, OffMapSpawn): + continue + return airfield + return None + def _changed_aircraft_count(self): old_count = self.flight.count new_count = int(self.aircraft_count_spinner.value()) diff --git a/qt_ui/windows/mission/flight/settings/QGeneralFlightSettingsTab.py b/qt_ui/windows/mission/flight/settings/QGeneralFlightSettingsTab.py index a04ab9ab..7b9a1d3d 100644 --- a/qt_ui/windows/mission/flight/settings/QGeneralFlightSettingsTab.py +++ b/qt_ui/windows/mission/flight/settings/QGeneralFlightSettingsTab.py @@ -21,6 +21,7 @@ from qt_ui.windows.mission.flight.waypoints.QFlightWaypointList import ( class QGeneralFlightSettingsTab(QFrame): flight_size_changed = Signal() + squadron_changed = Signal(Flight) def __init__( self, @@ -36,6 +37,7 @@ class QGeneralFlightSettingsTab(QFrame): self.flight_slot_editor = QFlightSlotEditor(package_model, flight, game.game) self.flight_slot_editor.flight_resized.connect(self.flight_size_changed) + self.flight_slot_editor.squadron_changed.connect(self.squadron_changed) for pc in self.flight_slot_editor.roster_editor.pilot_controls: pc.player_toggled.connect(self.on_player_toggle) pc.player_toggled.connect(