mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Show the status of each flight in the UI.
https://github.com/dcs-liberation/dcs_liberation/issues/1704
This commit is contained in:
parent
30cfd8a769
commit
d31f0e22e3
@ -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"
|
||||
|
||||
@ -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."""
|
||||
...
|
||||
|
||||
@ -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}"
|
||||
|
||||
@ -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}"
|
||||
|
||||
@ -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}"
|
||||
|
||||
@ -43,3 +43,7 @@ class StartUp(FlightState):
|
||||
)
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return "Starting up"
|
||||
|
||||
@ -51,3 +51,7 @@ class Takeoff(FlightState):
|
||||
)
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return "Taking off"
|
||||
|
||||
@ -43,3 +43,7 @@ class Taxi(FlightState):
|
||||
)
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return "Taxiing"
|
||||
|
||||
@ -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}"
|
||||
|
||||
@ -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}"
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user