diff --git a/changelog.md b/changelog.md index 46b4e756..031ba808 100644 --- a/changelog.md +++ b/changelog.md @@ -32,7 +32,9 @@ * **[Mission Generation]** Ability to choose whether player flights can spawn on the sixpack or not * **[Options]** New options in Mission Generator section: Limit AI radio callouts & Suppress AI radio callouts. * **[Options]** New option to use the combat landing flag in the landing waypoint task for helicopters. -* **[UI/UX]** Sync package waypoints when primary flight's waypoints are updated and recreate other flights within the package to ensure JOIN, INGRESS & SPLIT are synced +* **[UI/UX]** Sync package waypoints when primary flight's waypoints are updated and recreate other flights within the package to ensure JOIN, INGRESS & SPLIT are synced +* **[UI/UX]** Allow changing loadout on flight creation +* **[UI]** Display TOT for all waypoints in the flight plan ## Fixes * **[UI/UX]** A-10A flights can be edited again diff --git a/qt_ui/windows/mission/flight/QFlightCreator.py b/qt_ui/windows/mission/flight/QFlightCreator.py index 6143c868..01a794c8 100644 --- a/qt_ui/windows/mission/flight/QFlightCreator.py +++ b/qt_ui/windows/mission/flight/QFlightCreator.py @@ -1,6 +1,6 @@ from typing import Optional, Type -from PySide6.QtCore import Qt, Signal +from PySide6.QtCore import Qt, Signal, QEvent from PySide6.QtWidgets import ( QComboBox, QDialog, @@ -10,12 +10,15 @@ from PySide6.QtWidgets import ( QVBoxLayout, QLineEdit, QHBoxLayout, + QStyledItemDelegate, + QToolTip, ) from dcs.unittype import FlyingType from game import Game from game.ato.flight import Flight from game.ato.flightroster import FlightRoster +from game.ato.loadouts import Loadout from game.ato.package import Package from game.ato.starttype import StartType from game.squadrons.squadron import Squadron @@ -85,6 +88,15 @@ class QFlightCreator(QDialog): self.update_max_size(self.squadron_selector.aircraft_available) layout.addLayout(QLabeledWidget("Size:", self.flight_size_spinner)) + hbox = QHBoxLayout() + self.loadout_selector = QComboBox() + self.loadout_selector.setMaximumWidth(250) + self.loadout_selector.setItemDelegate(LoadoutDelegate(self.loadout_selector)) + self._init_loadout_selector() + hbox.addWidget(QLabel("Loadout:")) + hbox.addWidget(self.loadout_selector) + layout.addLayout(hbox) + required_start_type = None squadron = self.squadron_selector.currentData() if squadron is None: @@ -206,6 +218,7 @@ class QFlightCreator(QDialog): member.assign_tgp_laser_code( self.game.laser_code_registry.alloc_laser_code() ) + member.loadout = self.current_loadout() # noinspection PyUnresolvedReferences self.created.emit(flight) @@ -217,8 +230,9 @@ class QFlightCreator(QDialog): self.task_selector.currentData(), new_aircraft ) self.divert.change_aircraft(new_aircraft) - self.roster_editor.pilots_changed.emit() + if self.aircraft_selector.currentData() is not None: + self._init_loadout_selector() def on_departure_changed(self, departure: ControlPoint) -> None: if isinstance(departure, OffMapSpawn): @@ -290,3 +304,38 @@ class QFlightCreator(QDialog): start_type = self.game.settings.default_start_type self.start_type.setCurrentText(start_type.value) + + def current_loadout(self) -> Loadout: + loadout = self.loadout_selector.currentData() + if loadout is None: + return Loadout.empty_loadout() + return loadout + + def _init_loadout_selector(self): + self.loadout_selector.clear() + ac_type = self.aircraft_selector.currentData() + if ac_type is None: + return + for loadout in Loadout.iter_for_aircraft(ac_type): + self.loadout_selector.addItem(loadout.name, loadout) + for loadout in Loadout.default_loadout_names_for( + self.task_selector.currentData() + ): + index = self.loadout_selector.findText(loadout) + if index != -1: + self.loadout_selector.setCurrentIndex(index) + break + + +class LoadoutDelegate(QStyledItemDelegate): + def helpEvent(self, event, view, option, index): + if event.type() == QEvent.ToolTip: + loadout = index.data(Qt.UserRole) + if loadout: + max_pylon = max(loadout.pylons.keys(), default=0) + pylons_info = "\n".join( + f"Pylon {pylon}: {loadout.pylons.get(pylon, 'Clean')}" + for pylon in range(1, max_pylon + 1) + ) + QToolTip.showText(event.globalPos(), pylons_info, view) + return True diff --git a/qt_ui/windows/mission/flight/payload/QLoadoutEditor.py b/qt_ui/windows/mission/flight/payload/QLoadoutEditor.py index ad44cf1f..c9913d16 100644 --- a/qt_ui/windows/mission/flight/payload/QLoadoutEditor.py +++ b/qt_ui/windows/mission/flight/payload/QLoadoutEditor.py @@ -105,8 +105,7 @@ class QLoadoutEditor(QGroupBox): ac_id = ac_type.id payloads_folder = payloads_dir() payload_file = payloads_folder / f"{ac_id}.lua" - if not payloads_folder.exists(): - payloads_folder.mkdir() + payloads_folder.mkdir(parents=True, exist_ok=True) ac_type.payloads[payload_name] = DcsPayload.from_flight_member( self.flight_member, payload_name ).to_dict() diff --git a/qt_ui/windows/mission/flight/waypoints/QFlightWaypointList.py b/qt_ui/windows/mission/flight/waypoints/QFlightWaypointList.py index 9d544769..a3e2bb76 100644 --- a/qt_ui/windows/mission/flight/waypoints/QFlightWaypointList.py +++ b/qt_ui/windows/mission/flight/waypoints/QFlightWaypointList.py @@ -1,3 +1,5 @@ +from typing import Optional + from PySide6.QtCore import QItemSelectionModel, QPoint, QModelIndex from PySide6.QtGui import QStandardItem, QStandardItemModel from PySide6.QtWidgets import ( @@ -32,6 +34,7 @@ class AltitudeEditorDelegate(QStyledItemDelegate): class QFlightWaypointList(QTableView): def __init__(self, package: Package, flight: Flight): super().__init__() + self._last_waypoint: Optional[FlightWaypoint] = None self.package = package self.flight = flight @@ -83,7 +86,10 @@ class QFlightWaypointList(QTableView): self.update(self.currentIndex()) def _add_waypoint_row( - self, row: int, flight: Flight, waypoint: FlightWaypoint + self, + row: int, + flight: Flight, + waypoint: FlightWaypoint, ) -> None: self.model.insertRow(self.model.rowCount()) @@ -112,18 +118,36 @@ class QFlightWaypointList(QTableView): name = self.model.item(i, 0).text() self.flight.flight_plan.waypoints[i].pretty_name = name - def tot_text(self, flight: Flight, waypoint: FlightWaypoint) -> str: + def tot_text( + self, + flight: Flight, + waypoint: FlightWaypoint, + ) -> str: if waypoint.waypoint_type == FlightWaypointType.TAKEOFF: + self.update_last_tot(flight.flight_plan.takeoff_time()) + self._last_waypoint = waypoint return self.takeoff_text(flight) prefix = "" time = flight.flight_plan.tot_for_waypoint(waypoint) if time is None: prefix = "Depart " time = flight.flight_plan.depart_time_for_waypoint(waypoint) - if time is None: + if time is None and self._last_waypoint is not None: + prefix = "" + timedelta = flight.flight_plan.travel_time_between_waypoints( + self._last_waypoint, waypoint + ) + time = self._last_tot + timedelta + else: return "" + self.update_last_tot(time) + self._last_waypoint = waypoint return f"{prefix}{time:%H:%M:%S}" @staticmethod def takeoff_text(flight: Flight) -> str: return f"{flight.flight_plan.takeoff_time():%H:%M:%S}" + + def update_last_tot(self, time) -> None: + if time is not None: + self._last_tot = time diff --git a/qt_ui/windows/newgame/WizardPages/QFactionSelection.py b/qt_ui/windows/newgame/WizardPages/QFactionSelection.py index 1eca68f8..3209991c 100644 --- a/qt_ui/windows/newgame/WizardPages/QFactionSelection.py +++ b/qt_ui/windows/newgame/WizardPages/QFactionSelection.py @@ -35,6 +35,7 @@ class QFactionUnits(QScrollArea): self.parent = parent self.faction = faction self._create_checkboxes(show_jtac) + self.show_jtac = show_jtac def _add_checkboxes( self, @@ -214,7 +215,7 @@ class QFactionUnits(QScrollArea): self.faction = faction self.content = QWidget() self.setWidget(self.content) - self._create_checkboxes() + self._create_checkboxes(self.show_jtac) self.update() if self.parent: self.parent.update() diff --git a/resources/customized_payloads/AV8BNA.lua b/resources/customized_payloads/AV8BNA.lua index 87dfb406..3f68fb5a 100644 --- a/resources/customized_payloads/AV8BNA.lua +++ b/resources/customized_payloads/AV8BNA.lua @@ -2,28 +2,29 @@ local unitPayloads = { ["name"] = "AV8BNA", ["payloads"] = { [1] = { - ["name"] = "STRIKE", + ["displayName"] = "Retribution OCA/Runway", + ["name"] = "Retribution OCA/Runway", ["pylons"] = { [1] = { - ["CLSID"] = "{A111396E-D3E8-4b9c-8AC9-2432489304D5}", - ["num"] = 5, - }, - [2] = { ["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}", ["num"] = 8, }, - [3] = { + [2] = { ["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}", ["num"] = 1, }, - [4] = { + [3] = { ["CLSID"] = "{GBU_32_V_2B}", ["num"] = 7, }, - [5] = { + [4] = { ["CLSID"] = "{GBU_32_V_2B}", ["num"] = 6, }, + [5] = { + ["CLSID"] = "{A111396E-D3E8-4b9c-8AC9-2432489304D5}", + ["num"] = 5, + }, [6] = { ["CLSID"] = "{GBU_32_V_2B}", ["num"] = 3, @@ -38,26 +39,27 @@ local unitPayloads = { }, }, [2] = { - ["name"] = "SEAD", + ["displayName"] = "Retribution SEAD Escort", + ["name"] = "Retribution SEAD Escort", ["pylons"] = { [1] = { - ["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}", + ["CLSID"] = "{AGM_122_SIDEARM}", ["num"] = 8, }, [2] = { - ["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}", + ["CLSID"] = "{AGM_122_SIDEARM}", ["num"] = 1, }, [3] = { - ["CLSID"] = "{LAU_7_AGM_122_SIDEARM}", + ["CLSID"] = "LAU_117_AGM_65F", ["num"] = 2, }, [4] = { - ["CLSID"] = "{LAU_7_AGM_122_SIDEARM}", + ["CLSID"] = "LAU_117_AGM_65F", ["num"] = 7, }, [5] = { - ["CLSID"] = "{ALQ_164_RF_Jammer}", + ["CLSID"] = "{A111396E-D3E8-4b9c-8AC9-2432489304D5}", ["num"] = 5, }, [6] = { @@ -74,39 +76,36 @@ local unitPayloads = { }, }, [3] = { - ["name"] = "CAS", + ["displayName"] = "Retribution DEAD", + ["name"] = "Retribution DEAD", ["pylons"] = { [1] = { - ["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}", + ["CLSID"] = "{AGM_122_SIDEARM}", ["num"] = 8, }, [2] = { - ["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}", + ["CLSID"] = "{AGM_122_SIDEARM}", ["num"] = 1, }, [3] = { ["CLSID"] = "LAU_117_AGM_65F", - ["num"] = 2, + ["num"] = 6, }, [4] = { ["CLSID"] = "LAU_117_AGM_65F", - ["num"] = 7, + ["num"] = 3, }, [5] = { - ["CLSID"] = "{GAU_12_Equalizer}", - ["num"] = 4, - }, - [6] = { ["CLSID"] = "{A111396E-D3E8-4b9c-8AC9-2432489304D5}", ["num"] = 5, }, - [7] = { - ["CLSID"] = "{LAU-131 - 7 AGR-20 M282}", - ["num"] = 6, + [6] = { + ["CLSID"] = "LAU_117_AGM_65F", + ["num"] = 7, }, - [8] = { - ["CLSID"] = "{LAU-131 - 7 AGR-20 M282}", - ["num"] = 3, + [7] = { + ["CLSID"] = "LAU_117_AGM_65F", + ["num"] = 2, }, }, ["tasks"] = { @@ -114,27 +113,36 @@ local unitPayloads = { }, }, [4] = { - ["name"] = "CAP", + ["displayName"] = "Retribution BAI", + ["name"] = "Retribution BAI", ["pylons"] = { [1] = { - ["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}", + ["CLSID"] = "{AGM_122_SIDEARM}", ["num"] = 8, }, [2] = { - ["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}", + ["CLSID"] = "{AGM_122_SIDEARM}", ["num"] = 1, }, [3] = { - ["CLSID"] = "{AIM-9M-ON-ADAPTER}", - ["num"] = 2, - }, - [4] = { - ["CLSID"] = "{AIM-9M-ON-ADAPTER}", + ["CLSID"] = "{BRU-70A_3*GBU-54}", ["num"] = 7, }, + [4] = { + ["CLSID"] = "{BRU-70A_2*GBU-54_RIGHT}", + ["num"] = 6, + }, [5] = { - ["CLSID"] = "{GAU_12_Equalizer}", - ["num"] = 4, + ["CLSID"] = "{BRU-70A_2*GBU-54_LEFT}", + ["num"] = 3, + }, + [6] = { + ["CLSID"] = "{BRU-70A_3*GBU-54}", + ["num"] = 2, + }, + [7] = { + ["CLSID"] = "{A111396E-D3E8-4b9c-8AC9-2432489304D5}", + ["num"] = 5, }, }, ["tasks"] = { @@ -142,7 +150,8 @@ local unitPayloads = { }, }, [5] = { - ["name"] = "INTERCEPT", + ["displayName"] = "Retribution OCA/Aircraft", + ["name"] = "Retribution OCA/Aircraft", ["pylons"] = { [1] = { ["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}", @@ -153,16 +162,24 @@ local unitPayloads = { ["num"] = 1, }, [3] = { - ["CLSID"] = "{AIM-9M-ON-ADAPTER}", - ["num"] = 2, + ["CLSID"] = "{A111396E-D3E8-4b9c-8AC9-2432489304D5}", + ["num"] = 5, }, [4] = { - ["CLSID"] = "{AIM-9M-ON-ADAPTER}", + ["CLSID"] = "{GBU_32_V_2B}", ["num"] = 7, }, [5] = { - ["CLSID"] = "{GAU_12_Equalizer}", - ["num"] = 4, + ["CLSID"] = "{GBU_32_V_2B}", + ["num"] = 6, + }, + [6] = { + ["CLSID"] = "{GBU_32_V_2B}", + ["num"] = 3, + }, + [7] = { + ["CLSID"] = "{GBU_32_V_2B}", + ["num"] = 2, }, }, ["tasks"] = { @@ -170,14 +187,52 @@ local unitPayloads = { }, }, [6] = { - ["name"] = "ANTISHIP", + ["displayName"] = "Retribution CAS", + ["name"] = "Retribution CAS", ["pylons"] = { [1] = { - ["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}", + ["CLSID"] = "{A111396E-D3E8-4b9c-8AC9-2432489304D5}", + ["num"] = 5, + }, + [2] = { + ["CLSID"] = "LAU_117_AGM_65F", + ["num"] = 3, + }, + [3] = { + ["CLSID"] = "LAU_117_AGM_65F", + ["num"] = 6, + }, + [4] = { + ["CLSID"] = "{BRU-70A_2*GBU-54_LEFT}", + ["num"] = 7, + }, + [5] = { + ["CLSID"] = "{AGM_122_SIDEARM}", + ["num"] = 8, + }, + [6] = { + ["CLSID"] = "{AGM_122_SIDEARM}", + ["num"] = 1, + }, + [7] = { + ["CLSID"] = "{BRU-70A_2*GBU-54_RIGHT}", + ["num"] = 2, + }, + }, + ["tasks"] = { + [1] = 31, + }, + }, + [7] = { + ["displayName"] = "Retribution SEAD Sweep", + ["name"] = "Retribution SEAD Sweep", + ["pylons"] = { + [1] = { + ["CLSID"] = "{AGM_122_SIDEARM}", ["num"] = 8, }, [2] = { - ["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}", + ["CLSID"] = "{AGM_122_SIDEARM}", ["num"] = 1, }, [3] = { @@ -205,45 +260,107 @@ local unitPayloads = { [1] = 31, }, }, - [7] = { - ["displayName"] = "Retribution OCA/Runway", - ["name"] = "Retribution OCA/Runway", + [8] = { + ["displayName"] = "Retribution Strike", + ["name"] = "Retribution Strike", + ["pylons"] = { + [1] = { + ["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}", + ["num"] = 8, + }, + [2] = { + ["CLSID"] = "{A111396E-D3E8-4b9c-8AC9-2432489304D5}", + ["num"] = 5, + }, + [3] = { + ["CLSID"] = "{GBU_32_V_2B}", + ["num"] = 3, + }, + [4] = { + ["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}", + ["num"] = 1, + }, + [5] = { + ["CLSID"] = "{GBU_32_V_2B}", + ["num"] = 6, + }, + [6] = { + ["CLSID"] = "{GBU_32_V_2B}", + ["num"] = 7, + }, + [7] = { + ["CLSID"] = "{GBU_32_V_2B}", + ["num"] = 2, + }, + }, + ["tasks"] = { + [1] = 31, + }, + }, + [9] = { + ["displayName"] = "Retribution Anti-ship", + ["name"] = "Retribution Anti-ship", + ["pylons"] = { + [1] = { + ["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}", + ["num"] = 8, + }, + [2] = { + ["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}", + ["num"] = 1, + }, + [3] = { + ["CLSID"] = "{F16A4DE0-116C-4A71-97F0-2CF85B0313EC}", + ["num"] = 7, + }, + [4] = { + ["CLSID"] = "{F16A4DE0-116C-4A71-97F0-2CF85B0313EC}", + ["num"] = 6, + }, + [5] = { + ["CLSID"] = "{A111396E-D3E8-4b9c-8AC9-2432489304D5}", + ["num"] = 5, + }, + [6] = { + ["CLSID"] = "{F16A4DE0-116C-4A71-97F0-2CF85B0313EC}", + ["num"] = 3, + }, + [7] = { + ["CLSID"] = "{F16A4DE0-116C-4A71-97F0-2CF85B0313EC}", + ["num"] = 2, + }, + }, + ["tasks"] = { + [1] = 31, + }, + }, + [10] = { + ["displayName"] = "Retribution SEAD", + ["name"] = "Retribution SEAD", ["pylons"] = { [1] = { ["CLSID"] = "{AGM_122_SIDEARM}", ["num"] = 8, }, [2] = { - ["CLSID"] = "{7A44FF09-527C-4B7E-B42B-3F111CFE50FB}", - ["num"] = 7, + ["CLSID"] = "{AGM_122_SIDEARM}", + ["num"] = 1, }, [3] = { - ["CLSID"] = "{BRU-42_2*Mk-83_RIGHT}", - ["num"] = 6, + ["CLSID"] = "{LAU_7_AGM_122_SIDEARM}", + ["num"] = 2, }, [4] = { + ["CLSID"] = "{LAU_7_AGM_122_SIDEARM}", + ["num"] = 7, + }, + [5] = { ["CLSID"] = "{ALQ_164_RF_Jammer}", ["num"] = 5, }, - [5] = { - ["CLSID"] = "{GAU_12_Equalizer}", - ["num"] = 4, - }, - [6] = { - ["CLSID"] = "{BRU-42_2*Mk-83_LEFT}", - ["num"] = 3, - }, - [7] = { - ["CLSID"] = "{7A44FF09-527C-4B7E-B42B-3F111CFE50FB}", - ["num"] = 2, - }, - [8] = { - ["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}", - ["num"] = 1, - }, }, ["tasks"] = { - [1] = 34, + [1] = 31, }, }, },