mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Airlift support.
UI isn't finished. Bulk transfers where the player doesn't care what aircraft get used work (though they're chosen with no thought at all), but being able to plan your own airlift flight isn't here yet. Cargo planes are not implemented yet. No way to view the cargo of a flight (will come with the cargo flight planning UI). The airlift flight/package creation should probably be moved out of the UI and into the game code. AI doesn't use these yet. https://github.com/Khopa/dcs_liberation/issues/825
This commit is contained in:
@@ -15,7 +15,7 @@ from PySide2.QtGui import QIcon
|
||||
from game import db
|
||||
from game.game import Game
|
||||
from game.theater.missiontarget import MissionTarget
|
||||
from game.transfers import RoadTransferOrder
|
||||
from game.transfers import TransferOrder
|
||||
from gen.ato import AirTaskingOrder, Package
|
||||
from gen.flights.flight import Flight
|
||||
from gen.flights.traveltime import TotEstimator
|
||||
@@ -52,8 +52,13 @@ class DeletableChildModelManager:
|
||||
|
||||
ModelDict = Dict[DataType, ModelType]
|
||||
|
||||
def __init__(self, create_model: Callable[[DataType], ModelType]) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
create_model: Callable[[DataType, GameModel], ModelType],
|
||||
game_model: GameModel,
|
||||
) -> None:
|
||||
self.create_model = create_model
|
||||
self.game_model = game_model
|
||||
self.models: DeletableChildModelManager.ModelDict = {}
|
||||
|
||||
def acquire(self, data: DataType) -> ModelType:
|
||||
@@ -64,7 +69,7 @@ class DeletableChildModelManager:
|
||||
"""
|
||||
if data in self.models:
|
||||
return self.models[data]
|
||||
model = self.create_model(data)
|
||||
model = self.create_model(data, self.game_model)
|
||||
self.models[data] = model
|
||||
return model
|
||||
|
||||
@@ -105,9 +110,10 @@ class PackageModel(QAbstractListModel):
|
||||
|
||||
tot_changed = Signal()
|
||||
|
||||
def __init__(self, package: Package) -> None:
|
||||
def __init__(self, package: Package, game_model: GameModel) -> None:
|
||||
super().__init__()
|
||||
self.package = package
|
||||
self.game_model = game_model
|
||||
|
||||
def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
|
||||
return len(self.package.flights)
|
||||
@@ -154,14 +160,15 @@ class PackageModel(QAbstractListModel):
|
||||
self.delete_flight(self.flight_at_index(index))
|
||||
|
||||
def delete_flight(self, flight: Flight) -> None:
|
||||
"""Removes the given flight from the package.
|
||||
|
||||
If the flight is using claimed inventory, the caller is responsible for
|
||||
returning that inventory.
|
||||
"""
|
||||
"""Removes the given flight from the package."""
|
||||
index = self.package.flights.index(flight)
|
||||
self.beginRemoveRows(QModelIndex(), index, index)
|
||||
self.package.remove_flight(flight)
|
||||
if flight.cargo is None:
|
||||
self.game_model.game.aircraft_inventory.return_from_flight(flight)
|
||||
self.package.remove_flight(flight)
|
||||
else:
|
||||
# Deleted transfers will clean up after themselves.
|
||||
self.game_model.transfer_model.cancel_transfer(flight.cargo)
|
||||
self.endRemoveRows()
|
||||
self.update_tot()
|
||||
|
||||
@@ -210,11 +217,15 @@ class AtoModel(QAbstractListModel):
|
||||
|
||||
client_slots_changed = Signal()
|
||||
|
||||
def __init__(self, game: Optional[Game], ato: AirTaskingOrder) -> None:
|
||||
def __init__(self, game_model: GameModel, ato: AirTaskingOrder) -> None:
|
||||
super().__init__()
|
||||
self.game = game
|
||||
self.game_model = game_model
|
||||
self.ato = ato
|
||||
self.package_models = DeletableChildModelManager(PackageModel)
|
||||
self.package_models = DeletableChildModelManager(PackageModel, game_model)
|
||||
|
||||
@property
|
||||
def game(self) -> Optional[Game]:
|
||||
return self.game_model.game
|
||||
|
||||
def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
|
||||
return len(self.ato.packages)
|
||||
@@ -249,6 +260,8 @@ class AtoModel(QAbstractListModel):
|
||||
self.ato.remove_package(package)
|
||||
for flight in package.flights:
|
||||
self.game.aircraft_inventory.return_from_flight(flight)
|
||||
if flight.cargo is not None:
|
||||
self.game_model.transfer_model.cancel_transfer(flight.cargo)
|
||||
self.endRemoveRows()
|
||||
# noinspection PyUnresolvedReferences
|
||||
self.client_slots_changed.emit()
|
||||
@@ -257,20 +270,19 @@ class AtoModel(QAbstractListModel):
|
||||
"""Returns the package at the given index."""
|
||||
return self.ato.packages[index.row()]
|
||||
|
||||
def replace_from_game(self, game: Optional[Game], player: bool) -> None:
|
||||
def replace_from_game(self, player: bool) -> None:
|
||||
"""Updates the ATO object to match the updated game object.
|
||||
|
||||
If the game is None (as is the case when no game has been loaded), an
|
||||
empty ATO will be used.
|
||||
"""
|
||||
self.beginResetModel()
|
||||
self.game = game
|
||||
self.package_models.clear()
|
||||
if self.game is not None:
|
||||
if player:
|
||||
self.ato = game.blue_ato
|
||||
self.ato = self.game.blue_ato
|
||||
else:
|
||||
self.ato = game.red_ato
|
||||
self.ato = self.game.red_ato
|
||||
else:
|
||||
self.ato = AirTaskingOrder()
|
||||
self.endResetModel()
|
||||
@@ -313,7 +325,7 @@ class TransferModel(QAbstractListModel):
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def text_for_transfer(transfer: RoadTransferOrder) -> str:
|
||||
def text_for_transfer(transfer: TransferOrder) -> str:
|
||||
"""Returns the text that should be displayed for the transfer."""
|
||||
count = sum(transfer.units.values())
|
||||
origin = transfer.origin.name
|
||||
@@ -321,11 +333,11 @@ class TransferModel(QAbstractListModel):
|
||||
return f"Transfer of {count} units from {origin} to {destination}"
|
||||
|
||||
@staticmethod
|
||||
def icon_for_transfer(_transfer: RoadTransferOrder) -> Optional[QIcon]:
|
||||
def icon_for_transfer(_transfer: TransferOrder) -> Optional[QIcon]:
|
||||
"""Returns the icon that should be displayed for the transfer."""
|
||||
return None
|
||||
|
||||
def new_transfer(self, transfer: RoadTransferOrder) -> None:
|
||||
def new_transfer(self, transfer: TransferOrder) -> None:
|
||||
"""Updates the game with the new unit transfer."""
|
||||
self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
|
||||
# TODO: Needs to regenerate base inventory tab.
|
||||
@@ -334,13 +346,17 @@ class TransferModel(QAbstractListModel):
|
||||
|
||||
def cancel_transfer_at_index(self, index: QModelIndex) -> None:
|
||||
"""Cancels the planned unit transfer at the given index."""
|
||||
transfer = self.transfer_at_index(index)
|
||||
self.beginRemoveRows(QModelIndex(), index.row(), index.row())
|
||||
self.cancel_transfer(self.transfer_at_index(index))
|
||||
|
||||
def cancel_transfer(self, transfer: TransferOrder) -> None:
|
||||
"""Cancels the planned unit transfer at the given index."""
|
||||
index = self.game_model.game.transfers.index_of_transfer(transfer)
|
||||
self.beginRemoveRows(QModelIndex(), index, index)
|
||||
# TODO: Needs to regenerate base inventory tab.
|
||||
self.game_model.game.transfers.cancel_transfer(transfer)
|
||||
self.endRemoveRows()
|
||||
|
||||
def transfer_at_index(self, index: QModelIndex) -> RoadTransferOrder:
|
||||
def transfer_at_index(self, index: QModelIndex) -> TransferOrder:
|
||||
"""Returns the transfer located at the given index."""
|
||||
return self.game_model.game.transfers.transfer_at_index(index.row())
|
||||
|
||||
@@ -356,11 +372,11 @@ class GameModel:
|
||||
self.game: Optional[Game] = game
|
||||
self.transfer_model = TransferModel(self)
|
||||
if self.game is None:
|
||||
self.ato_model = AtoModel(self.game, AirTaskingOrder())
|
||||
self.red_ato_model = AtoModel(self.game, AirTaskingOrder())
|
||||
self.ato_model = AtoModel(self, AirTaskingOrder())
|
||||
self.red_ato_model = AtoModel(self, AirTaskingOrder())
|
||||
else:
|
||||
self.ato_model = AtoModel(self.game, self.game.blue_ato)
|
||||
self.red_ato_model = AtoModel(self.game, self.game.red_ato)
|
||||
self.ato_model = AtoModel(self, self.game.blue_ato)
|
||||
self.red_ato_model = AtoModel(self, self.game.red_ato)
|
||||
|
||||
def set(self, game: Optional[Game]) -> None:
|
||||
"""Updates the managed Game object.
|
||||
@@ -371,5 +387,5 @@ class GameModel:
|
||||
loaded.
|
||||
"""
|
||||
self.game = game
|
||||
self.ato_model.replace_from_game(self.game, player=True)
|
||||
self.red_ato_model.replace_from_game(self.game, player=False)
|
||||
self.ato_model.replace_from_game(player=True)
|
||||
self.red_ato_model.replace_from_game(player=False)
|
||||
|
||||
@@ -204,7 +204,6 @@ class QFlightList(QListView):
|
||||
)
|
||||
|
||||
def delete_flight(self, index: QModelIndex) -> None:
|
||||
self.game_model.game.aircraft_inventory.return_from_flight(self.selected_item)
|
||||
self.package_model.delete_flight_at_index(index)
|
||||
GameUpdateSignal.get_instance().redraw_flight_paths()
|
||||
|
||||
|
||||
@@ -22,8 +22,7 @@ from PySide2.QtWidgets import (
|
||||
QVBoxLayout,
|
||||
)
|
||||
|
||||
from game.theater.supplyroutes import SupplyRoute
|
||||
from game.transfers import RoadTransferOrder
|
||||
from game.transfers import TransferOrder
|
||||
from qt_ui.delegate_helpers import painter_context
|
||||
from qt_ui.models import GameModel, TransferModel
|
||||
|
||||
@@ -43,20 +42,14 @@ class TransferDelegate(QStyledItemDelegate):
|
||||
return font
|
||||
|
||||
@staticmethod
|
||||
def transfer(index: QModelIndex) -> RoadTransferOrder:
|
||||
def transfer(index: QModelIndex) -> TransferOrder:
|
||||
return index.data(TransferModel.TransferRole)
|
||||
|
||||
def first_row_text(self, index: QModelIndex) -> str:
|
||||
return self.transfer_model.data(index, Qt.DisplayRole)
|
||||
|
||||
def second_row_text(self, index: QModelIndex) -> str:
|
||||
transfer = self.transfer(index)
|
||||
path = transfer.path()
|
||||
if len(path) == 1:
|
||||
turns = "1 turn"
|
||||
else:
|
||||
turns = f"{len(path)} turns"
|
||||
return f"Currently at {transfer.position}. Arrives at destination in {turns}."
|
||||
return self.transfer(index).description
|
||||
|
||||
def paint(
|
||||
self, painter: QPainter, option: QStyleOptionViewItem, index: QModelIndex
|
||||
|
||||
@@ -15,6 +15,7 @@ from PySide2.QtWidgets import (
|
||||
QGroupBox,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QMessageBox,
|
||||
QPushButton,
|
||||
QScrollArea,
|
||||
QSizePolicy,
|
||||
@@ -23,12 +24,16 @@ from PySide2.QtWidgets import (
|
||||
QWidget,
|
||||
)
|
||||
from dcs.task import PinpointStrike
|
||||
from dcs.unittype import UnitType
|
||||
from dcs.unittype import FlyingType, UnitType, VehicleType
|
||||
|
||||
from game import Game, db
|
||||
from game.inventory import ControlPointAircraftInventory
|
||||
from game.theater import ControlPoint, SupplyRoute
|
||||
from game.transfers import RoadTransferOrder
|
||||
from qt_ui.models import GameModel
|
||||
from game.transfers import AirliftOrder, RoadTransferOrder
|
||||
from gen.ato import Package
|
||||
from gen.flights.flight import Flight, FlightType
|
||||
from gen.flights.flightplan import FlightPlanBuilder, PlanningError
|
||||
from qt_ui.models import GameModel, PackageModel
|
||||
from qt_ui.widgets.QLabeledWidget import QLabeledWidget
|
||||
|
||||
|
||||
@@ -111,7 +116,7 @@ class TransferOptionsPanel(QVBoxLayout):
|
||||
self.addLayout(QLabeledWidget("Destination:", self.source_combo_box))
|
||||
self.airlift = QCheckBox()
|
||||
self.airlift.toggled.connect(self.set_airlift)
|
||||
self.addLayout(QLabeledWidget("Airlift (non-functional):", self.airlift))
|
||||
self.addLayout(QLabeledWidget("Airlift (WIP):", self.airlift))
|
||||
self.addWidget(
|
||||
QLabel(
|
||||
f"{airlift_capacity.total} airlift capacity "
|
||||
@@ -363,12 +368,112 @@ class NewUnitTransferDialog(QDialog):
|
||||
)
|
||||
transfers[unit_type] = count
|
||||
|
||||
self.game_model.transfer_model.new_transfer(
|
||||
RoadTransferOrder(
|
||||
if self.dest_panel.airlift.isChecked():
|
||||
self.create_package_for_airlift(
|
||||
self.transfer_panel.cp,
|
||||
self.dest_panel.current,
|
||||
transfers,
|
||||
)
|
||||
else:
|
||||
transfer = RoadTransferOrder(
|
||||
player=True,
|
||||
origin=self.transfer_panel.cp,
|
||||
destination=self.dest_panel.current,
|
||||
units=transfers,
|
||||
)
|
||||
)
|
||||
self.game_model.transfer_model.new_transfer(transfer)
|
||||
self.close()
|
||||
|
||||
@staticmethod
|
||||
def take_units(
|
||||
units: Dict[Type[VehicleType], int], count: int
|
||||
) -> Dict[Type[VehicleType], int]:
|
||||
taken = {}
|
||||
for unit_type, remaining in units.items():
|
||||
take = min(remaining, count)
|
||||
count -= take
|
||||
units[unit_type] -= take
|
||||
taken[unit_type] = take
|
||||
if not count:
|
||||
break
|
||||
return taken
|
||||
|
||||
def create_airlift_flight(
|
||||
self,
|
||||
game: Game,
|
||||
package_model: PackageModel,
|
||||
unit_type: Type[FlyingType],
|
||||
inventory: ControlPointAircraftInventory,
|
||||
needed_capacity: int,
|
||||
pickup: ControlPoint,
|
||||
drop_off: ControlPoint,
|
||||
units: Dict[Type[VehicleType], int],
|
||||
) -> int:
|
||||
available = inventory.available(unit_type)
|
||||
# 4 is the max flight size in DCS.
|
||||
flight_size = min(needed_capacity, available, 4)
|
||||
flight = Flight(
|
||||
package_model.package,
|
||||
game.player_country,
|
||||
unit_type,
|
||||
flight_size,
|
||||
FlightType.TRANSPORT,
|
||||
game.settings.default_start_type,
|
||||
departure=inventory.control_point,
|
||||
arrival=inventory.control_point,
|
||||
divert=None,
|
||||
)
|
||||
|
||||
transfer = AirliftOrder(
|
||||
player=True,
|
||||
origin=pickup,
|
||||
destination=drop_off,
|
||||
units=self.take_units(units, flight_size),
|
||||
flight=flight,
|
||||
)
|
||||
flight.cargo = transfer
|
||||
|
||||
package_model.add_flight(flight)
|
||||
planner = FlightPlanBuilder(game, package_model.package, is_player=True)
|
||||
try:
|
||||
planner.populate_flight_plan(flight)
|
||||
except PlanningError as ex:
|
||||
package_model.delete_flight(flight)
|
||||
logging.exception("Could not create flight")
|
||||
QMessageBox.critical(
|
||||
self, "Could not create flight", str(ex), QMessageBox.Ok
|
||||
)
|
||||
game.aircraft_inventory.claim_for_flight(flight)
|
||||
self.game_model.transfer_model.new_transfer(transfer)
|
||||
return flight_size
|
||||
|
||||
def create_package_for_airlift(
|
||||
self,
|
||||
pickup: ControlPoint,
|
||||
drop_off: ControlPoint,
|
||||
units: Dict[Type[VehicleType], int],
|
||||
) -> None:
|
||||
package = Package(target=drop_off, auto_asap=True)
|
||||
package_model = PackageModel(package, self.game_model)
|
||||
|
||||
needed_capacity = sum(c for c in units.values())
|
||||
game = self.game_model.game
|
||||
for cp in game.theater.player_points():
|
||||
inventory = game.aircraft_inventory.for_control_point(cp)
|
||||
for unit_type, available in inventory.all_aircraft:
|
||||
if unit_type.helicopter:
|
||||
while available and needed_capacity:
|
||||
flight_size = self.create_airlift_flight(
|
||||
self.game_model.game,
|
||||
package_model,
|
||||
unit_type,
|
||||
inventory,
|
||||
needed_capacity,
|
||||
pickup,
|
||||
drop_off,
|
||||
units,
|
||||
)
|
||||
available -= flight_size
|
||||
needed_capacity -= flight_size
|
||||
package_model.update_tot()
|
||||
self.game_model.ato_model.add_package(package)
|
||||
|
||||
@@ -185,7 +185,6 @@ class QPackageDialog(QDialog):
|
||||
try:
|
||||
planner.populate_flight_plan(flight)
|
||||
except PlanningError as ex:
|
||||
self.game.aircraft_inventory.return_from_flight(flight)
|
||||
self.package_model.delete_flight(flight)
|
||||
logging.exception("Could not create flight")
|
||||
QMessageBox.critical(
|
||||
@@ -201,7 +200,6 @@ class QPackageDialog(QDialog):
|
||||
if flight is None:
|
||||
logging.error(f"Cannot delete flight when no flight is selected.")
|
||||
return
|
||||
self.game.aircraft_inventory.return_from_flight(flight)
|
||||
self.package_model.delete_flight(flight)
|
||||
# noinspection PyUnresolvedReferences
|
||||
self.package_changed.emit()
|
||||
@@ -216,7 +214,9 @@ class QNewPackageDialog(QPackageDialog):
|
||||
def __init__(
|
||||
self, game_model: GameModel, model: AtoModel, target: MissionTarget, parent=None
|
||||
) -> None:
|
||||
super().__init__(game_model, PackageModel(Package(target)), parent=parent)
|
||||
super().__init__(
|
||||
game_model, PackageModel(Package(target), game_model), parent=parent
|
||||
)
|
||||
self.ato_model = model
|
||||
|
||||
self.save_button = QPushButton("Save")
|
||||
|
||||
Reference in New Issue
Block a user