mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Reorganize flight planning.
Previously we were trying to make every potential flight plan look just like a strike mission's flight plan. This led to a lot of special case behavior in several places that was causing us to misplan TOTs. I've reorganized this such that there's now an explicit `FlightPlan` class, and any specialized behavior is handled by the subclasses. I've also taken the opportunity to alter the behavior of CAS and front-line CAP missions. These no longer involve the usual formation waypoints. Instead the CAP will aim to be on station at the time that the CAS mission reaches its ingress point, and leave at its egress time. Both flights fly directly to the point with a start time configured for a rendezvous. It might be worth adding hold points back to every flight plan just to ensure that non-formation flights don't end up with a very low speed enroute to the target if they perform ground ops quicker than expected.
This commit is contained in:
@@ -162,7 +162,7 @@ class PackageModel(QAbstractListModel):
|
||||
"""Returns the flight located at the given index."""
|
||||
return self.package.flights[index.row()]
|
||||
|
||||
def update_tot(self, tot: int) -> None:
|
||||
def update_tot(self, tot: datetime.timedelta) -> None:
|
||||
self.package.time_over_target = tot
|
||||
self.layoutChanged.emit()
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ class QTopPanel(QFrame):
|
||||
continue
|
||||
estimator = TotEstimator(package)
|
||||
for flight in package.flights:
|
||||
if estimator.mission_start_time(flight) < 0:
|
||||
if estimator.mission_start_time(flight).total_seconds() < 0:
|
||||
packages.append(package)
|
||||
break
|
||||
return packages
|
||||
|
||||
@@ -64,7 +64,8 @@ class FlightDelegate(QStyledItemDelegate):
|
||||
count = flight.count
|
||||
name = db.unit_type_name(flight.unit_type)
|
||||
estimator = TotEstimator(self.package)
|
||||
delay = datetime.timedelta(seconds=estimator.mission_start_time(flight))
|
||||
delay = datetime.timedelta(
|
||||
seconds=int(estimator.mission_start_time(flight).total_seconds()))
|
||||
return f"[{task}] {count} x {name} in {delay}"
|
||||
|
||||
def second_row_text(self, index: QModelIndex) -> str:
|
||||
@@ -328,10 +329,9 @@ class PackageDelegate(QStyledItemDelegate):
|
||||
|
||||
def right_text(self, index: QModelIndex) -> str:
|
||||
package = self.package(index)
|
||||
if package.time_over_target is None:
|
||||
return ""
|
||||
tot = datetime.timedelta(seconds=package.time_over_target)
|
||||
return f"TOT T+{tot}"
|
||||
delay = datetime.timedelta(
|
||||
seconds=int(package.time_over_target.total_seconds()))
|
||||
return f"TOT T+{delay}"
|
||||
|
||||
def paint(self, painter: QPainter, option: QStyleOptionViewItem,
|
||||
index: QModelIndex) -> None:
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
from PySide2.QtCore import QSortFilterProxyModel, Qt, QModelIndex
|
||||
from PySide2.QtGui import QStandardItem, QStandardItemModel
|
||||
from PySide2.QtWidgets import QComboBox, QCompleter
|
||||
|
||||
from game import Game
|
||||
from gen import Conflict, FlightWaypointType
|
||||
from gen.flights.flight import FlightWaypoint, PredefinedWaypointCategory
|
||||
from gen.flights.flight import FlightWaypoint
|
||||
from qt_ui.widgets.combos.QFilteredComboBox import QFilteredComboBox
|
||||
from theater import ControlPointType
|
||||
|
||||
@@ -66,8 +65,6 @@ class QPredefinedWaypointSelectionComboBox(QFilteredComboBox):
|
||||
wpt.alt_type = "RADIO"
|
||||
wpt.pretty_name = wpt.name
|
||||
wpt.description = "Frontline"
|
||||
wpt.data = [cp, ecp]
|
||||
wpt.category = PredefinedWaypointCategory.FRONTLINE
|
||||
i = add_model_item(i, model, wpt.pretty_name, wpt)
|
||||
|
||||
if self.include_targets:
|
||||
@@ -86,13 +83,10 @@ class QPredefinedWaypointSelectionComboBox(QFilteredComboBox):
|
||||
wpt.pretty_name = wpt.name
|
||||
wpt.obj_name = ground_object.obj_name
|
||||
wpt.targets.append(ground_object)
|
||||
wpt.data = ground_object
|
||||
if cp.captured:
|
||||
wpt.description = "Friendly Building"
|
||||
wpt.category = PredefinedWaypointCategory.ALLY_BUILDING
|
||||
else:
|
||||
wpt.description = "Enemy Building"
|
||||
wpt.category = PredefinedWaypointCategory.ENEMY_BUILDING
|
||||
i = add_model_item(i, model, wpt.pretty_name, wpt)
|
||||
|
||||
if self.include_units:
|
||||
@@ -112,15 +106,12 @@ class QPredefinedWaypointSelectionComboBox(QFilteredComboBox):
|
||||
wpt.name = wpt.name = "[" + str(ground_object.obj_name) + "] : " + u.type + " #" + str(j)
|
||||
wpt.pretty_name = wpt.name
|
||||
wpt.targets.append(u)
|
||||
wpt.data = u
|
||||
wpt.obj_name = ground_object.obj_name
|
||||
wpt.waypoint_type = FlightWaypointType.CUSTOM
|
||||
if cp.captured:
|
||||
wpt.description = "Friendly unit : " + u.type
|
||||
wpt.category = PredefinedWaypointCategory.ALLY_UNIT
|
||||
else:
|
||||
wpt.description = "Enemy unit : " + u.type
|
||||
wpt.category = PredefinedWaypointCategory.ENEMY_UNIT
|
||||
i = add_model_item(i, model, wpt.pretty_name, wpt)
|
||||
|
||||
if self.include_airbases:
|
||||
@@ -134,13 +125,10 @@ class QPredefinedWaypointSelectionComboBox(QFilteredComboBox):
|
||||
)
|
||||
wpt.alt_type = "RADIO"
|
||||
wpt.name = cp.name
|
||||
wpt.data = cp
|
||||
if cp.captured:
|
||||
wpt.description = "Position of " + cp.name + " [Friendly Airbase]"
|
||||
wpt.category = PredefinedWaypointCategory.ALLY_CP
|
||||
else:
|
||||
wpt.description = "Position of " + cp.name + " [Enemy Airbase]"
|
||||
wpt.category = PredefinedWaypointCategory.ENEMY_CP
|
||||
|
||||
if cp.cptype == ControlPointType.AIRCRAFT_CARRIER_GROUP:
|
||||
wpt.pretty_name = cp.name + " (Aircraft Carrier Group)"
|
||||
|
||||
@@ -30,9 +30,9 @@ from game.data.aaa_db import AAA_UNITS
|
||||
from game.data.radar_db import UNITS_WITH_RADAR
|
||||
from game.utils import meter_to_feet
|
||||
from game.weather import TimeOfDay
|
||||
from gen import Conflict, PackageWaypointTiming
|
||||
from gen.ato import Package
|
||||
from gen import Conflict
|
||||
from gen.flights.flight import Flight, FlightWaypoint, FlightWaypointType
|
||||
from gen.flights.flightplan import FlightPlan
|
||||
from qt_ui.displayoptions import DisplayOptions
|
||||
from qt_ui.models import GameModel
|
||||
from qt_ui.widgets.map.QFrontLine import QFrontLine
|
||||
@@ -294,11 +294,10 @@ class QLiberationMap(QGraphicsView):
|
||||
selected = (p_idx, f_idx) == self.selected_flight
|
||||
if DisplayOptions.flight_paths.only_selected and not selected:
|
||||
continue
|
||||
self.draw_flight_plan(scene, package_model.package, flight,
|
||||
selected)
|
||||
self.draw_flight_plan(scene, flight, selected)
|
||||
|
||||
def draw_flight_plan(self, scene: QGraphicsScene, package: Package,
|
||||
flight: Flight, selected: bool) -> None:
|
||||
def draw_flight_plan(self, scene: QGraphicsScene, flight: Flight,
|
||||
selected: bool) -> None:
|
||||
is_player = flight.from_cp.captured
|
||||
pos = self._transform_point(flight.from_cp.position)
|
||||
|
||||
@@ -310,7 +309,7 @@ class QLiberationMap(QGraphicsView):
|
||||
FlightWaypointType.TARGET_POINT,
|
||||
FlightWaypointType.TARGET_SHIP,
|
||||
)
|
||||
for idx, point in enumerate(flight.points):
|
||||
for idx, point in enumerate(flight.flight_plan.waypoints[1:]):
|
||||
new_pos = self._transform_point(Point(point.x, point.y))
|
||||
self.draw_flight_path(scene, prev_pos, new_pos, is_player,
|
||||
selected)
|
||||
@@ -321,8 +320,8 @@ class QLiberationMap(QGraphicsView):
|
||||
# Don't draw dozens of targets over each other.
|
||||
continue
|
||||
drew_target = True
|
||||
self.draw_waypoint_info(scene, idx + 1, point, new_pos, package,
|
||||
flight)
|
||||
self.draw_waypoint_info(scene, idx + 1, point, new_pos,
|
||||
flight.flight_plan)
|
||||
prev_pos = tuple(new_pos)
|
||||
self.draw_flight_path(scene, prev_pos, pos, is_player, selected)
|
||||
|
||||
@@ -337,21 +336,21 @@ class QLiberationMap(QGraphicsView):
|
||||
|
||||
def draw_waypoint_info(self, scene: QGraphicsScene, number: int,
|
||||
waypoint: FlightWaypoint, position: Tuple[int, int],
|
||||
package: Package, flight: Flight) -> None:
|
||||
timing = PackageWaypointTiming.for_package(package)
|
||||
flight_plan: FlightPlan) -> None:
|
||||
|
||||
altitude = meter_to_feet(waypoint.alt)
|
||||
altitude_type = "AGL" if waypoint.alt_type == "RADIO" else "MSL"
|
||||
|
||||
prefix = "TOT"
|
||||
time = timing.tot_for_waypoint(flight, waypoint)
|
||||
time = flight_plan.tot_for_waypoint(waypoint)
|
||||
if time is None:
|
||||
prefix = "Depart"
|
||||
time = timing.depart_time_for_waypoint(waypoint, flight)
|
||||
time = flight_plan.depart_time_for_waypoint(waypoint)
|
||||
if time is None:
|
||||
tot = ""
|
||||
else:
|
||||
tot = f"{prefix} T+{datetime.timedelta(seconds=time)}"
|
||||
time = datetime.timedelta(seconds=int(time.total_seconds()))
|
||||
tot = f"{prefix} T+{time}"
|
||||
|
||||
pen = QPen(QColor("black"), 0.3)
|
||||
brush = QColor("white")
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Dialogs for creating and editing ATO packages."""
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
from typing import Optional
|
||||
|
||||
from PySide2.QtCore import QItemSelection, QTime, Signal
|
||||
@@ -118,7 +119,7 @@ class QPackageDialog(QDialog):
|
||||
return self.game_model.game
|
||||
|
||||
def tot_qtime(self) -> QTime:
|
||||
delay = self.package_model.package.time_over_target
|
||||
delay = int(self.package_model.package.time_over_target.total_seconds())
|
||||
hours = delay // 3600
|
||||
minutes = delay // 60 % 60
|
||||
seconds = delay % 60
|
||||
@@ -137,11 +138,11 @@ 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(seconds)
|
||||
self.package_model.update_tot(timedelta(seconds=seconds))
|
||||
|
||||
def reset_tot(self) -> None:
|
||||
if not list(self.package_model.flights):
|
||||
self.package_model.update_tot(0)
|
||||
self.package_model.update_tot(timedelta())
|
||||
else:
|
||||
self.package_model.update_tot(
|
||||
TotEstimator(self.package_model.package).earliest_tot())
|
||||
|
||||
@@ -19,7 +19,8 @@ class QFlightDepartureDisplay(QGroupBox):
|
||||
layout.addLayout(departure_row)
|
||||
|
||||
estimator = TotEstimator(package)
|
||||
delay = datetime.timedelta(seconds=estimator.mission_start_time(flight))
|
||||
delay = datetime.timedelta(
|
||||
seconds=int(estimator.mission_start_time(flight).total_seconds()))
|
||||
|
||||
departure_row.addWidget(QLabel(
|
||||
f"Departing from <b>{flight.from_cp.name}</b>"
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import datetime
|
||||
import itertools
|
||||
|
||||
from PySide2.QtCore import QItemSelectionModel, QPoint
|
||||
@@ -6,7 +5,6 @@ from PySide2.QtGui import QStandardItem, QStandardItemModel
|
||||
from PySide2.QtWidgets import QHeaderView, QTableView
|
||||
|
||||
from game.utils import meter_to_feet
|
||||
from gen.aircraft import PackageWaypointTiming
|
||||
from gen.ato import Package
|
||||
from gen.flights.flight import Flight, FlightWaypoint
|
||||
from qt_ui.windows.mission.flight.waypoints.QFlightWaypointItem import \
|
||||
@@ -43,8 +41,6 @@ class QFlightWaypointList(QTableView):
|
||||
|
||||
self.model.setHorizontalHeaderLabels(["Name", "Alt", "TOT/DEPART"])
|
||||
|
||||
timing = PackageWaypointTiming.for_package(self.package)
|
||||
|
||||
# The first waypoint is set up by pydcs at mission generation time, so
|
||||
# we need to add that waypoint manually.
|
||||
takeoff = FlightWaypoint(self.flight.from_cp.position.x,
|
||||
@@ -55,13 +51,12 @@ class QFlightWaypointList(QTableView):
|
||||
|
||||
waypoints = itertools.chain([takeoff], self.flight.points)
|
||||
for row, waypoint in enumerate(waypoints):
|
||||
self.add_waypoint_row(row, self.flight, waypoint, timing)
|
||||
self.add_waypoint_row(row, self.flight, waypoint)
|
||||
self.selectionModel().setCurrentIndex(self.indexAt(QPoint(1, 1)),
|
||||
QItemSelectionModel.Select)
|
||||
|
||||
def add_waypoint_row(self, row: int, flight: Flight,
|
||||
waypoint: FlightWaypoint,
|
||||
timing: PackageWaypointTiming) -> None:
|
||||
waypoint: FlightWaypoint) -> None:
|
||||
self.model.insertRow(self.model.rowCount())
|
||||
|
||||
self.model.setItem(row, 0, QWaypointItem(waypoint, row))
|
||||
@@ -72,18 +67,18 @@ class QFlightWaypointList(QTableView):
|
||||
altitude_item.setEditable(False)
|
||||
self.model.setItem(row, 1, altitude_item)
|
||||
|
||||
tot = self.tot_text(flight, waypoint, timing)
|
||||
tot = self.tot_text(flight, waypoint)
|
||||
tot_item = QStandardItem(tot)
|
||||
tot_item.setEditable(False)
|
||||
self.model.setItem(row, 2, tot_item)
|
||||
|
||||
def tot_text(self, flight: Flight, waypoint: FlightWaypoint,
|
||||
timing: PackageWaypointTiming) -> str:
|
||||
@staticmethod
|
||||
def tot_text(flight: Flight, waypoint: FlightWaypoint) -> str:
|
||||
prefix = ""
|
||||
time = timing.tot_for_waypoint(flight, waypoint)
|
||||
time = flight.flight_plan.tot_for_waypoint(waypoint)
|
||||
if time is None:
|
||||
prefix = "Depart "
|
||||
time = timing.depart_time_for_waypoint(waypoint, self.flight)
|
||||
time = flight.flight_plan.depart_time_for_waypoint(waypoint)
|
||||
if time is None:
|
||||
return ""
|
||||
return f"{prefix}T+{datetime.timedelta(seconds=time)}"
|
||||
return f"{prefix}T+{time}"
|
||||
|
||||
Reference in New Issue
Block a user