Show the status of each flight in the UI.

https://github.com/dcs-liberation/dcs_liberation/issues/1704
This commit is contained in:
Dan Albert 2021-11-07 01:24:49 -07:00
parent 30cfd8a769
commit d31f0e22e3
15 changed files with 99 additions and 24 deletions

View File

@ -16,3 +16,7 @@ class Completed(FlightState):
def spawn_type(self) -> StartType:
# TODO: May want to do something different to make these uncontrolled?
return StartType.COLD
@property
def description(self) -> str:
return "Completed"

View File

@ -43,3 +43,9 @@ class FlightState(ABC):
if (max_takeoff_fuel := self.flight.max_takeoff_fuel()) is not None:
return max_takeoff_fuel
return self.flight.unit_type.dcs_unit_type.fuel_max
@property
@abstractmethod
def description(self) -> str:
"""Describes the current flight state."""
...

View File

@ -170,3 +170,7 @@ class InFlight(FlightState):
@property
def spawn_type(self) -> StartType:
return StartType.IN_FLIGHT
@property
def description(self) -> str:
return f"Flying to {self.next_waypoint.name}"

View File

@ -40,3 +40,7 @@ class Loiter(InFlight):
def travel_time_between_waypoints(self) -> timedelta:
return self.hold_duration
@property
def description(self) -> str:
return f"Loitering for {self.hold_duration - self.elapsed_time}"

View File

@ -51,3 +51,7 @@ class RaceTrack(InFlight):
if self.flight.flight_type in {FlightType.BARCAP, FlightType.TARCAP}:
return self.commit_region
return None
@property
def description(self) -> str:
return f"Patrolling for {self.patrol_duration - self.elapsed_time}"

View File

@ -43,3 +43,7 @@ class StartUp(FlightState):
)
return True
return False
@property
def description(self) -> str:
return "Starting up"

View File

@ -51,3 +51,7 @@ class Takeoff(FlightState):
)
return True
return False
@property
def description(self) -> str:
return "Taking off"

View File

@ -43,3 +43,7 @@ class Taxi(FlightState):
)
return True
return False
@property
def description(self) -> str:
return "Taxiing"

View File

@ -1,5 +1,6 @@
from datetime import datetime, timedelta
from gen.flights.traveltime import TotEstimator
from .flightstate import FlightState
from ..starttype import StartType
@ -15,3 +16,9 @@ class Uninitialized(FlightState):
@property
def spawn_type(self) -> StartType:
raise RuntimeError("Attempted to simulate flight that is not fully initialized")
@property
def description(self) -> str:
estimator = TotEstimator(self.flight.package)
delay = estimator.mission_start_time(self.flight)
return f"Starting in {delay}"

View File

@ -54,3 +54,7 @@ class WaitingForStart(FlightState):
@property
def spawn_type(self) -> StartType:
return self.flight.start_type
@property
def description(self) -> str:
return f"Waiting for startup at {self.start_time:%H:%M:%S}"

View File

@ -1,7 +1,7 @@
from __future__ import annotations
import logging
from datetime import datetime
from datetime import datetime, timedelta
from pathlib import Path
from typing import Callable, TYPE_CHECKING
@ -27,6 +27,10 @@ class GameLoop:
def current_time_in_sim(self) -> datetime:
return self.sim.time
@property
def elapsed_time(self) -> timedelta:
return self.sim.time - self.game.conditions.start_time
def start(self) -> None:
if self.started:
raise RuntimeError("Cannot start game loop because it has already started")

View File

@ -12,15 +12,16 @@ from PySide2.QtCore import (
)
from PySide2.QtGui import QIcon
from game.ato.airtaaskingorder import AirTaskingOrder
from game.ato.flight import Flight
from game.ato.flighttype import FlightType
from game.ato.package import Package
from game.game import Game
from game.squadrons.squadron import Pilot, Squadron
from game.theater.missiontarget import MissionTarget
from game.transfers import TransferOrder, PendingTransfers
from game.ato.airtaaskingorder import AirTaskingOrder
from game.ato.package import Package
from game.ato.flighttype import FlightType
from game.ato.flight import Flight
from game.transfers import PendingTransfers, TransferOrder
from gen.flights.traveltime import TotEstimator
from qt_ui.simcontroller import SimController
from qt_ui.uiconstants import AIRCRAFT_ICONS
@ -116,6 +117,7 @@ class PackageModel(QAbstractListModel):
super().__init__()
self.package = package
self.game_model = game_model
self.game_model.sim_controller.sim_update.connect(self.on_sim_update)
def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
return len(self.package.flights)
@ -209,6 +211,9 @@ class PackageModel(QAbstractListModel):
for flight in self.package.flights:
yield flight
def on_sim_update(self) -> None:
self.dataChanged.emit(self.index(0), self.index(self.rowCount()))
class AtoModel(QAbstractListModel):
"""The model for an AirTaskingOrder."""
@ -222,6 +227,7 @@ class AtoModel(QAbstractListModel):
self.game_model = game_model
self.ato = ato
self.package_models = DeletableChildModelManager(PackageModel, game_model)
self.game_model.sim_controller.sim_update.connect(self.on_sim_update)
@property
def game(self) -> Optional[Game]:
@ -305,6 +311,9 @@ class AtoModel(QAbstractListModel):
for package in self.ato.packages:
yield self.package_models.acquire(package)
def on_sim_update(self) -> None:
self.dataChanged.emit(self.index(0), self.index(self.rowCount()))
class TransferModel(QAbstractListModel):
"""The model for a ground unit transfer."""
@ -483,8 +492,9 @@ class GameModel:
its ATO objects.
"""
def __init__(self, game: Optional[Game]) -> None:
def __init__(self, game: Optional[Game], sim_controller: SimController) -> None:
self.game: Optional[Game] = game
self.sim_controller = sim_controller
self.transfer_model = TransferModel(self)
self.blue_air_wing_model = AirWingModel(self, player=True)
if self.game is None:

View File

@ -1,7 +1,7 @@
from __future__ import annotations
import logging
from datetime import datetime
from datetime import datetime, timedelta
from pathlib import Path
from typing import Callable, Optional, TYPE_CHECKING
@ -40,6 +40,12 @@ class SimController(QObject):
return None
return self.game_loop.current_time_in_sim
@property
def elapsed_time(self) -> timedelta:
if self.game_loop is None:
return timedelta()
return self.game_loop.elapsed_time
def set_game(self, game: Optional[Game]) -> None:
self.recreate_game_loop(game)
self.sim_speed_reset.emit(SimSpeedSetting.PAUSED)

View File

@ -1,5 +1,6 @@
"""Widgets for displaying air tasking orders."""
import logging
from datetime import timedelta
from typing import Optional
from PySide2.QtCore import (
@ -24,9 +25,8 @@ from PySide2.QtWidgets import (
QVBoxLayout,
)
from game.ato.package import Package
from game.ato.flight import Flight
from gen.flights.traveltime import TotEstimator
from game.ato.package import Package
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
from ..delegates import TwoColumnRowDelegate
from ..models import AtoModel, GameModel, NullListModel, PackageModel
@ -34,7 +34,7 @@ from ..models import AtoModel, GameModel, NullListModel, PackageModel
class FlightDelegate(TwoColumnRowDelegate):
def __init__(self, package: Package) -> None:
super().__init__(rows=2, columns=2, font_size=10)
super().__init__(rows=3, columns=2, font_size=10)
self.package = package
@staticmethod
@ -44,9 +44,7 @@ class FlightDelegate(TwoColumnRowDelegate):
def text_for(self, index: QModelIndex, row: int, column: int) -> str:
flight = self.flight(index)
if (row, column) == (0, 0):
estimator = TotEstimator(self.package)
delay = estimator.mission_start_time(flight)
return f"{flight} in {delay}"
return f"{flight}"
elif (row, column) == (0, 1):
clients = self.num_clients(index)
return f"Player Slots: {clients}" if clients else ""
@ -58,6 +56,8 @@ class FlightDelegate(TwoColumnRowDelegate):
elif (row, column) == (1, 1):
missing_pilots = flight.missing_pilots
return f"Missing pilots: {flight.missing_pilots}" if missing_pilots else ""
elif (row, column) == (2, 0):
return flight.state.description
return ""
def num_clients(self, index: QModelIndex) -> int:
@ -235,8 +235,9 @@ class QFlightPanel(QGroupBox):
class PackageDelegate(TwoColumnRowDelegate):
def __init__(self) -> None:
def __init__(self, game_model: GameModel) -> None:
super().__init__(rows=2, columns=2)
self.game_model = game_model
@staticmethod
def package(index: QModelIndex) -> Package:
@ -250,7 +251,16 @@ class PackageDelegate(TwoColumnRowDelegate):
clients = self.num_clients(index)
return f"Player Slots: {clients}" if clients else ""
elif (row, column) == (1, 0):
return f"TOT T+{package.time_over_target}"
tot_delay = (
package.time_over_target - self.game_model.sim_controller.elapsed_time
)
if tot_delay >= timedelta():
return f"TOT in {tot_delay}"
game = self.game_model.game
if game is None:
raise RuntimeError("Package TOT has elapsed but no game is loaded")
tot_time = game.conditions.start_time + package.time_over_target
return f"TOT passed at {tot_time:%H:%M:%S}"
elif (row, column) == (1, 1):
unassigned_pilots = self.missing_pilots(index)
return f"Missing pilots: {unassigned_pilots}" if unassigned_pilots else ""
@ -268,11 +278,11 @@ class PackageDelegate(TwoColumnRowDelegate):
class QPackageList(QListView):
"""List view for displaying the packages of an ATO."""
def __init__(self, model: AtoModel) -> None:
def __init__(self, game_model: GameModel, model: AtoModel) -> None:
super().__init__()
self.ato_model = model
self.setModel(model)
self.setItemDelegate(PackageDelegate())
self.setItemDelegate(PackageDelegate(game_model))
self.setIconSize(QSize(0, 0))
self.setSelectionBehavior(QAbstractItemView.SelectItems)
self.model().rowsInserted.connect(self.on_new_packages)
@ -331,9 +341,9 @@ class QPackagePanel(QGroupBox):
delete buttons for package management.
"""
def __init__(self, model: AtoModel) -> None:
def __init__(self, game_model: GameModel, ato_model: AtoModel) -> None:
super().__init__("Packages")
self.ato_model = model
self.ato_model = ato_model
self.ato_model.layoutChanged.connect(self.on_current_changed)
self.vbox = QVBoxLayout()
@ -346,7 +356,7 @@ class QPackagePanel(QGroupBox):
)
self.vbox.addWidget(self.tip)
self.package_list = QPackageList(self.ato_model)
self.package_list = QPackageList(game_model, self.ato_model)
self.vbox.addWidget(self.package_list)
self.button_row = QHBoxLayout()
@ -418,7 +428,7 @@ class QAirTaskingOrderPanel(QSplitter):
super().__init__(Qt.Vertical)
self.ato_model = game_model.ato_model
self.package_panel = QPackagePanel(self.ato_model)
self.package_panel = QPackagePanel(game_model, self.ato_model)
self.package_panel.current_changed.connect(self.on_package_change)
self.addWidget(self.package_panel)

View File

@ -49,9 +49,9 @@ class QLiberationWindow(QMainWindow):
self._uncaught_exception_handler = UncaughtExceptionHandler(self)
self.game = game
self.game_model = GameModel(game)
Dialog.set_game(self.game_model)
self.sim_controller = SimController(self.game)
self.game_model = GameModel(game, self.sim_controller)
Dialog.set_game(self.game_model)
self.ato_panel = QAirTaskingOrderPanel(self.game_model)
self.info_panel = QInfoPanel(self.game)
self.liberation_map = QLiberationMap(self.game_model, self.sim_controller, self)