mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
This Pull Request lets users plan Tanker flights. Features: - Introduction of `Refueling` flight type. - Tankers can be purchased at airbases and carriers. - Tankers get planned by AI. - Tankers are planned from airbases and at aircraft carriers. - Tankers aim to be at high, fast, and 70 miles from the nearest threat. (A10s won't be able to tank) - Tankers racetrack orbit for one hour. - Optional Tickbox to enable legacy tankers. - S-3B Tanker added to factions. - KC-130 MPRS added to factions. - Kneeboard shows planned tankers, their tacans, and radios. Limitations: - AI doesn't know whether to plan probe and drogue or boom refueling tankers. - User can't choose tanker speed. Heavily loaded aircraft may have trouble. - User can't choose tanker altitude. A-10s will not make it to high altitude. Problems: - Tanker callsigns do not increment, see attached image. (Investigated: Need to use `FlyingType.callsign_dict`, instead of just `FlyingType.callsign`. This seems like it might be significant work to do.). - Having a flight of two or more tankers only spawns one tanker. - Let me know if you have a solution, or feel free to commit one. https://user-images.githubusercontent.com/74509817/120909602-d7bc3680-c633-11eb-80d7-eccd4e095770.png
183 lines
6.5 KiB
Python
183 lines
6.5 KiB
Python
import logging
|
|
from typing import Set, Type
|
|
|
|
from PySide2.QtCore import Qt
|
|
from PySide2.QtWidgets import (
|
|
QFrame,
|
|
QGridLayout,
|
|
QHBoxLayout,
|
|
QLabel,
|
|
QMessageBox,
|
|
QScrollArea,
|
|
QVBoxLayout,
|
|
QWidget,
|
|
)
|
|
from dcs.helicopters import helicopter_map
|
|
from dcs.unittype import FlyingType, UnitType
|
|
|
|
from game import db
|
|
from game.theater import ControlPoint, ControlPointType
|
|
from qt_ui.models import GameModel
|
|
from qt_ui.uiconstants import ICONS
|
|
from qt_ui.windows.basemenu.QRecruitBehaviour import QRecruitBehaviour
|
|
|
|
|
|
class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour):
|
|
def __init__(self, cp: ControlPoint, game_model: GameModel) -> None:
|
|
QFrame.__init__(self)
|
|
self.cp = cp
|
|
self.game_model = game_model
|
|
|
|
self.bought_amount_labels = {}
|
|
self.existing_units_labels = {}
|
|
|
|
# Determine maximum number of aircrafts that can be bought
|
|
self.set_maximum_units(self.cp.total_aircraft_parking)
|
|
|
|
self.bought_amount_labels = {}
|
|
self.existing_units_labels = {}
|
|
|
|
self.hangar_status = QHangarStatus(game_model, self.cp)
|
|
|
|
self.init_ui()
|
|
|
|
def init_ui(self):
|
|
main_layout = QVBoxLayout()
|
|
|
|
scroll_content = QWidget()
|
|
task_box_layout = QGridLayout()
|
|
row = 0
|
|
|
|
unit_types: Set[Type[FlyingType]] = set()
|
|
for unit_type in self.game_model.game.player_faction.aircrafts:
|
|
if not issubclass(unit_type, FlyingType):
|
|
raise RuntimeError(f"Non-flying aircraft found in faction: {unit_type}")
|
|
if self.cp.is_carrier and unit_type not in db.CARRIER_CAPABLE:
|
|
continue
|
|
if self.cp.is_lha and unit_type not in db.LHA_CAPABLE:
|
|
continue
|
|
if (
|
|
self.cp.cptype in [ControlPointType.FOB, ControlPointType.FARP]
|
|
and unit_type not in helicopter_map.values()
|
|
):
|
|
continue
|
|
unit_types.add(unit_type)
|
|
|
|
sorted_units = sorted(
|
|
unit_types,
|
|
key=lambda u: db.unit_get_expanded_info(
|
|
self.game_model.game.player_country, u, "name"
|
|
),
|
|
)
|
|
for unit_type in sorted_units:
|
|
row = self.add_purchase_row(unit_type, task_box_layout, row)
|
|
stretch = QVBoxLayout()
|
|
stretch.addStretch()
|
|
task_box_layout.addLayout(stretch, row, 0)
|
|
|
|
scroll_content.setLayout(task_box_layout)
|
|
scroll = QScrollArea()
|
|
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
|
scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
|
|
scroll.setWidgetResizable(True)
|
|
scroll.setWidget(scroll_content)
|
|
main_layout.addLayout(self.hangar_status)
|
|
main_layout.addWidget(scroll)
|
|
self.setLayout(main_layout)
|
|
|
|
def enable_purchase(self, unit_type: Type[UnitType]) -> bool:
|
|
if not super().enable_purchase(unit_type):
|
|
return False
|
|
if not issubclass(unit_type, FlyingType):
|
|
return False
|
|
if not self.cp.can_operate(unit_type):
|
|
return False
|
|
return True
|
|
|
|
def enable_sale(self, unit_type: Type[UnitType]) -> bool:
|
|
if not issubclass(unit_type, FlyingType):
|
|
return False
|
|
if not self.cp.can_operate(unit_type):
|
|
return False
|
|
return True
|
|
|
|
def buy(self, unit_type):
|
|
if self.maximum_units > 0:
|
|
if self.cp.unclaimed_parking(self.game_model.game) <= 0:
|
|
logging.debug(f"No space for additional aircraft at {self.cp}.")
|
|
QMessageBox.warning(
|
|
self,
|
|
"No space for additional aircraft",
|
|
f"There is no parking space left at {self.cp.name} to accommodate another plane.",
|
|
QMessageBox.Ok,
|
|
)
|
|
return
|
|
# If we change our mind about selling, we want the aircraft to be put
|
|
# back in the inventory immediately.
|
|
elif self.pending_deliveries.units.get(unit_type, 0) < 0:
|
|
global_inventory = self.game_model.game.aircraft_inventory
|
|
inventory = global_inventory.for_control_point(self.cp)
|
|
inventory.add_aircraft(unit_type, 1)
|
|
|
|
super().buy(unit_type)
|
|
self.hangar_status.update_label()
|
|
|
|
def sell(self, unit_type: UnitType):
|
|
# Don't need to remove aircraft from the inventory if we're canceling
|
|
# orders.
|
|
if self.pending_deliveries.units.get(unit_type, 0) <= 0:
|
|
global_inventory = self.game_model.game.aircraft_inventory
|
|
inventory = global_inventory.for_control_point(self.cp)
|
|
try:
|
|
inventory.remove_aircraft(unit_type, 1)
|
|
except ValueError:
|
|
QMessageBox.critical(
|
|
self,
|
|
"Could not sell aircraft",
|
|
f"Attempted to sell one {unit_type.id} at {self.cp.name} "
|
|
"but none are available. Are all aircraft currently "
|
|
"assigned to a mission?",
|
|
QMessageBox.Ok,
|
|
)
|
|
return
|
|
super().sell(unit_type)
|
|
self.hangar_status.update_label()
|
|
|
|
|
|
class QHangarStatus(QHBoxLayout):
|
|
def __init__(self, game_model: GameModel, control_point: ControlPoint) -> None:
|
|
super().__init__()
|
|
self.game_model = game_model
|
|
self.control_point = control_point
|
|
|
|
self.icon = QLabel()
|
|
self.icon.setPixmap(ICONS["Hangar"])
|
|
self.text = QLabel("")
|
|
|
|
self.update_label()
|
|
self.addWidget(self.icon, Qt.AlignLeft)
|
|
self.addWidget(self.text, Qt.AlignLeft)
|
|
self.addStretch(50)
|
|
self.setAlignment(Qt.AlignLeft)
|
|
|
|
def update_label(self) -> None:
|
|
next_turn = self.control_point.allocated_aircraft(self.game_model.game)
|
|
max_amount = self.control_point.total_aircraft_parking
|
|
|
|
components = [f"{next_turn.total_present} present"]
|
|
if next_turn.total_ordered > 0:
|
|
components.append(f"{next_turn.total_ordered} purchased")
|
|
elif next_turn.total_ordered < 0:
|
|
components.append(f"{-next_turn.total_ordered} sold")
|
|
|
|
transferring = next_turn.total_transferring
|
|
if transferring > 0:
|
|
components.append(f"{transferring} transferring in")
|
|
if transferring < 0:
|
|
components.append(f"{-transferring} transferring out")
|
|
|
|
details = ", ".join(components)
|
|
self.text.setText(
|
|
f"<strong>{next_turn.total}/{max_amount}</strong> ({details})"
|
|
)
|