mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
259 lines
9.6 KiB
Python
259 lines
9.6 KiB
Python
from PySide2.QtCore import Qt
|
|
from PySide2.QtGui import QCloseEvent, QPixmap
|
|
from PySide2.QtWidgets import (
|
|
QDialog,
|
|
QHBoxLayout,
|
|
QLabel,
|
|
QMessageBox,
|
|
QPushButton,
|
|
QVBoxLayout,
|
|
QWidget,
|
|
)
|
|
|
|
from game import Game, db
|
|
from game.theater import (
|
|
ControlPoint,
|
|
ControlPointType,
|
|
FREE_FRONTLINE_UNIT_SUPPLY,
|
|
AMMO_DEPOT_FRONTLINE_UNIT_CONTRIBUTION,
|
|
)
|
|
from gen.flights.flight import FlightType
|
|
from qt_ui.dialogs import Dialog
|
|
from qt_ui.models import GameModel
|
|
from qt_ui.uiconstants import EVENT_ICONS
|
|
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
|
from qt_ui.windows.basemenu.NewUnitTransferDialog import NewUnitTransferDialog
|
|
from qt_ui.windows.basemenu.QBaseMenuTabs import QBaseMenuTabs
|
|
from qt_ui.windows.basemenu.QRecruitBehaviour import QRecruitBehaviour
|
|
|
|
|
|
class QBaseMenu2(QDialog):
|
|
def __init__(self, parent, cp: ControlPoint, game_model: GameModel):
|
|
super(QBaseMenu2, self).__init__(parent)
|
|
|
|
# Attrs
|
|
self.cp = cp
|
|
self.game_model = game_model
|
|
self.objectName = "menuDialogue"
|
|
|
|
try:
|
|
game = self.game_model.game
|
|
self.airport = game.theater.terrain.airport_by_id(self.cp.id)
|
|
except:
|
|
self.airport = None
|
|
|
|
if self.cp.captured:
|
|
self.deliveryEvent = None
|
|
|
|
self.setWindowIcon(EVENT_ICONS["capture"])
|
|
|
|
self.setWindowFlags(Qt.WindowStaysOnTopHint)
|
|
self.setMinimumSize(300, 200)
|
|
self.setMinimumWidth(1024)
|
|
self.setMaximumWidth(1024)
|
|
self.setModal(True)
|
|
|
|
self.setWindowTitle(self.cp.name)
|
|
|
|
base_menu_header = QWidget()
|
|
top_layout = QHBoxLayout()
|
|
|
|
header = QLabel(self)
|
|
header.setGeometry(0, 0, 655, 106)
|
|
pixmap = QPixmap(self.get_base_image())
|
|
header.setPixmap(pixmap)
|
|
|
|
title = QLabel("<b>" + self.cp.name + "</b>")
|
|
title.setAlignment(Qt.AlignLeft | Qt.AlignTop)
|
|
title.setProperty("style", "base-title")
|
|
self.intel_summary = QLabel()
|
|
self.intel_summary.setToolTip(self.generate_intel_tooltip())
|
|
self.update_intel_summary()
|
|
top_layout.addWidget(title)
|
|
top_layout.addWidget(self.intel_summary)
|
|
top_layout.setAlignment(Qt.AlignTop)
|
|
|
|
self.repair_button = QPushButton()
|
|
self.repair_button.clicked.connect(self.begin_runway_repair)
|
|
self.update_repair_button()
|
|
top_layout.addWidget(self.repair_button)
|
|
|
|
base_menu_header.setProperty("style", "baseMenuHeader")
|
|
base_menu_header.setLayout(top_layout)
|
|
|
|
main_layout = QVBoxLayout()
|
|
main_layout.addWidget(header)
|
|
main_layout.addWidget(base_menu_header)
|
|
main_layout.addWidget(QBaseMenuTabs(cp, self.game_model))
|
|
bottom_row = QHBoxLayout()
|
|
main_layout.addLayout(bottom_row)
|
|
|
|
if FlightType.OCA_RUNWAY in self.cp.mission_types(for_player=True):
|
|
runway_attack_button = QPushButton("Attack airfield")
|
|
bottom_row.addWidget(runway_attack_button)
|
|
|
|
runway_attack_button.setProperty("style", "btn-danger")
|
|
runway_attack_button.clicked.connect(self.new_package)
|
|
|
|
if self.cp.captured and self.has_transfer_destinations:
|
|
transfer_button = QPushButton("Transfer Units")
|
|
transfer_button.setProperty("style", "btn-success")
|
|
bottom_row.addWidget(transfer_button)
|
|
transfer_button.clicked.connect(self.open_transfer_dialog)
|
|
|
|
if self.cheat_capturable:
|
|
capture_button = QPushButton("CHEAT: Capture")
|
|
capture_button.setProperty("style", "btn-danger")
|
|
bottom_row.addWidget(capture_button)
|
|
capture_button.clicked.connect(self.cheat_capture)
|
|
|
|
self.budget_display = QLabel(
|
|
QRecruitBehaviour.BUDGET_FORMAT.format(self.game_model.game.budget)
|
|
)
|
|
self.budget_display.setAlignment(Qt.AlignRight | Qt.AlignBottom)
|
|
self.budget_display.setProperty("style", "budget-label")
|
|
bottom_row.addWidget(self.budget_display)
|
|
GameUpdateSignal.get_instance().budgetupdated.connect(self.update_budget)
|
|
self.setLayout(main_layout)
|
|
|
|
@property
|
|
def cheat_capturable(self) -> bool:
|
|
return self.game_model.game.settings.enable_base_capture_cheat
|
|
|
|
def cheat_capture(self) -> None:
|
|
self.cp.capture(self.game_model.game, for_player=not self.cp.captured)
|
|
# Reinitialized ground planners and the like. The ATO needs to be reset because
|
|
# missions planned against the flipped base are no longer valid.
|
|
self.game_model.game.initialize_turn()
|
|
GameUpdateSignal.get_instance().updateGame(self.game_model.game)
|
|
|
|
@property
|
|
def has_transfer_destinations(self) -> bool:
|
|
return self.game_model.game.transit_network_for(
|
|
self.cp.captured
|
|
).has_destinations(self.cp)
|
|
|
|
@property
|
|
def can_repair_runway(self) -> bool:
|
|
return self.cp.captured and self.cp.runway_can_be_repaired
|
|
|
|
@property
|
|
def can_afford_runway_repair(self) -> bool:
|
|
return self.game_model.game.budget >= db.RUNWAY_REPAIR_COST
|
|
|
|
def begin_runway_repair(self) -> None:
|
|
if not self.can_afford_runway_repair:
|
|
QMessageBox.critical(
|
|
self,
|
|
"Cannot repair runway",
|
|
f"Runway repair costs ${db.RUNWAY_REPAIR_COST}M but you have "
|
|
f"only ${self.game_model.game.budget}M available.",
|
|
QMessageBox.Ok,
|
|
)
|
|
return
|
|
if not self.can_repair_runway:
|
|
QMessageBox.critical(
|
|
self,
|
|
"Cannot repair runway",
|
|
f"Cannot repair this runway.",
|
|
QMessageBox.Ok,
|
|
)
|
|
return
|
|
|
|
self.cp.begin_runway_repair()
|
|
self.game_model.game.budget -= db.RUNWAY_REPAIR_COST
|
|
self.update_repair_button()
|
|
self.update_intel_summary()
|
|
GameUpdateSignal.get_instance().updateGame(self.game_model.game)
|
|
|
|
def update_repair_button(self) -> None:
|
|
self.repair_button.setVisible(True)
|
|
turns_remaining = self.cp.runway_status.repair_turns_remaining
|
|
if self.cp.captured and turns_remaining is not None:
|
|
self.repair_button.setText("Repairing...")
|
|
self.repair_button.setDisabled(True)
|
|
return
|
|
|
|
if self.can_repair_runway:
|
|
if self.can_afford_runway_repair:
|
|
self.repair_button.setText(f"Repair ${db.RUNWAY_REPAIR_COST}M")
|
|
self.repair_button.setDisabled(False)
|
|
return
|
|
else:
|
|
self.repair_button.setText(
|
|
f"Cannot afford repair ${db.RUNWAY_REPAIR_COST}M"
|
|
)
|
|
self.repair_button.setDisabled(True)
|
|
return
|
|
|
|
self.repair_button.setVisible(False)
|
|
self.repair_button.setDisabled(True)
|
|
|
|
def update_intel_summary(self) -> None:
|
|
aircraft = self.cp.base.total_aircraft
|
|
parking = self.cp.total_aircraft_parking
|
|
ground_unit_limit = self.cp.frontline_unit_count_limit
|
|
deployable_unit_info = ""
|
|
|
|
allocated = self.cp.allocated_ground_units(self.game_model.game.transfers)
|
|
unit_overage = max(
|
|
allocated.total_present - self.cp.frontline_unit_count_limit, 0
|
|
)
|
|
if self.cp.has_active_frontline:
|
|
deployable_unit_info = (
|
|
f" (Up to {ground_unit_limit} deployable, {unit_overage} reserve)"
|
|
)
|
|
|
|
self.intel_summary.setText(
|
|
"\n".join(
|
|
[
|
|
f"{aircraft}/{parking} aircraft",
|
|
f"{self.cp.base.total_armor} ground units" + deployable_unit_info,
|
|
f"{allocated.total_transferring} more ground units en route, {allocated.total_ordered} ordered",
|
|
str(self.cp.runway_status),
|
|
f"{self.cp.active_ammo_depots_count}/{self.cp.total_ammo_depots_count} ammo depots",
|
|
f"{'Factory can produce units' if self.cp.has_factory else 'Does not have a factory'}",
|
|
]
|
|
)
|
|
)
|
|
|
|
def generate_intel_tooltip(self) -> str:
|
|
tooltip = (
|
|
f"Deployable unit limit ({self.cp.frontline_unit_count_limit}) = {FREE_FRONTLINE_UNIT_SUPPLY} (base) + "
|
|
f" {AMMO_DEPOT_FRONTLINE_UNIT_CONTRIBUTION} (per connected ammo depot) * {self.cp.total_ammo_depots_count} "
|
|
f"(depots)"
|
|
)
|
|
|
|
if self.cp.has_active_frontline:
|
|
unit_overage = max(
|
|
self.cp.base.total_armor - self.cp.frontline_unit_count_limit, 0
|
|
)
|
|
tooltip += (
|
|
f"\n{unit_overage} units will be held in reserve and will not be deployed to "
|
|
f"connected frontlines for this turn"
|
|
)
|
|
|
|
return tooltip
|
|
|
|
def closeEvent(self, close_event: QCloseEvent):
|
|
GameUpdateSignal.get_instance().updateGame(self.game_model.game)
|
|
|
|
def get_base_image(self):
|
|
if self.cp.cptype == ControlPointType.AIRCRAFT_CARRIER_GROUP:
|
|
return "./resources/ui/carrier.png"
|
|
elif self.cp.cptype == ControlPointType.LHA_GROUP:
|
|
return "./resources/ui/lha.png"
|
|
elif self.cp.cptype == ControlPointType.FOB:
|
|
return "./resources/ui/fob.png"
|
|
else:
|
|
return "./resources/ui/airbase.png"
|
|
|
|
def new_package(self) -> None:
|
|
Dialog.open_new_package_dialog(self.cp, parent=self.window())
|
|
|
|
def open_transfer_dialog(self) -> None:
|
|
NewUnitTransferDialog(self.game_model, self.cp, parent=self.window()).show()
|
|
|
|
def update_budget(self, game: Game) -> None:
|
|
self.budget_display.setText(QRecruitBehaviour.BUDGET_FORMAT.format(game.budget))
|