mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
refactor to enum typing and many other fixes fix tests attempt to fix some typescript more typescript fixes more typescript test fixes revert all API changes update to pydcs mypy fixes Use properties to check if player is blue/red/neutral update requirements.txt black -_- bump pydcs and fix mypy add opponent property bump pydcs
319 lines
12 KiB
Python
319 lines
12 KiB
Python
import logging
|
|
|
|
from PySide6.QtGui import QTransform
|
|
from PySide6.QtWidgets import (
|
|
QDialog,
|
|
QGridLayout,
|
|
QGroupBox,
|
|
QHBoxLayout,
|
|
QLabel,
|
|
QPushButton,
|
|
QVBoxLayout,
|
|
QSpinBox,
|
|
QWidget,
|
|
QCheckBox,
|
|
)
|
|
from dcs import Point
|
|
|
|
from game.config import REWARDS
|
|
from game.data.building_data import FORTIFICATION_BUILDINGS
|
|
from game.server import EventStream
|
|
from game.sim.gameupdateevents import GameUpdateEvents
|
|
from game.theater import ControlPoint, TheaterGroundObject, Player
|
|
from game.theater.theatergroundobject import (
|
|
BuildingGroundObject,
|
|
)
|
|
from game.utils import Heading
|
|
from qt_ui.models import GameModel
|
|
from qt_ui.uiconstants import EVENT_ICONS, ICONS
|
|
from qt_ui.widgets.QBudgetBox import QBudgetBox
|
|
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
|
from qt_ui.windows.groundobject.QBuildingInfo import QBuildingInfo
|
|
from qt_ui.windows.groundobject.QGroundObjectBuyMenu import QGroundObjectBuyMenu
|
|
|
|
|
|
class HeadingIndicator(QLabel):
|
|
def __init__(self, initial_heading: Heading, parent: QWidget) -> None:
|
|
super().__init__(parent)
|
|
self.set_heading(initial_heading)
|
|
self.setFixedSize(32, 32)
|
|
|
|
def set_heading(self, heading: Heading) -> None:
|
|
self.setPixmap(
|
|
ICONS["heading"].transformed(QTransform().rotate(heading.degrees))
|
|
)
|
|
|
|
|
|
class SamIndicator(QLabel):
|
|
def __init__(self, parent: QWidget) -> None:
|
|
super().__init__(parent)
|
|
self.setFixedSize(32, 32)
|
|
self.setPixmap(ICONS["blue-sam"])
|
|
|
|
|
|
class QGroundObjectMenu(QDialog):
|
|
def __init__(
|
|
self,
|
|
parent,
|
|
ground_object: TheaterGroundObject,
|
|
cp: ControlPoint,
|
|
gm: GameModel,
|
|
):
|
|
super().__init__(parent)
|
|
self.setMinimumWidth(350)
|
|
self.ground_object = ground_object
|
|
self.cp = cp
|
|
self.game_model = gm
|
|
self.game = gm.game
|
|
self.setWindowTitle(
|
|
f"Location - {self.ground_object.obj_name} ({self.cp.name})"
|
|
)
|
|
self.setWindowIcon(EVENT_ICONS["capture"])
|
|
self.intelBox = QGroupBox("Units :")
|
|
self.buildingBox = QGroupBox("Buildings :")
|
|
self.orientationBox = QGroupBox("Orientation :")
|
|
self.intelLayout = QGridLayout()
|
|
self.buildingsLayout = QGridLayout()
|
|
self.sell_all_button = None
|
|
self.total_value = 0
|
|
self.init_ui()
|
|
|
|
def init_ui(self):
|
|
self.mainLayout = QVBoxLayout()
|
|
self.budget = QBudgetBox(self.game)
|
|
self.budget.setGame(self.game)
|
|
|
|
self.doLayout()
|
|
|
|
if isinstance(self.ground_object, BuildingGroundObject):
|
|
self.mainLayout.addWidget(self.buildingBox)
|
|
if self.cp.captured.is_blue:
|
|
self.mainLayout.addWidget(self.financesBox)
|
|
else:
|
|
self.mainLayout.addWidget(self.intelBox)
|
|
self.mainLayout.addWidget(self.orientationBox)
|
|
if self.ground_object.is_iads and self.cp.is_friendly(to_player=Player.RED):
|
|
self.mainLayout.addWidget(self.hiddenBox)
|
|
|
|
self.actionLayout = QHBoxLayout()
|
|
|
|
self.sell_all_button = QPushButton("Disband (+" + str(self.total_value) + "M)")
|
|
self.sell_all_button.clicked.connect(self.sell_all)
|
|
self.sell_all_button.setProperty("style", "btn-danger")
|
|
|
|
self.buy_replace = QPushButton("Buy/Replace")
|
|
self.buy_replace.clicked.connect(self.buy_group)
|
|
self.buy_replace.setProperty("style", "btn-success")
|
|
|
|
if self.ground_object.purchasable:
|
|
# if not purchasable but is_iads => naval unit
|
|
if self.total_value > 0:
|
|
self.actionLayout.addWidget(self.sell_all_button)
|
|
self.actionLayout.addWidget(self.buy_replace)
|
|
|
|
if self.show_buy_sell_actions and self.ground_object.purchasable:
|
|
# if not purchasable but is_iads => naval unit
|
|
self.mainLayout.addLayout(self.actionLayout)
|
|
self.setLayout(self.mainLayout)
|
|
|
|
@property
|
|
def show_buy_sell_actions(self) -> bool:
|
|
if self.cp.captured.is_neutral:
|
|
return False
|
|
buysell_allowed = self.game.settings.enable_enemy_buy_sell
|
|
buysell_allowed |= self.cp.captured.is_blue
|
|
return buysell_allowed
|
|
|
|
def doLayout(self):
|
|
self.update_total_value()
|
|
self.intelBox = QGroupBox("Units :")
|
|
self.intelLayout = QGridLayout()
|
|
i = 0
|
|
for g in self.ground_object.groups:
|
|
for unit in g.units:
|
|
self.intelLayout.addWidget(
|
|
QLabel(f"<b>Unit {str(unit.display_name)}</b>"), i, 0
|
|
)
|
|
|
|
if not unit.alive and unit.repairable and self.cp.captured.is_blue:
|
|
price = unit.unit_type.price if unit.unit_type else 0
|
|
repair = QPushButton(f"Repair [{price}M]")
|
|
repair.setProperty("style", "btn-success")
|
|
repair.clicked.connect(
|
|
lambda u=unit, p=price: self.repair_unit(u, p)
|
|
)
|
|
self.intelLayout.addWidget(repair, i, 1)
|
|
i += 1
|
|
|
|
stretch = QVBoxLayout()
|
|
stretch.addStretch()
|
|
self.intelLayout.addLayout(stretch, i, 0)
|
|
|
|
self.buildingBox = QGroupBox("Buildings :")
|
|
self.buildingsLayout = QGridLayout()
|
|
|
|
j = 0
|
|
total_income = 0
|
|
received_income = 0
|
|
for static in self.ground_object.statics:
|
|
if static not in FORTIFICATION_BUILDINGS:
|
|
self.buildingsLayout.addWidget(
|
|
QBuildingInfo(static, self.ground_object), j / 3, j % 3
|
|
)
|
|
j = j + 1
|
|
|
|
if self.ground_object.category in REWARDS.keys():
|
|
total_income += REWARDS[self.ground_object.category]
|
|
if static.alive:
|
|
received_income += REWARDS[self.ground_object.category]
|
|
else:
|
|
logging.warning(self.ground_object.category + " not in REWARDS")
|
|
|
|
self.financesBox = QGroupBox("Finances: ")
|
|
self.financesBoxLayout = QGridLayout()
|
|
self.financesBoxLayout.addWidget(
|
|
QLabel("Available: " + str(total_income) + "M"), 2, 1
|
|
)
|
|
self.financesBoxLayout.addWidget(
|
|
QLabel("Receiving: " + str(received_income) + "M"), 2, 2
|
|
)
|
|
|
|
# Orientation Box
|
|
self.orientationBox = QGroupBox("Orientation :")
|
|
self.orientationBoxLayout = QHBoxLayout()
|
|
|
|
self.heading_image = HeadingIndicator(self.ground_object.heading, self)
|
|
self.orientationBoxLayout.addWidget(self.heading_image)
|
|
self.headingLabel = QLabel("Heading:")
|
|
self.orientationBoxLayout.addWidget(self.headingLabel)
|
|
self.headingSelector = QSpinBox()
|
|
self.headingSelector.setRange(0, 359)
|
|
self.headingSelector.setWrapping(True)
|
|
self.headingSelector.setSingleStep(5)
|
|
self.headingSelector.setValue(self.ground_object.heading.degrees)
|
|
self.headingSelector.valueChanged.connect(
|
|
lambda degrees: self.rotate_tgo(Heading(degrees))
|
|
)
|
|
self.orientationBoxLayout.addWidget(self.headingSelector)
|
|
can_adjust_heading = self.cp.is_friendly(to_player=self.game_model.is_ownfor)
|
|
if can_adjust_heading:
|
|
self.head_to_conflict_button = QPushButton("Head to conflict")
|
|
heading = (
|
|
self.game.theater.heading_to_conflict_from(self.ground_object.position)
|
|
or self.ground_object.heading
|
|
)
|
|
self.head_to_conflict_button.clicked.connect(
|
|
lambda: self.headingSelector.setValue(heading.degrees)
|
|
)
|
|
self.orientationBoxLayout.addWidget(self.head_to_conflict_button)
|
|
else:
|
|
self.headingSelector.setEnabled(False)
|
|
|
|
# Hidden Box
|
|
self.hiddenBox = QGroupBox()
|
|
self.hiddenBoxLayout = QHBoxLayout()
|
|
self.hiddenBoxLayout.addWidget(SamIndicator(self))
|
|
self.hiddenBoxLayout.addWidget(QLabel("Hidden on MFD:"))
|
|
self.hiddenCheckBox = QCheckBox()
|
|
self.hiddenCheckBox.setChecked(self.ground_object.hide_on_mfd)
|
|
self.hiddenCheckBox.stateChanged.connect(self.update_hidden_on_mfd)
|
|
self.hiddenBoxLayout.addWidget(self.hiddenCheckBox)
|
|
|
|
# Set the layouts
|
|
self.financesBox.setLayout(self.financesBoxLayout)
|
|
self.buildingBox.setLayout(self.buildingsLayout)
|
|
self.intelBox.setLayout(self.intelLayout)
|
|
self.orientationBox.setLayout(self.orientationBoxLayout)
|
|
self.hiddenBox.setLayout(self.hiddenBoxLayout)
|
|
|
|
def update_hidden_on_mfd(self, state: bool) -> None:
|
|
self.ground_object.hide_on_mfd = bool(state)
|
|
|
|
def do_refresh_layout(self):
|
|
try:
|
|
for i in reversed(range(self.mainLayout.count())):
|
|
item = self.mainLayout.itemAt(i)
|
|
if item is not None and item.widget() is not None:
|
|
item.widget().setParent(None)
|
|
self.sell_all_button.setParent(None)
|
|
self.buy_replace.setParent(None)
|
|
self.actionLayout.setParent(None)
|
|
|
|
self.doLayout()
|
|
if isinstance(self.ground_object, BuildingGroundObject):
|
|
self.mainLayout.addWidget(self.buildingBox)
|
|
else:
|
|
self.mainLayout.addWidget(self.intelBox)
|
|
self.mainLayout.addWidget(self.orientationBox)
|
|
|
|
self.actionLayout = QHBoxLayout()
|
|
if self.total_value > 0:
|
|
self.actionLayout.addWidget(self.sell_all_button)
|
|
self.actionLayout.addWidget(self.buy_replace)
|
|
|
|
if self.show_buy_sell_actions and self.ground_object.purchasable:
|
|
self.mainLayout.addLayout(self.actionLayout)
|
|
except Exception as e:
|
|
logging.exception(e)
|
|
self.update_total_value()
|
|
|
|
def update_total_value(self):
|
|
if not self.ground_object.purchasable:
|
|
return
|
|
self.total_value = self.ground_object.value
|
|
if self.sell_all_button is not None:
|
|
self.sell_all_button.setText("Disband (+$" + str(self.total_value) + "M)")
|
|
|
|
def repair_unit(self, unit, price):
|
|
if self.game.blue.budget > price:
|
|
self.game.blue.budget -= price
|
|
unit.alive = True
|
|
GameUpdateSignal.get_instance().updateGame(self.game)
|
|
|
|
# Remove destroyed units in the vicinity
|
|
destroyed_units = self.game.get_destroyed_units()
|
|
for d in destroyed_units:
|
|
p = Point(d["x"], d["z"], self.game.theater.terrain)
|
|
if p.distance_to_point(unit.position) < 15:
|
|
destroyed_units.remove(d)
|
|
logging.info("Removed destroyed units " + str(d))
|
|
logging.info(f"Repaired unit: {unit.unit_name}")
|
|
|
|
self.update_game()
|
|
|
|
def rotate_tgo(self, heading: Heading) -> None:
|
|
self.ground_object.rotate(heading)
|
|
self.heading_image.set_heading(heading)
|
|
|
|
def sell_all(self):
|
|
self.update_total_value()
|
|
coalition = self.ground_object.coalition
|
|
coalition.budget += self.total_value
|
|
self.ground_object.groups = []
|
|
self.update_game()
|
|
|
|
def buy_group(self) -> None:
|
|
self.subwindow = QGroundObjectBuyMenu(
|
|
self, self.ground_object, self.game, self.total_value
|
|
)
|
|
if self.subwindow.exec_():
|
|
self.update_game()
|
|
|
|
def update_game(self) -> None:
|
|
events = GameUpdateEvents()
|
|
events.update_tgo(self.ground_object)
|
|
self.game.theater.iads_network.update_tgo(self.ground_object, events)
|
|
if any(
|
|
package.target == self.ground_object
|
|
for package in self.game.ato_for(player=False).packages
|
|
):
|
|
# Replan if the tgo was a target of the redfor
|
|
coalition = self.ground_object.coalition
|
|
self.game.initialize_turn(
|
|
events, for_red=coalition.player, for_blue=not coalition.player
|
|
)
|
|
EventStream.put_nowait(events)
|
|
GameUpdateSignal.get_instance().updateGame(self.game)
|
|
# Refresh the dialog
|
|
self.do_refresh_layout()
|