mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Implement manual squadron transfers.
Lightly tested but seems to work fine. https://github.com/dcs-liberation/dcs_liberation/issues/1145
This commit is contained in:
parent
cd15de6d42
commit
c2e5cba061
@ -53,6 +53,9 @@ class Squadron:
|
||||
settings: Settings = field(hash=False, compare=False)
|
||||
|
||||
location: ControlPoint
|
||||
destination: Optional[ControlPoint] = field(
|
||||
init=False, hash=False, compare=False, default=None
|
||||
)
|
||||
|
||||
owned_aircraft: int = field(init=False, hash=False, compare=False, default=0)
|
||||
untasked_aircraft: int = field(init=False, hash=False, compare=False, default=0)
|
||||
@ -168,6 +171,8 @@ class Squadron:
|
||||
self._recruit_pilots(self.settings.squadron_pilot_limit)
|
||||
|
||||
def end_turn(self) -> None:
|
||||
if self.destination is not None:
|
||||
self.relocate_to(self.destination)
|
||||
self.replenish_lost_pilots()
|
||||
self.deliver_orders()
|
||||
|
||||
@ -280,6 +285,8 @@ class Squadron:
|
||||
|
||||
def relocate_to(self, destination: ControlPoint) -> None:
|
||||
self.location = destination
|
||||
if self.location == self.destination:
|
||||
self.destination = None
|
||||
|
||||
def cancel_overflow_orders(self) -> None:
|
||||
if self.pending_deliveries <= 0:
|
||||
@ -297,6 +304,42 @@ class Squadron:
|
||||
def max_fulfillable_aircraft(self) -> int:
|
||||
return max(self.number_of_available_pilots, self.untasked_aircraft)
|
||||
|
||||
@property
|
||||
def expected_size_next_turn(self) -> int:
|
||||
return self.owned_aircraft + self.pending_deliveries
|
||||
|
||||
def plan_relocation(self, destination: ControlPoint) -> None:
|
||||
if destination == self.location:
|
||||
logging.warning(
|
||||
f"Attempted to plan relocation of {self} to current location "
|
||||
f"{destination}. Ignoring."
|
||||
)
|
||||
return
|
||||
if destination == self.destination:
|
||||
logging.warning(
|
||||
f"Attempted to plan relocation of {self} to current destination "
|
||||
f"{destination}. Ignoring."
|
||||
)
|
||||
return
|
||||
|
||||
if self.expected_size_next_turn >= destination.unclaimed_parking():
|
||||
raise RuntimeError(f"Not enough parking for {self} at {destination}.")
|
||||
if not destination.can_operate(self.aircraft):
|
||||
raise RuntimeError(f"{self} cannot operate at {destination}.")
|
||||
self.destination = destination
|
||||
|
||||
def cancel_relocation(self) -> None:
|
||||
if self.destination is None:
|
||||
logging.warning(
|
||||
f"Attempted to cancel relocation of squadron with no transfer order. "
|
||||
"Ignoring."
|
||||
)
|
||||
return
|
||||
|
||||
if self.expected_size_next_turn >= self.location.unclaimed_parking():
|
||||
raise RuntimeError(f"Not enough parking for {self} at {self.location}.")
|
||||
self.destination = None
|
||||
|
||||
@classmethod
|
||||
def create_from(
|
||||
cls,
|
||||
|
||||
@ -728,13 +728,19 @@ class ControlPoint(MissionTarget, ABC):
|
||||
def allocated_aircraft(self) -> AircraftAllocations:
|
||||
present: dict[AircraftType, int] = defaultdict(int)
|
||||
on_order: dict[AircraftType, int] = defaultdict(int)
|
||||
transferring: dict[AircraftType, int] = defaultdict(int)
|
||||
for squadron in self.squadrons:
|
||||
present[squadron.aircraft] += squadron.owned_aircraft
|
||||
# TODO: Only if this is the squadron destination, not location.
|
||||
on_order[squadron.aircraft] += squadron.pending_deliveries
|
||||
if squadron.destination is None:
|
||||
on_order[squadron.aircraft] += squadron.pending_deliveries
|
||||
else:
|
||||
transferring[squadron.aircraft] -= squadron.owned_aircraft
|
||||
for squadron in self.coalition.air_wing.iter_squadrons():
|
||||
if squadron.destination == self:
|
||||
on_order[squadron.aircraft] += squadron.pending_deliveries
|
||||
transferring[squadron.aircraft] += squadron.owned_aircraft
|
||||
|
||||
# TODO: Implement squadron transfers.
|
||||
return AircraftAllocations(present, on_order, transferring={})
|
||||
return AircraftAllocations(present, on_order, transferring)
|
||||
|
||||
def allocated_ground_units(
|
||||
self, transfers: PendingTransfers
|
||||
|
||||
17
qt_ui/errorreporter.py
Normal file
17
qt_ui/errorreporter.py
Normal file
@ -0,0 +1,17 @@
|
||||
import logging
|
||||
from collections import Iterator
|
||||
from contextlib import contextmanager
|
||||
from typing import Type
|
||||
|
||||
from PySide2.QtWidgets import QDialog, QMessageBox
|
||||
|
||||
|
||||
@contextmanager
|
||||
def report_errors(
|
||||
title: str, parent: QDialog, error_type: Type[Exception] = Exception
|
||||
) -> Iterator[None]:
|
||||
try:
|
||||
yield
|
||||
except error_type as ex:
|
||||
logging.exception(title)
|
||||
QMessageBox().critical(parent, title, str(ex), QMessageBox.Ok)
|
||||
@ -13,6 +13,7 @@ from PySide2.QtWidgets import QApplication, QSplashScreen
|
||||
from dcs.payloads import PayloadDirectories
|
||||
|
||||
from game import Game, VERSION, persistency
|
||||
from game.campaignloader.campaign import Campaign
|
||||
from game.data.weapons import WeaponGroup, Pylon, Weapon
|
||||
from game.db import FACTIONS
|
||||
from game.dcs.aircrafttype import AircraftType
|
||||
@ -27,7 +28,6 @@ from qt_ui import (
|
||||
)
|
||||
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
||||
from qt_ui.windows.QLiberationWindow import QLiberationWindow
|
||||
from game.campaignloader.campaign import Campaign
|
||||
from qt_ui.windows.newgame.QNewGameWizard import DEFAULT_BUDGET
|
||||
from qt_ui.windows.preferences.QLiberationFirstStartWindow import (
|
||||
QLiberationFirstStartWindow,
|
||||
|
||||
@ -10,7 +10,6 @@ from PySide2.QtCore import (
|
||||
)
|
||||
from PySide2.QtGui import QStandardItemModel, QStandardItem, QIcon
|
||||
from PySide2.QtWidgets import (
|
||||
QAbstractItemView,
|
||||
QDialog,
|
||||
QListView,
|
||||
QVBoxLayout,
|
||||
@ -32,38 +31,7 @@ from game.dcs.aircrafttype import AircraftType
|
||||
from game.squadrons import AirWing, Pilot, Squadron
|
||||
from game.theater import ControlPoint, ConflictTheater
|
||||
from gen.flights.flight import FlightType
|
||||
from qt_ui.models import AirWingModel, SquadronModel
|
||||
from qt_ui.uiconstants import AIRCRAFT_ICONS
|
||||
from qt_ui.windows.AirWingDialog import SquadronDelegate
|
||||
from qt_ui.windows.SquadronDialog import SquadronDialog
|
||||
|
||||
|
||||
class SquadronList(QListView):
|
||||
"""List view for displaying the air wing's squadrons."""
|
||||
|
||||
def __init__(self, air_wing_model: AirWingModel) -> None:
|
||||
super().__init__()
|
||||
self.air_wing_model = air_wing_model
|
||||
self.dialog: Optional[SquadronDialog] = None
|
||||
|
||||
self.setIconSize(QSize(91, 24))
|
||||
self.setItemDelegate(SquadronDelegate(self.air_wing_model))
|
||||
self.setModel(self.air_wing_model)
|
||||
self.selectionModel().setCurrentIndex(
|
||||
self.air_wing_model.index(0, 0, QModelIndex()), QItemSelectionModel.Select
|
||||
)
|
||||
|
||||
# self.setIconSize(QSize(91, 24))
|
||||
self.setSelectionBehavior(QAbstractItemView.SelectItems)
|
||||
self.doubleClicked.connect(self.on_double_click)
|
||||
|
||||
def on_double_click(self, index: QModelIndex) -> None:
|
||||
if not index.isValid():
|
||||
return
|
||||
self.dialog = SquadronDialog(
|
||||
SquadronModel(self.air_wing_model.squadron_at_index(index)), self
|
||||
)
|
||||
self.dialog.show()
|
||||
|
||||
|
||||
class AllowedMissionTypeControls(QVBoxLayout):
|
||||
|
||||
@ -17,6 +17,7 @@ from PySide2.QtWidgets import (
|
||||
)
|
||||
|
||||
from game.squadrons import Squadron
|
||||
from game.theater import ConflictTheater
|
||||
from gen.flights.flight import Flight
|
||||
from qt_ui.delegates import TwoColumnRowDelegate
|
||||
from qt_ui.models import GameModel, AirWingModel, SquadronModel
|
||||
@ -56,9 +57,10 @@ class SquadronDelegate(TwoColumnRowDelegate):
|
||||
class SquadronList(QListView):
|
||||
"""List view for displaying the air wing's squadrons."""
|
||||
|
||||
def __init__(self, air_wing_model: AirWingModel) -> None:
|
||||
def __init__(self, air_wing_model: AirWingModel, theater: ConflictTheater) -> None:
|
||||
super().__init__()
|
||||
self.air_wing_model = air_wing_model
|
||||
self.theater = theater
|
||||
self.dialog: Optional[SquadronDialog] = None
|
||||
|
||||
self.setIconSize(QSize(91, 24))
|
||||
@ -76,7 +78,9 @@ class SquadronList(QListView):
|
||||
if not index.isValid():
|
||||
return
|
||||
self.dialog = SquadronDialog(
|
||||
SquadronModel(self.air_wing_model.squadron_at_index(index)), self
|
||||
SquadronModel(self.air_wing_model.squadron_at_index(index)),
|
||||
self.theater,
|
||||
self,
|
||||
)
|
||||
self.dialog.show()
|
||||
|
||||
@ -194,7 +198,10 @@ class AirWingTabs(QTabWidget):
|
||||
def __init__(self, game_model: GameModel) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.addTab(SquadronList(game_model.blue_air_wing_model), "Squadrons")
|
||||
self.addTab(
|
||||
SquadronList(game_model.blue_air_wing_model, game_model.game.theater),
|
||||
"Squadrons",
|
||||
)
|
||||
self.addTab(AirInventoryView(game_model), "Inventory")
|
||||
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import logging
|
||||
from typing import Callable
|
||||
from typing import Callable, Iterator, Optional
|
||||
|
||||
from PySide2.QtCore import (
|
||||
QItemSelectionModel,
|
||||
@ -16,11 +16,14 @@ from PySide2.QtWidgets import (
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QCheckBox,
|
||||
QComboBox,
|
||||
)
|
||||
|
||||
from game.squadrons import Pilot
|
||||
from game.squadrons import Pilot, Squadron
|
||||
from game.theater import ControlPoint, ConflictTheater
|
||||
from gen.flights.flight import FlightType
|
||||
from qt_ui.delegates import TwoColumnRowDelegate
|
||||
from qt_ui.errorreporter import report_errors
|
||||
from qt_ui.models import SquadronModel
|
||||
|
||||
|
||||
@ -90,10 +93,50 @@ class AutoAssignedTaskControls(QVBoxLayout):
|
||||
self.squadron_model.set_auto_assignable(task, checked)
|
||||
|
||||
|
||||
class SquadronDestinationComboBox(QComboBox):
|
||||
def __init__(self, squadron: Squadron, theater: ConflictTheater) -> None:
|
||||
super().__init__()
|
||||
self.squadron = squadron
|
||||
self.theater = theater
|
||||
|
||||
room = squadron.location.unclaimed_parking()
|
||||
self.addItem(
|
||||
f"Remain at {squadron.location} (room for {room} more aircraft)", None
|
||||
)
|
||||
selected_index: Optional[int] = None
|
||||
for idx, destination in enumerate(sorted(self.iter_destinations(), key=str), 1):
|
||||
if destination == squadron.destination:
|
||||
selected_index = idx
|
||||
room = destination.unclaimed_parking()
|
||||
self.addItem(
|
||||
f"Transfer to {destination} (room for {room} more aircraft)",
|
||||
destination,
|
||||
)
|
||||
|
||||
if squadron.destination is None:
|
||||
selected_index = 0
|
||||
|
||||
if selected_index is not None:
|
||||
self.setCurrentIndex(selected_index)
|
||||
|
||||
def iter_destinations(self) -> Iterator[ControlPoint]:
|
||||
size = self.squadron.expected_size_next_turn
|
||||
for control_point in self.theater.control_points_for(self.squadron.player):
|
||||
if control_point == self:
|
||||
continue
|
||||
if not control_point.can_operate(self.squadron.aircraft):
|
||||
continue
|
||||
if control_point.unclaimed_parking() < size:
|
||||
continue
|
||||
yield control_point
|
||||
|
||||
|
||||
class SquadronDialog(QDialog):
|
||||
"""Dialog window showing a squadron."""
|
||||
|
||||
def __init__(self, squadron_model: SquadronModel, parent) -> None:
|
||||
def __init__(
|
||||
self, squadron_model: SquadronModel, theater: ConflictTheater, parent
|
||||
) -> None:
|
||||
super().__init__(parent)
|
||||
self.squadron_model = squadron_model
|
||||
|
||||
@ -117,6 +160,15 @@ class SquadronDialog(QDialog):
|
||||
columns.addWidget(self.pilot_list)
|
||||
|
||||
button_panel = QHBoxLayout()
|
||||
|
||||
self.transfer_destination = SquadronDestinationComboBox(
|
||||
squadron_model.squadron, theater
|
||||
)
|
||||
self.transfer_destination.currentIndexChanged.connect(
|
||||
self.on_destination_changed
|
||||
)
|
||||
button_panel.addWidget(self.transfer_destination)
|
||||
|
||||
button_panel.addStretch()
|
||||
layout.addLayout(button_panel)
|
||||
|
||||
@ -132,6 +184,18 @@ class SquadronDialog(QDialog):
|
||||
self.toggle_leave_button.clicked.connect(self.toggle_leave)
|
||||
button_panel.addWidget(self.toggle_leave_button, alignment=Qt.AlignRight)
|
||||
|
||||
@property
|
||||
def squadron(self) -> Squadron:
|
||||
return self.squadron_model.squadron
|
||||
|
||||
def on_destination_changed(self, index: int) -> None:
|
||||
with report_errors("Could not change squadron destination", self):
|
||||
destination = self.transfer_destination.itemData(index)
|
||||
if destination is None:
|
||||
self.squadron.cancel_relocation()
|
||||
else:
|
||||
self.squadron.plan_relocation(destination)
|
||||
|
||||
def check_disabled_button_states(
|
||||
self, button: QPushButton, index: QModelIndex
|
||||
) -> bool:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user