diff --git a/changelog.md b/changelog.md
index 2116f320..cadbc6c4 100644
--- a/changelog.md
+++ b/changelog.md
@@ -29,6 +29,7 @@ Saves from 2.3 are not compatible with 2.4.
* **[UI]** Aircraft for a new flight are now only selectable if they match the task type for that flight.
* **[UI]** WIP - There is now a unit info button for each unit in the recruitment list, that should help newer players learn what each unit does.
* **[UI]** Docs for time-on-target and creating new theaters/factions/loadouts are now linked in the UI at the appropriate places.
+* **[UI]** ASAP is now a checkbox rather than a button. Enabling this will disable the TOT selector but changes to the package structure will automatically re-ASAP the package.
* **[Factions]** Added option for date-based loadout restriction. Active radar homing missiles are handled, patches welcome for the other thousand weapons.
* **[Factions]** Added Poland 2010 faction.
* **[Factions]** Added Greece 2005 faction.
diff --git a/gen/ato.py b/gen/ato.py
index fd7da75f..99a76789 100644
--- a/gen/ato.py
+++ b/gen/ato.py
@@ -20,6 +20,7 @@ from game.theater.missiontarget import MissionTarget
from game.utils import Speed
from .flights.flight import Flight, FlightType
from .flights.flightplan import FormationFlightPlan
+from .flights.traveltime import TotEstimator
@dataclass(frozen=True)
@@ -54,6 +55,11 @@ class Package:
delay: int = field(default=0)
+ #: True if the package ToT should be reset to ASAP whenever the player makes
+ #: a change. This is really a UI property rather than a game property, but
+ #: we want it to persist in the save.
+ auto_asap: bool = field(default=False)
+
#: Desired TOT as an offset from mission start.
time_over_target: timedelta = field(default=timedelta())
@@ -127,6 +133,9 @@ class Package:
return max(times)
return None
+ def set_tot_asap(self) -> None:
+ self.time_over_target = TotEstimator(self).earliest_tot()
+
def add_flight(self, flight: Flight) -> None:
"""Adds a flight to the package."""
self.flights.append(flight)
diff --git a/qt_ui/dialogs.py b/qt_ui/dialogs.py
index 263bfb62..09fd2b93 100644
--- a/qt_ui/dialogs.py
+++ b/qt_ui/dialogs.py
@@ -60,7 +60,7 @@ class Dialog:
"""Opens the dialog to edit the given flight."""
cls.edit_flight_dialog = QEditFlightDialog(
cls.game_model,
- package_model.package,
+ package_model,
flight,
parent=parent
)
diff --git a/qt_ui/models.py b/qt_ui/models.py
index bd0d6397..7155b3e0 100644
--- a/qt_ui/models.py
+++ b/qt_ui/models.py
@@ -100,6 +100,8 @@ class PackageModel(QAbstractListModel):
#: Emitted when this package is being deleted from the ATO.
deleted = Signal()
+ tot_changed = Signal()
+
def __init__(self, package: Package) -> None:
super().__init__()
self.package = package
@@ -139,6 +141,8 @@ class PackageModel(QAbstractListModel):
"""Adds the given flight to the package."""
self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
self.package.add_flight(flight)
+ # update_tot is not called here because the new flight does not have a
+ # flight plan yet. Will be called manually by the caller.
self.endInsertRows()
def delete_flight_at_index(self, index: QModelIndex) -> None:
@@ -155,14 +159,24 @@ class PackageModel(QAbstractListModel):
self.beginRemoveRows(QModelIndex(), index, index)
self.package.remove_flight(flight)
self.endRemoveRows()
+ self.update_tot()
def flight_at_index(self, index: QModelIndex) -> Flight:
"""Returns the flight located at the given index."""
return self.package.flights[index.row()]
- def update_tot(self, tot: datetime.timedelta) -> None:
+ def set_tot(self, tot: datetime.timedelta) -> None:
self.package.time_over_target = tot
- self.layoutChanged.emit()
+ self.update_tot()
+
+ def set_asap(self, asap: bool) -> None:
+ self.package.auto_asap = asap
+ self.update_tot()
+
+ def update_tot(self) -> None:
+ if self.package.auto_asap:
+ self.package.set_tot_asap()
+ self.tot_changed.emit()
@property
def mission_target(self) -> MissionTarget:
diff --git a/qt_ui/windows/mission/QEditFlightDialog.py b/qt_ui/windows/mission/QEditFlightDialog.py
index a3d8cbd2..a6893a47 100644
--- a/qt_ui/windows/mission/QEditFlightDialog.py
+++ b/qt_ui/windows/mission/QEditFlightDialog.py
@@ -4,9 +4,8 @@ from PySide2.QtWidgets import (
QVBoxLayout,
)
-from gen.ato import Package
from gen.flights.flight import Flight
-from qt_ui.models import GameModel
+from qt_ui.models import GameModel, PackageModel
from qt_ui.uiconstants import EVENT_ICONS
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
from qt_ui.windows.mission.flight.QFlightPlanner import QFlightPlanner
@@ -15,7 +14,8 @@ from qt_ui.windows.mission.flight.QFlightPlanner import QFlightPlanner
class QEditFlightDialog(QDialog):
"""Dialog window for editing flight plans and loadouts."""
- def __init__(self, game_model: GameModel, package: Package, flight: Flight, parent=None) -> None:
+ def __init__(self, game_model: GameModel, package_model: PackageModel,
+ flight: Flight, parent=None) -> None:
super().__init__(parent=parent)
self.game_model = game_model
@@ -25,7 +25,8 @@ class QEditFlightDialog(QDialog):
layout = QVBoxLayout()
- self.flight_planner = QFlightPlanner(package, flight, game_model.game)
+ self.flight_planner = QFlightPlanner(package_model, flight,
+ game_model.game)
layout.addWidget(self.flight_planner)
self.setLayout(layout)
diff --git a/qt_ui/windows/mission/QPackageDialog.py b/qt_ui/windows/mission/QPackageDialog.py
index 2e78ce6e..10acc4fe 100644
--- a/qt_ui/windows/mission/QPackageDialog.py
+++ b/qt_ui/windows/mission/QPackageDialog.py
@@ -3,8 +3,9 @@ import logging
from datetime import timedelta
from typing import Optional
-from PySide2.QtCore import QItemSelection, QTime, Signal, Qt
+from PySide2.QtCore import QItemSelection, QTime, Qt, Signal
from PySide2.QtWidgets import (
+ QCheckBox,
QDialog,
QHBoxLayout,
QLabel,
@@ -15,16 +16,15 @@ from PySide2.QtWidgets import (
)
from game.game import Game
+from game.theater.missiontarget import MissionTarget
from gen.ato import Package
from gen.flights.flight import Flight
from gen.flights.flightplan import FlightPlanBuilder, PlanningError
-from gen.flights.traveltime import TotEstimator
from qt_ui.models import AtoModel, GameModel, PackageModel
from qt_ui.uiconstants import EVENT_ICONS
from qt_ui.widgets.ato import QFlightList
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
from qt_ui.windows.mission.flight.QFlightCreator import QFlightCreator
-from game.theater.missiontarget import MissionTarget
class QPackageDialog(QDialog):
@@ -79,15 +79,17 @@ class QPackageDialog(QDialog):
self.tot_spinner.setDisplayFormat("T+hh:mm:ss")
self.tot_spinner.timeChanged.connect(self.save_tot)
self.tot_spinner.setToolTip("Package TOT relative to mission TOT")
+ self.tot_spinner.setEnabled(not self.package_model.package.auto_asap)
self.tot_column.addWidget(self.tot_spinner)
- self.reset_tot_button = QPushButton("ASAP")
- self.reset_tot_button.setToolTip(
+ self.auto_asap = QCheckBox("ASAP")
+ self.auto_asap.setToolTip(
"Sets the package TOT to the earliest time that all flights can "
"arrive at the target."
)
- self.reset_tot_button.clicked.connect(self.reset_tot)
- self.tot_column.addWidget(self.reset_tot_button)
+ self.auto_asap.setChecked(self.package_model.package.auto_asap)
+ self.auto_asap.toggled.connect(self.set_asap)
+ self.tot_column.addWidget(self.auto_asap)
self.tot_help_label = QLabel("Help")
self.tot_help_label.setAlignment(Qt.AlignCenter)
@@ -113,6 +115,8 @@ class QPackageDialog(QDialog):
self.delete_flight_button.setEnabled(model.rowCount() > 0)
self.button_layout.addWidget(self.delete_flight_button)
+ self.package_model.tot_changed.connect(self.update_tot)
+
self.button_layout.addStretch()
self.setLayout(self.layout)
@@ -145,14 +149,14 @@ class QPackageDialog(QDialog):
def save_tot(self) -> None:
time = self.tot_spinner.time()
seconds = time.hour() * 3600 + time.minute() * 60 + time.second()
- self.package_model.update_tot(timedelta(seconds=seconds))
+ self.package_model.set_tot(timedelta(seconds=seconds))
- def reset_tot(self) -> None:
- if not list(self.package_model.flights):
- self.package_model.update_tot(timedelta())
- else:
- self.package_model.update_tot(
- TotEstimator(self.package_model.package).earliest_tot())
+ def set_asap(self, checked: bool) -> None:
+ self.package_model.set_asap(checked)
+ self.tot_spinner.setEnabled(not self.package_model.package.auto_asap)
+ self.update_tot()
+
+ def update_tot(self) -> None:
self.tot_spinner.setTime(self.tot_qtime())
def on_selection_changed(self, selected: QItemSelection,
@@ -183,6 +187,7 @@ class QPackageDialog(QDialog):
QMessageBox.critical(
self, "Could not create flight", str(ex), QMessageBox.Ok
)
+ self.package_model.update_tot()
# noinspection PyUnresolvedReferences
self.package_changed.emit()
diff --git a/qt_ui/windows/mission/flight/QFlightPlanner.py b/qt_ui/windows/mission/flight/QFlightPlanner.py
index b4eb9b36..71904b48 100644
--- a/qt_ui/windows/mission/flight/QFlightPlanner.py
+++ b/qt_ui/windows/mission/flight/QFlightPlanner.py
@@ -1,9 +1,8 @@
-from PySide2.QtCore import Signal
from PySide2.QtWidgets import QTabWidget
from game import Game
-from gen.ato import Package
from gen.flights.flight import Flight
+from qt_ui.models import PackageModel
from qt_ui.windows.mission.flight.payload.QFlightPayloadTab import \
QFlightPayloadTab
from qt_ui.windows.mission.flight.settings.QGeneralFlightSettingsTab import \
@@ -14,22 +13,15 @@ from qt_ui.windows.mission.flight.waypoints.QFlightWaypointTab import \
class QFlightPlanner(QTabWidget):
- on_planned_flight_changed = Signal()
-
- def __init__(self, package: Package, flight: Flight, game: Game):
+ def __init__(self, package_model: PackageModel, flight: Flight, game: Game):
super().__init__()
self.general_settings_tab = QGeneralFlightSettingsTab(
- game, package, flight
+ game, package_model, flight
)
- # noinspection PyUnresolvedReferences
- self.general_settings_tab.on_flight_settings_changed.connect(
- lambda: self.on_planned_flight_changed.emit())
self.payload_tab = QFlightPayloadTab(flight, game)
- self.waypoint_tab = QFlightWaypointTab(game, package, flight)
- # noinspection PyUnresolvedReferences
- self.waypoint_tab.on_flight_changed.connect(
- lambda: self.on_planned_flight_changed.emit())
+ self.waypoint_tab = QFlightWaypointTab(game, package_model.package,
+ flight)
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/QFlightDepartureDisplay.py b/qt_ui/windows/mission/flight/settings/QFlightDepartureDisplay.py
index 6d789585..fe1271ca 100644
--- a/qt_ui/windows/mission/flight/settings/QFlightDepartureDisplay.py
+++ b/qt_ui/windows/mission/flight/settings/QFlightDepartureDisplay.py
@@ -1,32 +1,38 @@
-import datetime
-
from PySide2.QtWidgets import QLabel, QHBoxLayout, QGroupBox, QVBoxLayout
-from gen.ato import Package
from gen.flights.flight import Flight
from gen.flights.traveltime import TotEstimator
-# TODO: Remove?
+from qt_ui.models import PackageModel
+
+
class QFlightDepartureDisplay(QGroupBox):
- def __init__(self, package: Package, flight: Flight):
+ def __init__(self, package_model: PackageModel, flight: Flight):
super().__init__("Departure")
+ self.package_model = package_model
+ self.flight = flight
layout = QVBoxLayout()
departure_row = QHBoxLayout()
layout.addLayout(departure_row)
- estimator = TotEstimator(package)
- delay = estimator.mission_start_time(flight)
-
departure_row.addWidget(QLabel(
f"Departing from {flight.from_cp.name}"
))
- departure_row.addWidget(QLabel(f"At T+{delay}"))
+ self.departure_time = QLabel()
+ self.package_model.tot_changed.connect(self.update_departure_time)
+ departure_row.addWidget(self.departure_time)
+ self.update_departure_time()
layout.addWidget(QLabel("Determined based on the package TOT. Edit the "
"package to adjust the TOT."))
self.setLayout(layout)
+
+ def update_departure_time(self) -> None:
+ estimator = TotEstimator(self.package_model.package)
+ delay = estimator.mission_start_time(self.flight)
+ self.departure_time.setText(f"At T+{delay}")
diff --git a/qt_ui/windows/mission/flight/settings/QFlightSlotEditor.py b/qt_ui/windows/mission/flight/settings/QFlightSlotEditor.py
index d6e745e0..1ecb9a28 100644
--- a/qt_ui/windows/mission/flight/settings/QFlightSlotEditor.py
+++ b/qt_ui/windows/mission/flight/settings/QFlightSlotEditor.py
@@ -1,15 +1,20 @@
import logging
from PySide2.QtCore import Signal
-from PySide2.QtWidgets import QLabel, QHBoxLayout, QGroupBox, QSpinBox, QGridLayout
+from PySide2.QtWidgets import QLabel, QGroupBox, QSpinBox, QGridLayout
+
+from game import Game
+from gen.flights.flight import Flight
+from qt_ui.models import PackageModel
class QFlightSlotEditor(QGroupBox):
changed = Signal()
- def __init__(self, flight, game):
- super(QFlightSlotEditor, self).__init__("Slots")
+ def __init__(self, package_model: PackageModel, flight: Flight, game: Game):
+ super().__init__("Slots")
+ self.package_model = package_model
self.flight = flight
self.game = game
self.inventory = self.game.aircraft_inventory.for_control_point(
@@ -70,6 +75,7 @@ class QFlightSlotEditor(QGroupBox):
def _changed_client_count(self):
self.flight.client_count = int(self.client_count_spinner.value())
self._cap_client_count()
+ self.package_model.update_tot()
self.changed.emit()
def _cap_client_count(self):
diff --git a/qt_ui/windows/mission/flight/settings/QFlightStartType.py b/qt_ui/windows/mission/flight/settings/QFlightStartType.py
index 438c56de..b49fb5d7 100644
--- a/qt_ui/windows/mission/flight/settings/QFlightStartType.py
+++ b/qt_ui/windows/mission/flight/settings/QFlightStartType.py
@@ -1,14 +1,14 @@
-from PySide2.QtWidgets import QGroupBox, QHBoxLayout, QComboBox, QLabel
-from dcs.mission import StartType
+from PySide2.QtWidgets import QComboBox, QGroupBox, QHBoxLayout, QLabel
from gen.flights.flight import Flight
+from qt_ui.models import PackageModel
class QFlightStartType(QGroupBox):
- def __init__(self, flight:Flight):
- super(QFlightStartType, self).__init__()
-
+ def __init__(self, package_model: PackageModel, flight: Flight):
+ super().__init__()
+ self.package_model = package_model
self.flight = flight
self.layout = QHBoxLayout()
@@ -28,6 +28,4 @@ class QFlightStartType(QGroupBox):
def _on_start_type_selected(self):
selected = self.start_type.currentData()
self.flight.start_type = selected
-
-
-
+ self.package_model.update_tot()
\ No newline at end of file
diff --git a/qt_ui/windows/mission/flight/settings/QGeneralFlightSettingsTab.py b/qt_ui/windows/mission/flight/settings/QGeneralFlightSettingsTab.py
index 71dfc036..5875a79d 100644
--- a/qt_ui/windows/mission/flight/settings/QGeneralFlightSettingsTab.py
+++ b/qt_ui/windows/mission/flight/settings/QGeneralFlightSettingsTab.py
@@ -1,9 +1,10 @@
from PySide2.QtCore import Signal
-from PySide2.QtWidgets import QFrame, QGridLayout, QVBoxLayout, QLabel
+from PySide2.QtWidgets import QFrame, QGridLayout, QVBoxLayout
from game import Game
from gen.ato import Package
from gen.flights.flight import Flight
+from qt_ui.models import PackageModel
from qt_ui.windows.mission.flight.settings.QFlightDepartureDisplay import \
QFlightDepartureDisplay
from qt_ui.windows.mission.flight.settings.QFlightSlotEditor import \
@@ -19,14 +20,14 @@ from qt_ui.windows.mission.flight.settings.QCustomName import \
class QGeneralFlightSettingsTab(QFrame):
on_flight_settings_changed = Signal()
- def __init__(self, game: Game, package: Package, flight: Flight):
+ def __init__(self, game: Game, package_model: PackageModel, flight: Flight):
super().__init__()
layout = QGridLayout()
flight_info = QFlightTypeTaskInfo(flight)
- flight_departure = QFlightDepartureDisplay(package, flight)
- flight_slots = QFlightSlotEditor(flight, game)
- flight_start_type = QFlightStartType(flight)
+ flight_departure = QFlightDepartureDisplay(package_model, flight)
+ flight_slots = QFlightSlotEditor(package_model, flight, game)
+ flight_start_type = QFlightStartType(package_model, flight)
flight_custom_name = QFlightCustomName(flight)
layout.addWidget(flight_info, 0, 0)
layout.addWidget(flight_departure, 1, 0)
diff --git a/qt_ui/windows/mission/flight/waypoints/QFlightWaypointTab.py b/qt_ui/windows/mission/flight/waypoints/QFlightWaypointTab.py
index 92145a1c..abd1733d 100644
--- a/qt_ui/windows/mission/flight/waypoints/QFlightWaypointTab.py
+++ b/qt_ui/windows/mission/flight/waypoints/QFlightWaypointTab.py
@@ -29,8 +29,6 @@ from qt_ui.windows.mission.flight.waypoints \
class QFlightWaypointTab(QFrame):
- on_flight_changed = Signal()
-
def __init__(self, game: Game, package: Package, flight: Flight):
super(QFlightWaypointTab, self).__init__()
self.game = game
@@ -163,4 +161,3 @@ class QFlightWaypointTab(QFrame):
def on_change(self):
self.flight_waypoint_list.update_list()
- self.on_flight_changed.emit()