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:
|
def spawn_type(self) -> StartType:
|
||||||
# TODO: May want to do something different to make these uncontrolled?
|
# TODO: May want to do something different to make these uncontrolled?
|
||||||
return StartType.COLD
|
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:
|
if (max_takeoff_fuel := self.flight.max_takeoff_fuel()) is not None:
|
||||||
return max_takeoff_fuel
|
return max_takeoff_fuel
|
||||||
return self.flight.unit_type.dcs_unit_type.fuel_max
|
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
|
@property
|
||||||
def spawn_type(self) -> StartType:
|
def spawn_type(self) -> StartType:
|
||||||
return StartType.IN_FLIGHT
|
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:
|
def travel_time_between_waypoints(self) -> timedelta:
|
||||||
return self.hold_duration
|
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}:
|
if self.flight.flight_type in {FlightType.BARCAP, FlightType.TARCAP}:
|
||||||
return self.commit_region
|
return self.commit_region
|
||||||
return None
|
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 True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description(self) -> str:
|
||||||
|
return "Starting up"
|
||||||
|
|||||||
@ -51,3 +51,7 @@ class Takeoff(FlightState):
|
|||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description(self) -> str:
|
||||||
|
return "Taking off"
|
||||||
|
|||||||
@ -43,3 +43,7 @@ class Taxi(FlightState):
|
|||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description(self) -> str:
|
||||||
|
return "Taxiing"
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
from gen.flights.traveltime import TotEstimator
|
||||||
from .flightstate import FlightState
|
from .flightstate import FlightState
|
||||||
from ..starttype import StartType
|
from ..starttype import StartType
|
||||||
|
|
||||||
@ -15,3 +16,9 @@ class Uninitialized(FlightState):
|
|||||||
@property
|
@property
|
||||||
def spawn_type(self) -> StartType:
|
def spawn_type(self) -> StartType:
|
||||||
raise RuntimeError("Attempted to simulate flight that is not fully initialized")
|
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
|
@property
|
||||||
def spawn_type(self) -> StartType:
|
def spawn_type(self) -> StartType:
|
||||||
return self.flight.start_type
|
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
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime, timedelta
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Callable, TYPE_CHECKING
|
from typing import Callable, TYPE_CHECKING
|
||||||
|
|
||||||
@ -27,6 +27,10 @@ class GameLoop:
|
|||||||
def current_time_in_sim(self) -> datetime:
|
def current_time_in_sim(self) -> datetime:
|
||||||
return self.sim.time
|
return self.sim.time
|
||||||
|
|
||||||
|
@property
|
||||||
|
def elapsed_time(self) -> timedelta:
|
||||||
|
return self.sim.time - self.game.conditions.start_time
|
||||||
|
|
||||||
def start(self) -> None:
|
def start(self) -> None:
|
||||||
if self.started:
|
if self.started:
|
||||||
raise RuntimeError("Cannot start game loop because it has already 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 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.game import Game
|
||||||
from game.squadrons.squadron import Pilot, Squadron
|
from game.squadrons.squadron import Pilot, Squadron
|
||||||
from game.theater.missiontarget import MissionTarget
|
from game.theater.missiontarget import MissionTarget
|
||||||
from game.transfers import TransferOrder, PendingTransfers
|
from game.transfers import PendingTransfers, TransferOrder
|
||||||
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 gen.flights.traveltime import TotEstimator
|
from gen.flights.traveltime import TotEstimator
|
||||||
|
from qt_ui.simcontroller import SimController
|
||||||
from qt_ui.uiconstants import AIRCRAFT_ICONS
|
from qt_ui.uiconstants import AIRCRAFT_ICONS
|
||||||
|
|
||||||
|
|
||||||
@ -116,6 +117,7 @@ class PackageModel(QAbstractListModel):
|
|||||||
super().__init__()
|
super().__init__()
|
||||||
self.package = package
|
self.package = package
|
||||||
self.game_model = game_model
|
self.game_model = game_model
|
||||||
|
self.game_model.sim_controller.sim_update.connect(self.on_sim_update)
|
||||||
|
|
||||||
def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
|
def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
|
||||||
return len(self.package.flights)
|
return len(self.package.flights)
|
||||||
@ -209,6 +211,9 @@ class PackageModel(QAbstractListModel):
|
|||||||
for flight in self.package.flights:
|
for flight in self.package.flights:
|
||||||
yield flight
|
yield flight
|
||||||
|
|
||||||
|
def on_sim_update(self) -> None:
|
||||||
|
self.dataChanged.emit(self.index(0), self.index(self.rowCount()))
|
||||||
|
|
||||||
|
|
||||||
class AtoModel(QAbstractListModel):
|
class AtoModel(QAbstractListModel):
|
||||||
"""The model for an AirTaskingOrder."""
|
"""The model for an AirTaskingOrder."""
|
||||||
@ -222,6 +227,7 @@ class AtoModel(QAbstractListModel):
|
|||||||
self.game_model = game_model
|
self.game_model = game_model
|
||||||
self.ato = ato
|
self.ato = ato
|
||||||
self.package_models = DeletableChildModelManager(PackageModel, game_model)
|
self.package_models = DeletableChildModelManager(PackageModel, game_model)
|
||||||
|
self.game_model.sim_controller.sim_update.connect(self.on_sim_update)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def game(self) -> Optional[Game]:
|
def game(self) -> Optional[Game]:
|
||||||
@ -305,6 +311,9 @@ class AtoModel(QAbstractListModel):
|
|||||||
for package in self.ato.packages:
|
for package in self.ato.packages:
|
||||||
yield self.package_models.acquire(package)
|
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):
|
class TransferModel(QAbstractListModel):
|
||||||
"""The model for a ground unit transfer."""
|
"""The model for a ground unit transfer."""
|
||||||
@ -483,8 +492,9 @@ class GameModel:
|
|||||||
its ATO objects.
|
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.game: Optional[Game] = game
|
||||||
|
self.sim_controller = sim_controller
|
||||||
self.transfer_model = TransferModel(self)
|
self.transfer_model = TransferModel(self)
|
||||||
self.blue_air_wing_model = AirWingModel(self, player=True)
|
self.blue_air_wing_model = AirWingModel(self, player=True)
|
||||||
if self.game is None:
|
if self.game is None:
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime, timedelta
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Callable, Optional, TYPE_CHECKING
|
from typing import Callable, Optional, TYPE_CHECKING
|
||||||
|
|
||||||
@ -40,6 +40,12 @@ class SimController(QObject):
|
|||||||
return None
|
return None
|
||||||
return self.game_loop.current_time_in_sim
|
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:
|
def set_game(self, game: Optional[Game]) -> None:
|
||||||
self.recreate_game_loop(game)
|
self.recreate_game_loop(game)
|
||||||
self.sim_speed_reset.emit(SimSpeedSetting.PAUSED)
|
self.sim_speed_reset.emit(SimSpeedSetting.PAUSED)
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
"""Widgets for displaying air tasking orders."""
|
"""Widgets for displaying air tasking orders."""
|
||||||
import logging
|
import logging
|
||||||
|
from datetime import timedelta
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from PySide2.QtCore import (
|
from PySide2.QtCore import (
|
||||||
@ -24,9 +25,8 @@ from PySide2.QtWidgets import (
|
|||||||
QVBoxLayout,
|
QVBoxLayout,
|
||||||
)
|
)
|
||||||
|
|
||||||
from game.ato.package import Package
|
|
||||||
from game.ato.flight import Flight
|
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 qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
||||||
from ..delegates import TwoColumnRowDelegate
|
from ..delegates import TwoColumnRowDelegate
|
||||||
from ..models import AtoModel, GameModel, NullListModel, PackageModel
|
from ..models import AtoModel, GameModel, NullListModel, PackageModel
|
||||||
@ -34,7 +34,7 @@ from ..models import AtoModel, GameModel, NullListModel, PackageModel
|
|||||||
|
|
||||||
class FlightDelegate(TwoColumnRowDelegate):
|
class FlightDelegate(TwoColumnRowDelegate):
|
||||||
def __init__(self, package: Package) -> None:
|
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
|
self.package = package
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -44,9 +44,7 @@ class FlightDelegate(TwoColumnRowDelegate):
|
|||||||
def text_for(self, index: QModelIndex, row: int, column: int) -> str:
|
def text_for(self, index: QModelIndex, row: int, column: int) -> str:
|
||||||
flight = self.flight(index)
|
flight = self.flight(index)
|
||||||
if (row, column) == (0, 0):
|
if (row, column) == (0, 0):
|
||||||
estimator = TotEstimator(self.package)
|
return f"{flight}"
|
||||||
delay = estimator.mission_start_time(flight)
|
|
||||||
return f"{flight} in {delay}"
|
|
||||||
elif (row, column) == (0, 1):
|
elif (row, column) == (0, 1):
|
||||||
clients = self.num_clients(index)
|
clients = self.num_clients(index)
|
||||||
return f"Player Slots: {clients}" if clients else ""
|
return f"Player Slots: {clients}" if clients else ""
|
||||||
@ -58,6 +56,8 @@ class FlightDelegate(TwoColumnRowDelegate):
|
|||||||
elif (row, column) == (1, 1):
|
elif (row, column) == (1, 1):
|
||||||
missing_pilots = flight.missing_pilots
|
missing_pilots = flight.missing_pilots
|
||||||
return f"Missing pilots: {flight.missing_pilots}" if missing_pilots else ""
|
return f"Missing pilots: {flight.missing_pilots}" if missing_pilots else ""
|
||||||
|
elif (row, column) == (2, 0):
|
||||||
|
return flight.state.description
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def num_clients(self, index: QModelIndex) -> int:
|
def num_clients(self, index: QModelIndex) -> int:
|
||||||
@ -235,8 +235,9 @@ class QFlightPanel(QGroupBox):
|
|||||||
|
|
||||||
|
|
||||||
class PackageDelegate(TwoColumnRowDelegate):
|
class PackageDelegate(TwoColumnRowDelegate):
|
||||||
def __init__(self) -> None:
|
def __init__(self, game_model: GameModel) -> None:
|
||||||
super().__init__(rows=2, columns=2)
|
super().__init__(rows=2, columns=2)
|
||||||
|
self.game_model = game_model
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def package(index: QModelIndex) -> Package:
|
def package(index: QModelIndex) -> Package:
|
||||||
@ -250,7 +251,16 @@ class PackageDelegate(TwoColumnRowDelegate):
|
|||||||
clients = self.num_clients(index)
|
clients = self.num_clients(index)
|
||||||
return f"Player Slots: {clients}" if clients else ""
|
return f"Player Slots: {clients}" if clients else ""
|
||||||
elif (row, column) == (1, 0):
|
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):
|
elif (row, column) == (1, 1):
|
||||||
unassigned_pilots = self.missing_pilots(index)
|
unassigned_pilots = self.missing_pilots(index)
|
||||||
return f"Missing pilots: {unassigned_pilots}" if unassigned_pilots else ""
|
return f"Missing pilots: {unassigned_pilots}" if unassigned_pilots else ""
|
||||||
@ -268,11 +278,11 @@ class PackageDelegate(TwoColumnRowDelegate):
|
|||||||
class QPackageList(QListView):
|
class QPackageList(QListView):
|
||||||
"""List view for displaying the packages of an ATO."""
|
"""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__()
|
super().__init__()
|
||||||
self.ato_model = model
|
self.ato_model = model
|
||||||
self.setModel(model)
|
self.setModel(model)
|
||||||
self.setItemDelegate(PackageDelegate())
|
self.setItemDelegate(PackageDelegate(game_model))
|
||||||
self.setIconSize(QSize(0, 0))
|
self.setIconSize(QSize(0, 0))
|
||||||
self.setSelectionBehavior(QAbstractItemView.SelectItems)
|
self.setSelectionBehavior(QAbstractItemView.SelectItems)
|
||||||
self.model().rowsInserted.connect(self.on_new_packages)
|
self.model().rowsInserted.connect(self.on_new_packages)
|
||||||
@ -331,9 +341,9 @@ class QPackagePanel(QGroupBox):
|
|||||||
delete buttons for package management.
|
delete buttons for package management.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, model: AtoModel) -> None:
|
def __init__(self, game_model: GameModel, ato_model: AtoModel) -> None:
|
||||||
super().__init__("Packages")
|
super().__init__("Packages")
|
||||||
self.ato_model = model
|
self.ato_model = ato_model
|
||||||
self.ato_model.layoutChanged.connect(self.on_current_changed)
|
self.ato_model.layoutChanged.connect(self.on_current_changed)
|
||||||
|
|
||||||
self.vbox = QVBoxLayout()
|
self.vbox = QVBoxLayout()
|
||||||
@ -346,7 +356,7 @@ class QPackagePanel(QGroupBox):
|
|||||||
)
|
)
|
||||||
self.vbox.addWidget(self.tip)
|
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.vbox.addWidget(self.package_list)
|
||||||
|
|
||||||
self.button_row = QHBoxLayout()
|
self.button_row = QHBoxLayout()
|
||||||
@ -418,7 +428,7 @@ class QAirTaskingOrderPanel(QSplitter):
|
|||||||
super().__init__(Qt.Vertical)
|
super().__init__(Qt.Vertical)
|
||||||
self.ato_model = game_model.ato_model
|
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.package_panel.current_changed.connect(self.on_package_change)
|
||||||
self.addWidget(self.package_panel)
|
self.addWidget(self.package_panel)
|
||||||
|
|
||||||
|
|||||||
@ -49,9 +49,9 @@ class QLiberationWindow(QMainWindow):
|
|||||||
self._uncaught_exception_handler = UncaughtExceptionHandler(self)
|
self._uncaught_exception_handler = UncaughtExceptionHandler(self)
|
||||||
|
|
||||||
self.game = game
|
self.game = game
|
||||||
self.game_model = GameModel(game)
|
|
||||||
Dialog.set_game(self.game_model)
|
|
||||||
self.sim_controller = SimController(self.game)
|
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.ato_panel = QAirTaskingOrderPanel(self.game_model)
|
||||||
self.info_panel = QInfoPanel(self.game)
|
self.info_panel = QInfoPanel(self.game)
|
||||||
self.liberation_map = QLiberationMap(self.game_model, self.sim_controller, self)
|
self.liberation_map = QLiberationMap(self.game_model, self.sim_controller, self)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user