class QTopPanel(QFrame): def __init__(self, game_model: GameModel): super(QTopPanel, self).__init__() self.game_model = game_model self.dialog: Optional[QDialog] = None self.setMaximumHeight(70) self.init_ui() GameUpdateSignal.get_instance().gameupdated.connect(self.setGame) GameUpdateSignal.get_instance().budgetupdated.connect(self.budget_update) @property def game(self) -> Optional[Game]: return self.game_model.game def init_ui(self): self.conditionsWidget = QConditionsWidget() self.budgetBox = QBudgetBox(self.game) pass_turn_text = "Pass Turn" if not self.game or self.game.turn == 0: pass_turn_text = "Begin Campaign" self.passTurnButton = QPushButton(pass_turn_text) self.passTurnButton.setIcon(CONST.ICONS["PassTurn"]) self.passTurnButton.setProperty("style", "btn-primary") self.passTurnButton.clicked.connect(self.passTurn) if not self.game: self.passTurnButton.setEnabled(False) self.proceedButton = QPushButton("Take off") self.proceedButton.setIcon(CONST.ICONS["Proceed"]) self.proceedButton.setProperty("style", "start-button") self.proceedButton.clicked.connect(self.launch_mission) if not self.game or self.game.turn == 0: self.proceedButton.setEnabled(False) self.factionsInfos = QFactionsInfos(self.game) self.air_wing = QPushButton("Air Wing") self.air_wing.setDisabled(True) self.air_wing.setProperty("style", "btn-primary") self.air_wing.clicked.connect(self.open_air_wing) self.transfers = QPushButton("Transfers") self.transfers.setDisabled(True) self.transfers.setProperty("style", "btn-primary") self.transfers.clicked.connect(self.open_transfers) self.intel_box = QIntelBox(self.game) self.buttonBox = QGroupBox("Misc") self.buttonBoxLayout = QHBoxLayout() self.buttonBoxLayout.addWidget(self.air_wing) self.buttonBoxLayout.addWidget(self.transfers) self.buttonBox.setLayout(self.buttonBoxLayout) self.proceedBox = QGroupBox("Proceed") self.proceedBoxLayout = QHBoxLayout() self.proceedBoxLayout.addLayout(MaxPlayerCount(self.game_model.ato_model)) self.proceedBoxLayout.addWidget(self.passTurnButton) self.proceedBoxLayout.addWidget(self.proceedButton) self.proceedBox.setLayout(self.proceedBoxLayout) self.layout = QHBoxLayout() self.layout.addWidget(self.factionsInfos) self.layout.addWidget(self.conditionsWidget) self.layout.addWidget(self.budgetBox) self.layout.addWidget(self.intel_box) self.layout.addWidget(self.buttonBox) self.layout.addStretch(1) self.layout.addWidget(self.proceedBox) self.layout.setContentsMargins(0, 0, 0, 0) self.setLayout(self.layout) def setGame(self, game: Optional[Game]): if game is None: return self.air_wing.setEnabled(True) self.transfers.setEnabled(True) self.conditionsWidget.setCurrentTurn(game.turn, game.conditions) if game.conditions.weather.clouds: base_m = game.conditions.weather.clouds.base base_ft = int(meters(base_m).feet) self.conditionsWidget.setToolTip(f"Cloud Base: {base_m}m / {base_ft}ft") else: self.conditionsWidget.setToolTip("") self.intel_box.set_game(game) self.budgetBox.setGame(game) self.factionsInfos.setGame(game) self.passTurnButton.setEnabled(True) if game and game.turn > 0: self.passTurnButton.setText("Pass Turn") if game and game.turn == 0: self.passTurnButton.setText("Begin Campaign") self.proceedButton.setEnabled(False) else: self.proceedButton.setEnabled(True) def open_air_wing(self): self.dialog = AirWingDialog(self.game_model, self.window()) self.dialog.show() def open_transfers(self): self.dialog = PendingTransfersDialog(self.game_model) self.dialog.show() def passTurn(self): with logged_duration("Skipping turn"): self.game.pass_turn(no_action=True) GameUpdateSignal.get_instance().updateGame(self.game) self.proceedButton.setEnabled(True) def negative_start_packages(self) -> List[Package]: packages = [] for package in self.game_model.ato_model.ato.packages: if not package.flights: continue estimator = TotEstimator(package) for flight in package.flights: if estimator.mission_start_time(flight).total_seconds() < 0: packages.append(package) break return packages @staticmethod def fix_tots(packages: List[Package]) -> None: for package in packages: estimator = TotEstimator(package) package.time_over_target = estimator.earliest_tot() def ato_has_clients(self) -> bool: for package in self.game.blue_ato.packages: for flight in package.flights: if flight.client_count > 0: return True return False def confirm_no_client_launch(self) -> bool: result = QMessageBox.question( self, "Continue without player pilots?", ( "No player pilots have been assigned to flights. Continuing will allow " "the AI to perform the mission, but players will be unable to " "participate.<br />" "<br />" "To assign player pilots to a flight, select a package from the " "Packages panel on the left of the main window, and then a flight from " "the Flights panel below the Packages panel. The edit button below the " "Flights panel will allow you to assign specific pilots to the flight. " "If you have no player pilots available, the checkbox next to the " "name will convert them to a player.<br />" "<br />Click 'Yes' to continue with an AI only mission" "<br />Click 'No' if you'd like to make more changes." ), QMessageBox.No, QMessageBox.Yes, ) return result == QMessageBox.Yes def confirm_negative_start_time(self, negative_starts: List[Package]) -> bool: formatted = "<br />".join( [f"{p.primary_task} {p.target.name}" for p in negative_starts] ) mbox = QMessageBox( QMessageBox.Question, "Continue with past start times?", ( "Some flights in the following packages have start times set " "earlier than mission start time:<br />" "<br />" f"{formatted}<br />" "<br />" "Flight start times are estimated based on the package TOT, so it " "is possible that not all flights will be able to reach the " "target area at their assigned times.<br />" "<br />" "You can either continue with the mission as planned, with the " "misplanned flights potentially flying too fast and/or missing " "their rendezvous; automatically fix negative TOTs; or cancel " "mission start and fix the packages manually." ), parent=self, ) auto = mbox.addButton("Fix TOTs automatically", QMessageBox.ActionRole) ignore = mbox.addButton("Continue without fixing", QMessageBox.DestructiveRole) cancel = mbox.addButton(QMessageBox.Cancel) mbox.setEscapeButton(cancel) mbox.exec_() clicked = mbox.clickedButton() if clicked == auto: self.fix_tots(negative_starts) return True elif clicked == ignore: return True return False def check_no_missing_pilots(self) -> bool: missing_pilots = [] for package in self.game.blue_ato.packages: for flight in package.flights: if flight.missing_pilots > 0: missing_pilots.append((package, flight)) if not missing_pilots: return False formatted = "<br />".join( [f"{p.primary_task} {p.target}: {f}" for p, f in missing_pilots] ) mbox = QMessageBox( QMessageBox.Critical, "Flights are missing pilots", ( "The following flights are missing one or more pilots:<br />" "<br />" f"{formatted}<br />" "<br />" "You must either assign pilots to those flights or cancel those " "missions." ), parent=self, ) mbox.setEscapeButton(mbox.addButton(QMessageBox.Close)) mbox.exec_() return True def launch_mission(self): """Finishes planning and waits for mission completion.""" if not self.ato_has_clients() and not self.confirm_no_client_launch(): return if self.check_no_missing_pilots(): return negative_starts = self.negative_start_packages() if negative_starts: if not self.confirm_negative_start_time(negative_starts): return closest_cps = self.game.theater.closest_opposing_control_points() game_event = AirWarEvent( self.game, closest_cps[0], closest_cps[1], self.game.theater.controlpoints[0].position, self.game.player_faction.name, self.game.enemy_faction.name, ) unit_map = self.game.initiate_event(game_event) waiting = QWaitingForMissionResultWindow(game_event, self.game, unit_map) waiting.show() def budget_update(self, game: Game): self.budgetBox.setGame(game)
class QTopPanel(QFrame): def __init__(self, game_model: GameModel): super(QTopPanel, self).__init__() self.game_model = game_model self.setMaximumHeight(70) self.init_ui() GameUpdateSignal.get_instance().gameupdated.connect(self.setGame) GameUpdateSignal.get_instance().budgetupdated.connect( self.budget_update) @property def game(self) -> Optional[Game]: return self.game_model.game def init_ui(self): self.turnCounter = QTurnCounter() self.budgetBox = QBudgetBox(self.game) self.passTurnButton = QPushButton("Pass Turn") self.passTurnButton.setIcon(CONST.ICONS["PassTurn"]) self.passTurnButton.setProperty("style", "btn-primary") self.passTurnButton.clicked.connect(self.passTurn) self.proceedButton = QPushButton("Take off") self.proceedButton.setIcon(CONST.ICONS["Proceed"]) self.proceedButton.setProperty("style", "start-button") self.proceedButton.clicked.connect(self.launch_mission) if self.game and self.game.turn == 0: self.proceedButton.setEnabled(False) self.factionsInfos = QFactionsInfos(self.game) self.settings = QPushButton("Settings") self.settings.setIcon(CONST.ICONS["Settings"]) self.settings.setProperty("style", "btn-primary") self.settings.clicked.connect(self.openSettings) self.statistics = QPushButton("Statistics") self.statistics.setIcon(CONST.ICONS["Statistics"]) self.statistics.setProperty("style", "btn-primary") self.statistics.clicked.connect(self.openStatisticsWindow) self.buttonBox = QGroupBox("Misc") self.buttonBoxLayout = QHBoxLayout() self.buttonBoxLayout.addWidget(self.settings) self.buttonBoxLayout.addWidget(self.statistics) self.buttonBox.setLayout(self.buttonBoxLayout) self.proceedBox = QGroupBox("Proceed") self.proceedBoxLayout = QHBoxLayout() self.proceedBoxLayout.addLayout( MaxPlayerCount(self.game_model.ato_model)) self.proceedBoxLayout.addWidget(self.passTurnButton) self.proceedBoxLayout.addWidget(self.proceedButton) self.proceedBox.setLayout(self.proceedBoxLayout) self.layout = QHBoxLayout() self.layout.addWidget(self.factionsInfos) self.layout.addWidget(self.turnCounter) self.layout.addWidget(self.budgetBox) self.layout.addWidget(self.buttonBox) self.layout.addStretch(1) self.layout.addWidget(self.proceedBox) self.layout.setContentsMargins(0, 0, 0, 0) self.setLayout(self.layout) def setGame(self, game: Optional[Game]): if game is None: return self.turnCounter.setCurrentTurn(game.turn, game.conditions) self.budgetBox.setGame(game) self.factionsInfos.setGame(game) if game and game.turn == 0: self.proceedButton.setEnabled(False) else: self.proceedButton.setEnabled(True) def openSettings(self): self.subwindow = QSettingsWindow(self.game) self.subwindow.show() def openStatisticsWindow(self): self.subwindow = QStatsWindow(self.game) self.subwindow.show() def passTurn(self): self.game.pass_turn(no_action=True) GameUpdateSignal.get_instance().updateGame(self.game) self.proceedButton.setEnabled(True) def negative_start_packages(self) -> List[Package]: packages = [] for package in self.game_model.ato_model.ato.packages: if not package.flights: continue estimator = TotEstimator(package) for flight in package.flights: if estimator.mission_start_time(flight).total_seconds() < 0: packages.append(package) break return packages @staticmethod def fix_tots(packages: List[Package]) -> None: for package in packages: estimator = TotEstimator(package) package.time_over_target = estimator.earliest_tot() def ato_has_clients(self) -> bool: for package in self.game.blue_ato.packages: for flight in package.flights: if flight.client_count > 0: return True return False def confirm_no_client_launch(self) -> bool: result = QMessageBox.question(self, "Continue without client slots?", ( "No client slots have been created for players. Continuing will " "allow the AI to perform the mission, but players will be unable " "to participate.<br />" "<br />" "To add client slots for players, select a package from the " "Packages panel on the left of the main window, and then a flight " "from the Flights panel below the Packages panel. The edit button " "below the Flights panel will allow you to edit the number of " "client slots in the flight. Each client slot allows one player.<br />" "<br />Click 'Yes' to continue with an AI only mission" "<br />Click 'No' if you'd like to make more changes."), QMessageBox.No, QMessageBox.Yes) return result == QMessageBox.Yes def confirm_negative_start_time(self, negative_starts: List[Package]) -> bool: formatted = '<br />'.join([ f"{p.primary_task.name} {p.target.name}" for p in negative_starts ]) mbox = QMessageBox( QMessageBox.Question, "Continue with past start times?", ("Some flights in the following packages have start times set " "earlier than mission start time:<br />" "<br />" f"{formatted}<br />" "<br />" "Flight start times are estimated based on the package TOT, so it " "is possible that not all flights will be able to reach the " "target area at their assigned times.<br />" "<br />" "You can either continue with the mission as planned, with the " "misplanned flights potentially flying too fast and/or missing " "their rendezvous; automatically fix negative TOTs; or cancel " "mission start and fix the packages manually."), parent=self) auto = mbox.addButton("Fix TOTs automatically", QMessageBox.ActionRole) ignore = mbox.addButton("Continue without fixing", QMessageBox.DestructiveRole) cancel = mbox.addButton(QMessageBox.Cancel) mbox.setEscapeButton(cancel) mbox.exec_() clicked = mbox.clickedButton() if clicked == auto: self.fix_tots(negative_starts) return True elif clicked == ignore: return True return False def launch_mission(self): """Finishes planning and waits for mission completion.""" if not self.ato_has_clients() and not self.confirm_no_client_launch(): return negative_starts = self.negative_start_packages() if negative_starts: if not self.confirm_negative_start_time(negative_starts): return # TODO: Refactor this nonsense. game_event = None for event in self.game.events: if isinstance(event, FrontlineAttackEvent) and event.is_player_attacking: game_event = event if game_event is None: game_event = FrontlineAttackEvent( self.game, self.game.theater.controlpoints[0], self.game.theater.controlpoints[0], self.game.theater.controlpoints[0].position, self.game.player_name, self.game.enemy_name) game_event.is_awacs_enabled = True game_event.ca_slots = 1 game_event.departure_cp = self.game.theater.controlpoints[0] game_event.player_attacking({CAS: {}, CAP: {}}) game_event.depart_from = self.game.theater.controlpoints[0] self.game.initiate_event(game_event) waiting = QWaitingForMissionResultWindow(game_event, self.game) waiting.show() def budget_update(self, game: Game): self.budgetBox.setGame(game)
class QGroundObjectMenu(QDialog): def __init__(self, parent, ground_object: TheaterGroundObject, buildings: [], cp: ControlPoint, game: Game): super(QGroundObjectMenu, self).__init__(parent) self.setMinimumWidth(350) self.ground_object = ground_object self.buildings = buildings self.cp = cp self.game = game self.setWindowTitle("Location " + self.ground_object.obj_name) self.setWindowIcon(EVENT_ICONS["capture"]) self.intelBox = QGroupBox("Units :") self.buildingBox = QGroupBox("Buildings :") self.intelLayout = QGridLayout() self.buildingsLayout = QGridLayout() self.init_ui() def init_ui(self): self.mainLayout = QVBoxLayout() self.budget = QBudgetBox(self.game) self.budget.setGame(self.game) self.doLayout() if self.ground_object.dcs_identifier == "AA": self.mainLayout.addWidget(self.intelBox) else: self.mainLayout.addWidget(self.buildingBox) self.setLayout(self.mainLayout) def doLayout(self): self.intelBox = QGroupBox("Units :") self.intelLayout = QGridLayout() i = 0 for g in self.ground_object.groups: if not hasattr(g, "units_losts"): g.units_losts = [] for u in g.units: self.intelLayout.addWidget( QLabel("<b>Unit #" + str(u.id) + " - " + str(u.type) + "</b>"), i, 0) i = i + 1 for u in g.units_losts: utype = unit_type_of(u) if utype in PRICES: price = PRICES[utype] else: price = 6 self.intelLayout.addWidget( QLabel("<b>Unit #" + str(u.id) + " - " + str(u.type) + "</b> [DEAD]"), i, 0) if self.cp.captured: repair = QPushButton("Repair [" + str(price) + "M]") repair.setProperty("style", "btn-success") repair.clicked.connect( lambda u=u, g=g, p=price: self.repair_unit(g, u, p)) self.intelLayout.addWidget(repair, i, 1) i = i + 1 self.buildingBox = QGroupBox("Buildings :") self.buildingsLayout = QGridLayout() j = 0 for i, building in enumerate(self.buildings): if building.dcs_identifier not in FORTIFICATION_BUILDINGS: self.buildingsLayout.addWidget( QBuildingInfo(building, self.ground_object), j / 3, j % 3) j = j + 1 self.buildingBox.setLayout(self.buildingsLayout) self.intelBox.setLayout(self.intelLayout) def do_refresh_layout(self): try: for i in range(self.mainLayout.count()): self.mainLayout.removeItem(self.mainLayout.itemAt(i)) self.doLayout() if len(self.ground_object.groups) > 0: self.mainLayout.addWidget(self.intelBox) else: self.mainLayout.addWidget(self.buildingBox) except Exception as e: print(e) def repair_unit(self, group, unit, price): if self.game.budget > price: self.game.budget -= price group.units_losts = [ u for u in group.units_losts if u.id != unit.id ] group.units.append(unit) 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"]) if p.distance_to_point(unit.position) < 15: destroyed_units.remove(d) logging.info("Removed destroyed units " + str(d)) logging.info("Repaired unit : " + str(unit.id) + " " + str(unit.type)) self.do_refresh_layout() def closeEvent(self, closeEvent: QCloseEvent): GameUpdateSignal.get_instance().updateGame(self.game)
class QGroundObjectMenu(QDialog): def __init__( self, parent, ground_object: TheaterGroundObject, buildings: Optional[List[TheaterGroundObject]], cp: ControlPoint, game: Game, ): super().__init__(parent) self.setMinimumWidth(350) self.ground_object = ground_object if buildings is None: self.buildings = [] else: self.buildings = buildings 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.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.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: if not hasattr(g, "units_losts"): g.units_losts = [] for unit in g.units: unit_display_name = unit.type dcs_unit_type = vehicles.vehicle_map.get(unit.type) if dcs_unit_type is not None: # Hack: Don't know which variant is used. try: unit_display_name = next( GroundUnitType.for_dcs_type(dcs_unit_type) ).name except StopIteration: pass self.intelLayout.addWidget( QLabel( "<b>Unit #" + str(unit.id) + " - " + str(unit_display_name) + "</b>" ), i, 0, ) i = i + 1 for unit in g.units_losts: dcs_unit_type = vehicles.vehicle_map.get(unit.type) if dcs_unit_type is None: continue # Hack: Don't know which variant is used. try: unit_type = next(GroundUnitType.for_dcs_type(dcs_unit_type)) name = unit_type.name price = unit_type.price except StopIteration: name = dcs_unit_type.name price = 0 self.intelLayout.addWidget( QLabel(f"<b>Unit #{unit.id} - {name}</b> [DEAD]"), i, 0 ) if self.cp.captured: repair = QPushButton(f"Repair [{price}M]") repair.setProperty("style", "btn-success") repair.clicked.connect( lambda u=unit, g=g, p=unit_type.price: self.repair_unit(g, u, p) ) self.intelLayout.addWidget(repair, i, 1) i = 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 i, building in enumerate(self.buildings): if building.dcs_identifier not in FORTIFICATION_BUILDINGS: self.buildingsLayout.addWidget( QBuildingInfo(building, self.ground_object), j / 3, j % 3 ) j = j + 1 if building.category in REWARDS.keys(): total_income = total_income + REWARDS[building.category] if not building.is_dead: received_income = received_income + REWARDS[building.category] else: logging.warning(building.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 ) self.financesBox.setLayout(self.financesBoxLayout) self.buildingBox.setLayout(self.buildingsLayout) self.intelBox.setLayout(self.intelLayout) def do_refresh_layout(self): try: for i in 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.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): total_value = 0 if not self.ground_object.purchasable: return for u in self.ground_object.units: # Hack: Unknown variant. unit_type = next(GroundUnitType.for_dcs_type(vehicles.vehicle_map[u.type])) total_value += unit_type.price if self.sell_all_button is not None: self.sell_all_button.setText("Disband (+$" + str(self.total_value) + "M)") self.total_value = total_value def repair_unit(self, group, unit, price): if self.game.budget > price: self.game.budget -= price group.units_losts = [u for u in group.units_losts if u.id != unit.id] group.units.append(unit) 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"]) if p.distance_to_point(unit.position) < 15: destroyed_units.remove(d) logging.info("Removed destroyed units " + str(d)) logging.info("Repaired unit : " + str(unit.id) + " " + str(unit.type)) self.do_refresh_layout() def sell_all(self): self.update_total_value() self.game.budget = self.game.budget + self.total_value self.ground_object.groups = [] # Replan if the tgo was a target of the redfor if any( package.target == self.ground_object for package in self.game.ato_for(player=False).packages ): self.game.initialize_turn(for_red=True, for_blue=False) self.do_refresh_layout() GameUpdateSignal.get_instance().updateGame(self.game) def buy_group(self): self.subwindow = QBuyGroupForGroundObjectDialog( self, self.ground_object, self.cp, self.game, self.total_value ) self.subwindow.show()
class QGroundObjectMenu(QDialog): changed = QtCore.Signal() def __init__(self, parent, ground_object: TheaterGroundObject, buildings: [], cp: ControlPoint, game: Game): super(QGroundObjectMenu, self).__init__(parent) self.setMinimumWidth(350) self.ground_object = ground_object self.buildings = buildings self.cp = cp self.game = game self.setWindowTitle("Location " + self.ground_object.obj_name) self.setWindowIcon(EVENT_ICONS["capture"]) self.intelBox = QGroupBox("Units :") self.buildingBox = QGroupBox("Buildings :") 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 self.ground_object.dcs_identifier == "AA": self.mainLayout.addWidget(self.intelBox) else: self.mainLayout.addWidget(self.buildingBox) 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.total_value > 0: self.actionLayout.addWidget(self.sell_all_button) self.actionLayout.addWidget(self.buy_replace) if self.cp.captured and self.ground_object.dcs_identifier == "AA": 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: if not hasattr(g, "units_losts"): g.units_losts = [] for u in g.units: self.intelLayout.addWidget( QLabel("<b>Unit #" + str(u.id) + " - " + str(u.type) + "</b>"), i, 0) i = i + 1 for u in g.units_losts: utype = unit_type_of(u) if utype in PRICES: price = PRICES[utype] else: price = 6 self.intelLayout.addWidget( QLabel("<b>Unit #" + str(u.id) + " - " + str(u.type) + "</b> [DEAD]"), i, 0) if self.cp.captured: repair = QPushButton("Repair [" + str(price) + "M]") repair.setProperty("style", "btn-success") repair.clicked.connect( lambda u=u, g=g, p=price: self.repair_unit(g, u, p)) self.intelLayout.addWidget(repair, i, 1) i = i + 1 stretch = QVBoxLayout() stretch.addStretch() self.intelLayout.addLayout(stretch, i, 0) self.buildingBox = QGroupBox("Buildings :") self.buildingsLayout = QGridLayout() j = 0 for i, building in enumerate(self.buildings): if building.dcs_identifier not in FORTIFICATION_BUILDINGS: self.buildingsLayout.addWidget( QBuildingInfo(building, self.ground_object), j / 3, j % 3) j = j + 1 self.buildingBox.setLayout(self.buildingsLayout) self.intelBox.setLayout(self.intelLayout) def do_refresh_layout(self): try: for i in 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 self.ground_object.dcs_identifier == "AA": self.mainLayout.addWidget(self.intelBox) else: self.mainLayout.addWidget(self.buildingBox) 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.dcs_identifier == "AA": self.mainLayout.addLayout(self.actionLayout) except Exception as e: print(e) self.update_total_value() self.changed.emit() def update_total_value(self): total_value = 0 for group in self.ground_object.groups: for u in group.units: utype = unit_type_of(u) if utype in PRICES: total_value = total_value + PRICES[utype] else: total_value = total_value + 1 if self.sell_all_button is not None: self.sell_all_button.setText("Disband (+$" + str(self.total_value) + "M)") self.total_value = total_value def repair_unit(self, group, unit, price): if self.game.budget > price: self.game.budget -= price group.units_losts = [ u for u in group.units_losts if u.id != unit.id ] group.units.append(unit) 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"]) if p.distance_to_point(unit.position) < 15: destroyed_units.remove(d) logging.info("Removed destroyed units " + str(d)) logging.info("Repaired unit : " + str(unit.id) + " " + str(unit.type)) self.do_refresh_layout() self.changed.emit() def sell_all(self): self.update_total_value() self.game.budget = self.game.budget + self.total_value self.ground_object.groups = [] self.do_refresh_layout() GameUpdateSignal.get_instance().updateBudget(self.game) def buy_group(self): self.subwindow = QBuyGroupForGroundObjectDialog( self, self.ground_object, self.cp, self.game, self.total_value) self.subwindow.changed.connect(self.do_refresh_layout) self.subwindow.show()
class QTopPanel(QFrame): def __init__(self, game: Game): super(QTopPanel, self).__init__() self.game = game self.setMaximumHeight(70) self.init_ui() GameUpdateSignal.get_instance().gameupdated.connect(self.setGame) def init_ui(self): self.turnCounter = QTurnCounter() self.budgetBox = QBudgetBox(self.game) self.passTurnButton = QPushButton("Pass Turn") self.passTurnButton.setIcon(CONST.ICONS["PassTurn"]) self.passTurnButton.setProperty("style", "btn-primary") self.passTurnButton.clicked.connect(self.passTurn) self.proceedButton = QPushButton("Mission Planning") self.proceedButton.setIcon(CONST.ICONS["Proceed"]) self.proceedButton.setProperty("style", "btn-primary") self.proceedButton.clicked.connect(self.proceed) if self.game and self.game.turn == 0: self.proceedButton.setEnabled(False) self.factionsInfos = QFactionsInfos(self.game) self.settings = QPushButton("Settings") self.settings.setIcon(CONST.ICONS["Settings"]) self.settings.setProperty("style", "btn-primary") self.settings.clicked.connect(self.openSettings) self.statistics = QPushButton("Statistics") self.statistics.setIcon(CONST.ICONS["Statistics"]) self.statistics.setProperty("style", "btn-primary") self.statistics.clicked.connect(self.openStatisticsWindow) self.buttonBox = QGroupBox("Misc") self.buttonBoxLayout = QHBoxLayout() self.buttonBoxLayout.addWidget(self.settings) self.buttonBoxLayout.addWidget(self.statistics) self.buttonBox.setLayout(self.buttonBoxLayout) self.proceedBox = QGroupBox("Proceed") self.proceedBoxLayout = QHBoxLayout() self.proceedBoxLayout.addWidget(self.passTurnButton) self.proceedBoxLayout.addWidget(self.proceedButton) self.proceedBox.setLayout(self.proceedBoxLayout) self.layout = QHBoxLayout() self.layout.addWidget(self.factionsInfos) self.layout.addWidget(self.turnCounter) self.layout.addWidget(self.budgetBox) self.layout.addWidget(self.buttonBox) self.layout.addStretch(1) self.layout.addWidget(self.proceedBox) self.layout.setContentsMargins(0, 0, 0, 0) self.setLayout(self.layout) def setGame(self, game: Game): self.game = game if game is not None: self.turnCounter.setCurrentTurn(self.game.turn, self.game.current_day) self.budgetBox.setGame(self.game) self.factionsInfos.setGame(self.game) if self.game and self.game.turn == 0: self.proceedButton.setEnabled(False) else: self.proceedButton.setEnabled(True) def openSettings(self): self.subwindow = QSettingsWindow(self.game) self.subwindow.show() def openStatisticsWindow(self): self.subwindow = QStatsWindow(self.game) self.subwindow.show() def passTurn(self): self.game.pass_turn(no_action=True) GameUpdateSignal.get_instance().updateGame(self.game) self.proceedButton.setEnabled(True) def proceed(self): self.subwindow = QMissionPlanning(self.game) self.subwindow.show()
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 = 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.do_refresh_layout() def sell_all(self): self.update_total_value() 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()