Make ASAP a checkbox and maintain ASAP on changes.

Fixes https://github.com/Khopa/dcs_liberation/issues/642
This commit is contained in:
Dan Albert 2021-01-30 13:33:39 -08:00
parent 768d239840
commit 5047b535c4
12 changed files with 92 additions and 62 deletions

View File

@ -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.

View File

@ -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)

View File

@ -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
)

View File

@ -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:

View File

@ -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)

View File

@ -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("<a href=\"https://github.com/Khopa/dcs_liberation/wiki/Mission-planning\"><span style=\"color:#FFFFFF;\">Help</span></a>")
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()

View File

@ -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")

View File

@ -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 <b>{flight.from_cp.name}</b>"
))
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}")

View File

@ -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):

View File

@ -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()

View File

@ -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)

View File

@ -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()