mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
A user would be able to sell a complete damaged TGO for the price of a new one as the value calculation function was not taking care if the unit was dead or alive. This fix now only takes alive units into account for the actual value of the tgo
275 lines
10 KiB
Python
275 lines
10 KiB
Python
import logging
|
|
|
|
from PySide2.QtGui import QTransform
|
|
from PySide2.QtWidgets import (
|
|
QDialog,
|
|
QGridLayout,
|
|
QGroupBox,
|
|
QHBoxLayout,
|
|
QLabel,
|
|
QPushButton,
|
|
QVBoxLayout,
|
|
QSpinBox,
|
|
)
|
|
from dcs import Point
|
|
|
|
from game import Game
|
|
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
|
|
from game.theater.theatergroundobject import (
|
|
BuildingGroundObject,
|
|
)
|
|
from game.utils import Heading
|
|
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 QGroundObjectMenu(QDialog):
|
|
def __init__(
|
|
self,
|
|
parent,
|
|
ground_object: TheaterGroundObject,
|
|
cp: ControlPoint,
|
|
game: Game,
|
|
):
|
|
super().__init__(parent)
|
|
self.setMinimumWidth(350)
|
|
self.ground_object = ground_object
|
|
self.cp = cp
|
|
self.game = 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:
|
|
self.mainLayout.addWidget(self.financesBox)
|
|
else:
|
|
self.mainLayout.addWidget(self.intelBox)
|
|
self.mainLayout.addWidget(self.orientationBox)
|
|
|
|
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 self.total_value > 0:
|
|
self.actionLayout.addWidget(self.sell_all_button)
|
|
self.actionLayout.addWidget(self.buy_replace)
|
|
|
|
if self.cp.captured and self.ground_object.purchasable:
|
|
self.mainLayout.addLayout(self.actionLayout)
|
|
self.setLayout(self.mainLayout)
|
|
|
|
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:
|
|
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()
|
|
|
|
heading_image = QLabel()
|
|
heading_image.setPixmap(
|
|
ICONS["heading"].transformed(
|
|
QTransform().rotate(self.ground_object.heading.degrees)
|
|
)
|
|
)
|
|
self.orientationBoxLayout.addWidget(heading_image)
|
|
self.headingLabel = QLabel("Heading:")
|
|
self.orientationBoxLayout.addWidget(self.headingLabel)
|
|
self.headingSelector = QSpinBox()
|
|
self.headingSelector.setEnabled(False) # Disable for now
|
|
self.headingSelector.setMinimum(0)
|
|
self.headingSelector.setMaximum(360)
|
|
self.headingSelector.setValue(self.ground_object.heading.degrees)
|
|
self.orientationBoxLayout.addWidget(self.headingSelector)
|
|
if self.cp.captured:
|
|
# TODO Let the user choose the heading with the SpinBox
|
|
self.headingSelector.setEnabled(False)
|
|
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.rotate_tgo(heading)
|
|
)
|
|
self.orientationBoxLayout.addWidget(self.head_to_conflict_button)
|
|
else:
|
|
self.headingSelector.setEnabled(False)
|
|
|
|
# Set the layouts
|
|
self.financesBox.setLayout(self.financesBoxLayout)
|
|
self.buildingBox.setLayout(self.buildingsLayout)
|
|
self.intelBox.setLayout(self.intelLayout)
|
|
self.orientationBox.setLayout(self.orientationBoxLayout)
|
|
|
|
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.cp.captured 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 = sum(
|
|
u.unit_type.price
|
|
for u in self.ground_object.units
|
|
if u.unit_type and u.alive
|
|
)
|
|
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.do_refresh_layout()
|
|
|
|
def sell_all(self):
|
|
self.update_total_value()
|
|
self.game.blue.budget = self.game.blue.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)
|
|
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
|
|
self.game.initialize_turn(events, for_red=True, for_blue=False)
|
|
EventStream.put_nowait(events)
|
|
GameUpdateSignal.get_instance().updateGame(self.game)
|
|
# Refresh the dialog
|
|
self.do_refresh_layout()
|