From 7e40d58d04872fb4f9a079e0358d5e1ccb18e61f Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 25 Apr 2021 14:12:59 -0700 Subject: [PATCH] Add cargo ships to the sim, track kills. Not targetable yet. https://github.com/Khopa/dcs_liberation/issues/826 --- changelog.md | 2 +- game/debriefing.py | 28 +++ game/event/event.py | 9 + game/operation/operation.py | 8 +- game/transfers.py | 45 ++-- game/unitmap.py | 24 ++- gen/cargoshipgen.py | 47 ++++ gen/{convoys.py => convoygen.py} | 4 +- qt_ui/windows/QDebriefingWindow.py | 204 ++++++------------ .../windows/QWaitingForMissionResultWindow.py | 61 +++--- 10 files changed, 233 insertions(+), 199 deletions(-) create mode 100644 gen/cargoshipgen.py rename gen/{convoys.py => convoygen.py} (95%) diff --git a/changelog.md b/changelog.md index a28e5bd2..9a48c7d7 100644 --- a/changelog.md +++ b/changelog.md @@ -4,7 +4,7 @@ Saves from 2.5 are not compatible with 3.0. ## Features/Improvements -* **[Campaign]** Ground units can now be transferred by road and airlift. See https://github.com/Khopa/dcs_liberation/wiki/Unit-Transfers for more information. +* **[Campaign]** Ground units can now be transferred by road, airlift, and cargo ship. See https://github.com/Khopa/dcs_liberation/wiki/Unit-Transfers for more information. * **[Campaign]** Ground units can no longer be sold. To move units to a new location, transfer them. * **[Campaign]** Ground units must now be recruited at a base with a factory and transferred to their destination. When buying units in the UI, the purchase will automatically be fulfilled at the closest factory and a transfer will be created on the next turn. This feature is off by default. * **[UI]** Campaigns generated for an older or newer version of the game will now be marked as incompatible. They can still be played, but bugs may be present. diff --git a/game/debriefing.py b/game/debriefing.py index 1ccafaca..1d534be2 100644 --- a/game/debriefing.py +++ b/game/debriefing.py @@ -22,6 +22,7 @@ from dcs.unittype import FlyingType, UnitType from game import db from game.theater import Airfield, ControlPoint +from game.transfers import CargoShip from game.unitmap import ( AirliftUnit, Building, @@ -70,6 +71,9 @@ class GroundLosses: player_convoy: List[ConvoyUnit] = field(default_factory=list) enemy_convoy: List[ConvoyUnit] = field(default_factory=list) + player_cargo_ships: List[CargoShip] = field(default_factory=list) + enemy_cargo_ships: List[CargoShip] = field(default_factory=list) + player_airlifts: List[AirliftUnit] = field(default_factory=list) enemy_airlifts: List[AirliftUnit] = field(default_factory=list) @@ -138,6 +142,11 @@ class Debriefing: yield from self.ground_losses.player_convoy yield from self.ground_losses.enemy_convoy + @property + def cargo_ship_losses(self) -> Iterator[CargoShip]: + yield from self.ground_losses.player_cargo_ships + yield from self.ground_losses.enemy_cargo_ships + @property def airlift_losses(self) -> Iterator[AirliftUnit]: yield from self.ground_losses.player_airlifts @@ -181,6 +190,17 @@ class Debriefing: losses_by_type[loss.unit_type] += 1 return losses_by_type + def cargo_ship_losses_by_type(self, player: bool) -> Dict[Type[UnitType], int]: + losses_by_type: Dict[Type[UnitType], int] = defaultdict(int) + if player: + ships = self.ground_losses.player_cargo_ships + else: + ships = self.ground_losses.enemy_cargo_ships + for ship in ships: + for unit_type, count in ship.units.items(): + losses_by_type[unit_type] += count + return losses_by_type + def airlift_losses_by_type(self, player: bool) -> Dict[Type[UnitType], int]: losses_by_type: Dict[Type[UnitType], int] = defaultdict(int) if player: @@ -237,6 +257,14 @@ class Debriefing: losses.enemy_convoy.append(convoy_unit) continue + cargo_ship = self.unit_map.cargo_ship(unit_name) + if cargo_ship is not None: + if cargo_ship.player_owned: + losses.player_cargo_ships.append(cargo_ship) + else: + losses.enemy_cargo_ships.append(cargo_ship) + continue + ground_object_unit = self.unit_map.ground_object_unit(unit_name) if ground_object_unit is not None: if ground_object_unit.ground_object.control_point.captured: diff --git a/game/event/event.py b/game/event/event.py index d37664ee..4ca70790 100644 --- a/game/event/event.py +++ b/game/event/event.py @@ -169,6 +169,15 @@ class Event: logging.info(f"{unit_type} destroyed in {convoy_name}") convoy.kill_unit(unit_type) + @staticmethod + def commit_cargo_ship_losses(debriefing: Debriefing) -> None: + for ship in debriefing.cargo_ship_losses: + logging.info( + f"All units destroyed in cargo ship from {ship.origin} to " + f"{ship.destination}." + ) + ship.kill_all() + @staticmethod def commit_airlift_losses(debriefing: Debriefing) -> None: for loss in debriefing.airlift_losses: diff --git a/game/operation/operation.py b/game/operation/operation.py index 1376be88..1304c064 100644 --- a/game/operation/operation.py +++ b/game/operation/operation.py @@ -23,7 +23,8 @@ from gen.airsupportgen import AirSupport, AirSupportConflictGenerator from gen.armor import GroundConflictGenerator, JtacInfo from gen.beacons import load_beacons_for_terrain from gen.briefinggen import BriefingGenerator, MissionInfoGenerator -from gen.convoys import ConvoyGenerator +from gen.cargoshipgen import CargoShipGenerator +from gen.convoygen import ConvoyGenerator from gen.environmentgen import EnvironmentGenerator from gen.forcedoptionsgen import ForcedOptionsGenerator from gen.groundobjectsgen import GroundObjectsGenerator @@ -304,7 +305,7 @@ class Operation: # Set mission time and weather conditions. EnvironmentGenerator(cls.current_mission, cls.game.conditions).generate() cls._generate_ground_units() - cls._generate_convoys() + cls._generate_transports() cls._generate_destroyed_units() cls._generate_air_units() cls.assign_channels_to_flights( @@ -426,9 +427,10 @@ class Operation: cls.jtacs.extend(ground_conflict_gen.jtacs) @classmethod - def _generate_convoys(cls) -> None: + def _generate_transports(cls) -> None: """Generates convoys for unit transfers by road.""" ConvoyGenerator(cls.current_mission, cls.game, cls.unit_map).generate() + CargoShipGenerator(cls.current_mission, cls.game, cls.unit_map).generate() @classmethod def reset_naming_ids(cls): diff --git a/game/transfers.py b/game/transfers.py index 3685ed79..a22cabb6 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -4,7 +4,7 @@ import logging from collections import defaultdict from dataclasses import dataclass, field from functools import singledispatchmethod -from typing import Dict, Iterator, List, Optional, TYPE_CHECKING, Type +from typing import Dict, Generic, Iterator, List, Optional, TYPE_CHECKING, Type, TypeVar from dcs.mapping import Point from dcs.unittype import FlyingType, VehicleType @@ -271,6 +271,10 @@ class MultiGroupTransport(MissionTarget, Transport): pass raise KeyError + def kill_all(self) -> None: + for transfer in self.transfers: + transfer.kill_all() + def disband(self) -> None: for transfer in list(self.transfers): self.remove_units(transfer) @@ -298,14 +302,6 @@ class MultiGroupTransport(MissionTarget, Transport): def description(self) -> str: raise NotImplementedError - @property - def route_start(self) -> Point: - raise NotImplementedError - - @property - def route_end(self) -> Point: - raise NotImplementedError - class Convoy(MultiGroupTransport): def __init__(self, origin: ControlPoint, destination: ControlPoint) -> None: @@ -345,12 +341,8 @@ class CargoShip(MultiGroupTransport): yield from super().mission_types(for_player) @property - def route_start(self) -> Point: - return self.origin.shipping_lanes[self.destination][0] - - @property - def route_end(self) -> Point: - return self.destination.shipping_lanes[self.origin][-1] + def route(self) -> List[Point]: + return self.origin.shipping_lanes[self.destination] def description(self) -> str: return f"On a ship to {self.destination}" @@ -359,16 +351,19 @@ class CargoShip(MultiGroupTransport): return None -class TransportMap: +TransportType = TypeVar("TransportType", bound=MultiGroupTransport) + + +class TransportMap(Generic[TransportType]): def __init__(self) -> None: # Dict of origin -> destination -> transport. self.transports: Dict[ - ControlPoint, Dict[ControlPoint, MultiGroupTransport] + ControlPoint, Dict[ControlPoint, TransportType] ] = defaultdict(dict) def create_transport( self, origin: ControlPoint, destination: ControlPoint - ) -> MultiGroupTransport: + ) -> TransportType: raise NotImplementedError def transport_exists(self, origin: ControlPoint, destination: ControlPoint) -> bool: @@ -376,27 +371,27 @@ class TransportMap: def find_transport( self, origin: ControlPoint, destination: ControlPoint - ) -> Optional[MultiGroupTransport]: + ) -> Optional[TransportType]: return self.transports[origin].get(destination) def find_or_create_transport( self, origin: ControlPoint, destination: ControlPoint - ) -> MultiGroupTransport: + ) -> TransportType: transport = self.find_transport(origin, destination) if transport is None: transport = self.create_transport(origin, destination) self.transports[origin][destination] = transport return transport - def departing_from(self, origin: ControlPoint) -> Iterator[MultiGroupTransport]: + def departing_from(self, origin: ControlPoint) -> Iterator[TransportType]: yield from self.transports[origin].values() - def travelling_to(self, destination: ControlPoint) -> Iterator[MultiGroupTransport]: + def travelling_to(self, destination: ControlPoint) -> Iterator[TransportType]: for destination_dict in self.transports.values(): if destination in destination_dict: yield destination_dict[destination] - def disband_transport(self, transport: MultiGroupTransport) -> None: + def disband_transport(self, transport: TransportType) -> None: transport.disband() del self.transports[transport.origin][transport.destination] @@ -416,7 +411,7 @@ class TransportMap: next_stop = self.next_stop_for(transfer) self.find_or_create_transport(transfer.position, next_stop).add_units(transfer) - def remove(self, transport: MultiGroupTransport, transfer: TransferOrder) -> None: + def remove(self, transport: TransportType, transfer: TransferOrder) -> None: transport.remove_units(transfer) if not transport.transfers: self.disband_transport(transport) @@ -425,7 +420,7 @@ class TransportMap: for transport in list(self): self.disband_transport(transport) - def __iter__(self) -> Iterator[MultiGroupTransport]: + def __iter__(self) -> Iterator[TransportType]: for destination_dict in self.transports.values(): yield from destination_dict.values() diff --git a/game/unitmap.py b/game/unitmap.py index 4571f2b3..dd1f527b 100644 --- a/game/unitmap.py +++ b/game/unitmap.py @@ -9,7 +9,7 @@ from dcs.unittype import VehicleType from game import db from game.theater import Airfield, ControlPoint, TheaterGroundObject from game.theater.theatergroundobject import BuildingGroundObject -from game.transfers import MultiGroupTransport, TransferOrder +from game.transfers import CargoShip, Convoy, TransferOrder from gen.flights.flight import Flight @@ -29,7 +29,7 @@ class GroundObjectUnit: @dataclass(frozen=True) class ConvoyUnit: unit_type: Type[VehicleType] - convoy: MultiGroupTransport + convoy: Convoy @dataclass(frozen=True) @@ -51,6 +51,7 @@ class UnitMap: self.ground_object_units: Dict[str, GroundObjectUnit] = {} self.buildings: Dict[str, Building] = {} self.convoys: Dict[str, ConvoyUnit] = {} + self.cargo_ships: Dict[str, CargoShip] = {} self.airlifts: Dict[str, AirliftUnit] = {} def add_aircraft(self, group: FlyingGroup, flight: Flight) -> None: @@ -130,7 +131,7 @@ class UnitMap: def ground_object_unit(self, name: str) -> Optional[GroundObjectUnit]: return self.ground_object_units.get(name, None) - def add_convoy_units(self, group: Group, convoy: MultiGroupTransport) -> None: + def add_convoy_units(self, group: Group, convoy: Convoy) -> None: for unit in group.units: # The actual name is a String (the pydcs translatable string), which # doesn't define __eq__. @@ -149,6 +150,23 @@ class UnitMap: def convoy_unit(self, name: str) -> Optional[ConvoyUnit]: return self.convoys.get(name, None) + def add_cargo_ship(self, group: Group, ship: CargoShip) -> None: + if len(group.units) > 1: + # Cargo ship "groups" are single units. Killing the one ship kills the whole + # transfer. If we ever want to add escorts or create multiple cargo ships in + # a convoy of ships that logic needs to change. + raise ValueError("Expected cargo ship to be a single unit group.") + unit = group.units[0] + # The actual name is a String (the pydcs translatable string), which + # doesn't define __eq__. + name = str(unit.name) + if name in self.cargo_ships: + raise RuntimeError(f"Duplicate cargo ship: {name}") + self.cargo_ships[name] = ship + + def cargo_ship(self, name: str) -> Optional[CargoShip]: + return self.cargo_ships.get(name, None) + def add_airlift_units(self, group: FlyingGroup, transfer: TransferOrder) -> None: for transport, cargo_type in zip(group.units, transfer.iter_units()): # The actual name is a String (the pydcs translatable string), which diff --git a/gen/cargoshipgen.py b/gen/cargoshipgen.py new file mode 100644 index 00000000..e3161a42 --- /dev/null +++ b/gen/cargoshipgen.py @@ -0,0 +1,47 @@ +from __future__ import annotations + +import itertools +from typing import TYPE_CHECKING + +from dcs import Mission +from dcs.ships import Bulker_Handy_Wind +from dcs.unitgroup import ShipGroup + +from game.transfers import CargoShip +from game.unitmap import UnitMap +from game.utils import knots + +if TYPE_CHECKING: + from game import Game + + +class CargoShipGenerator: + def __init__(self, mission: Mission, game: Game, unit_map: UnitMap) -> None: + self.mission = mission + self.game = game + self.unit_map = unit_map + self.count = itertools.count() + + def generate(self) -> None: + # Reset the count to make generation deterministic. + for ship in self.game.transfers.cargo_ships: + self.generate_cargo_ship(ship) + + def generate_cargo_ship(self, ship: CargoShip) -> ShipGroup: + country = self.mission.country( + self.game.player_country if ship.player_owned else self.game.enemy_country + ) + waypoints = ship.route + group = self.mission.ship_group( + country, + ship.name, + Bulker_Handy_Wind, + position=waypoints[0], + group_size=1, + ) + for waypoint in waypoints[1:]: + # 12 knots is very slow but it's also nearly the max allowed by DCS for this + # type of ship. + group.add_waypoint(waypoint, speed=knots(12).kph) + self.unit_map.add_cargo_ship(group, ship) + return group diff --git a/gen/convoys.py b/gen/convoygen.py similarity index 95% rename from gen/convoys.py rename to gen/convoygen.py index f3033e9e..9c904009 100644 --- a/gen/convoys.py +++ b/gen/convoygen.py @@ -10,7 +10,7 @@ from dcs.unit import Vehicle from dcs.unitgroup import VehicleGroup from dcs.unittype import VehicleType -from game.transfers import MultiGroupTransport +from game.transfers import Convoy from game.unitmap import UnitMap from game.utils import kph @@ -30,7 +30,7 @@ class ConvoyGenerator: for convoy in self.game.transfers.convoys: self.generate_convoy(convoy) - def generate_convoy(self, convoy: MultiGroupTransport) -> VehicleGroup: + def generate_convoy(self, convoy: Convoy) -> VehicleGroup: group = self._create_mixed_unit_group( convoy.name, convoy.route_start, diff --git a/qt_ui/windows/QDebriefingWindow.py b/qt_ui/windows/QDebriefingWindow.py index 7308b0e6..86e59e0d 100644 --- a/qt_ui/windows/QDebriefingWindow.py +++ b/qt_ui/windows/QDebriefingWindow.py @@ -1,4 +1,5 @@ import logging +from typing import Callable, Dict, TypeVar from PySide2.QtGui import QIcon, QPixmap from PySide2.QtWidgets import ( @@ -14,6 +15,57 @@ from game import db from game.debriefing import Debriefing +T = TypeVar("T") + + +class LossGrid(QGridLayout): + def __init__(self, debriefing: Debriefing, player: bool) -> None: + super().__init__() + + if player: + country = debriefing.player_country + else: + country = debriefing.enemy_country + + self.add_loss_rows( + debriefing.air_losses.by_type(player), + lambda u: db.unit_get_expanded_info(country, u, "name"), + ) + self.add_loss_rows( + debriefing.front_line_losses_by_type(player), + lambda u: db.unit_type_name(u), + ) + self.add_loss_rows( + debriefing.convoy_losses_by_type(player), + lambda u: f"{db.unit_type_name(u)} from convoy", + ) + self.add_loss_rows( + debriefing.cargo_ship_losses_by_type(player), + lambda u: f"{db.unit_type_name(u)} from cargo ship", + ) + self.add_loss_rows( + debriefing.airlift_losses_by_type(player), + lambda u: f"{db.unit_type_name(u)} from airlift", + ) + self.add_loss_rows( + debriefing.building_losses_by_type(player), + lambda u: u, + ) + + # TODO: Display dead ground object units and runways. + + def add_loss_rows(self, losses: Dict[T, int], make_name: Callable[[T], str]): + for unit_type, count in losses.items(): + row = self.rowCount() + try: + name = make_name(unit_type) + except AttributeError: + logging.exception(f"Could not make unit name for {unit_type}") + name = unit_type.id + self.addWidget(QLabel(name), row, 0) + self.addWidget(QLabel(str(count)), row, 1) + + class QDebriefingWindow(QDialog): def __init__(self, debriefing: Debriefing): super(QDebriefingWindow, self).__init__() @@ -24,155 +76,27 @@ class QDebriefingWindow(QDialog): self.setMinimumSize(300, 200) self.setWindowIcon(QIcon("./resources/icon.png")) - self.initUI() - - def initUI(self): - - self.layout = QVBoxLayout() + layout = QVBoxLayout() + self.setLayout(layout) header = QLabel(self) header.setGeometry(0, 0, 655, 106) pixmap = QPixmap("./resources/ui/debriefing.png") header.setPixmap(pixmap) - self.layout.addWidget(header) - self.layout.addStretch() + layout.addWidget(header) + layout.addStretch() title = QLabel("Casualty report") - self.layout.addWidget(title) + layout.addWidget(title) - # Player lost units - lostUnits = QGroupBox(f"{self.debriefing.player_country}'s lost units:") - lostUnitsLayout = QGridLayout() - lostUnits.setLayout(lostUnitsLayout) + player_lost_units = QGroupBox(f"{self.debriefing.player_country}'s lost units:") + player_lost_units.setLayout(LossGrid(debriefing, player=True)) + layout.addWidget(player_lost_units) - row = 0 - player_air_losses = self.debriefing.air_losses.by_type(player=True) - for unit_type, count in player_air_losses.items(): - try: - lostUnitsLayout.addWidget( - QLabel( - db.unit_get_expanded_info( - self.debriefing.player_country, unit_type, "name" - ) - ), - row, - 0, - ) - lostUnitsLayout.addWidget(QLabel(str(count)), row, 1) - row += 1 - except AttributeError: - logging.exception(f"Issue adding {unit_type} to debriefing information") + enemy_lost_units = QGroupBox(f"{self.debriefing.enemy_country}'s lost units:") + enemy_lost_units.setLayout(LossGrid(debriefing, player=False)) + layout.addWidget(enemy_lost_units) - front_line_losses = self.debriefing.front_line_losses_by_type(player=True) - for unit_type, count in front_line_losses.items(): - try: - lostUnitsLayout.addWidget(QLabel(db.unit_type_name(unit_type)), row, 0) - lostUnitsLayout.addWidget(QLabel(str(count)), row, 1) - row += 1 - except AttributeError: - logging.exception(f"Issue adding {unit_type} to debriefing information") - - convoy_losses = self.debriefing.convoy_losses_by_type(player=True) - for unit_type, count in convoy_losses.items(): - try: - lostUnitsLayout.addWidget( - QLabel(f"{db.unit_type_name(unit_type)} from convoy"), row, 0 - ) - lostUnitsLayout.addWidget(QLabel(str(count)), row, 1) - row += 1 - except AttributeError: - logging.exception(f"Issue adding {unit_type} to debriefing information") - - airlift_losses = self.debriefing.airlift_losses_by_type(player=True) - for unit_type, count in airlift_losses.items(): - try: - lostUnitsLayout.addWidget( - QLabel(f"{db.unit_type_name(unit_type)} from airlift"), row, 0 - ) - lostUnitsLayout.addWidget(QLabel(str(count)), row, 1) - row += 1 - except AttributeError: - logging.exception(f"Issue adding {unit_type} to debriefing information") - - building_losses = self.debriefing.building_losses_by_type(player=True) - for building, count in building_losses.items(): - try: - lostUnitsLayout.addWidget(QLabel(building), row, 0) - lostUnitsLayout.addWidget(QLabel(str(count)), row, 1) - row += 1 - except AttributeError: - logging.exception(f"Issue adding {building} to debriefing information") - - self.layout.addWidget(lostUnits) - - # Enemy lost units - enemylostUnits = QGroupBox(f"{self.debriefing.enemy_country}'s lost units:") - enemylostUnitsLayout = QGridLayout() - enemylostUnits.setLayout(enemylostUnitsLayout) - - enemy_air_losses = self.debriefing.air_losses.by_type(player=False) - for unit_type, count in enemy_air_losses.items(): - try: - enemylostUnitsLayout.addWidget( - QLabel( - db.unit_get_expanded_info( - self.debriefing.enemy_country, unit_type, "name" - ) - ), - row, - 0, - ) - enemylostUnitsLayout.addWidget(QLabel(str(count)), row, 1) - row += 1 - except AttributeError: - logging.exception(f"Issue adding {unit_type} to debriefing information") - - front_line_losses = self.debriefing.front_line_losses_by_type(player=False) - for unit_type, count in front_line_losses.items(): - if count == 0: - continue - enemylostUnitsLayout.addWidget(QLabel(db.unit_type_name(unit_type)), row, 0) - enemylostUnitsLayout.addWidget(QLabel("{}".format(count)), row, 1) - row += 1 - - convoy_losses = self.debriefing.convoy_losses_by_type(player=False) - for unit_type, count in convoy_losses.items(): - try: - lostUnitsLayout.addWidget( - QLabel(f"{db.unit_type_name(unit_type)} from convoy"), row, 0 - ) - lostUnitsLayout.addWidget(QLabel(str(count)), row, 1) - row += 1 - except AttributeError: - logging.exception(f"Issue adding {unit_type} to debriefing information") - - airlift_losses = self.debriefing.airlift_losses_by_type(player=False) - for unit_type, count in airlift_losses.items(): - try: - lostUnitsLayout.addWidget( - QLabel(f"{db.unit_type_name(unit_type)} from airlift"), row, 0 - ) - lostUnitsLayout.addWidget(QLabel(str(count)), row, 1) - row += 1 - except AttributeError: - logging.exception(f"Issue adding {unit_type} to debriefing information") - - building_losses = self.debriefing.building_losses_by_type(player=False) - for building, count in building_losses.items(): - try: - enemylostUnitsLayout.addWidget(QLabel(building), row, 0) - enemylostUnitsLayout.addWidget(QLabel("{}".format(count)), row, 1) - row += 1 - except AttributeError: - logging.exception(f"Issue adding {building} to debriefing information") - - self.layout.addWidget(enemylostUnits) - - # TODO: Display dead ground object units and runways. - - # confirm button okay = QPushButton("Okay") okay.clicked.connect(self.close) - self.layout.addWidget(okay) - - self.setLayout(self.layout) + layout.addWidget(okay) diff --git a/qt_ui/windows/QWaitingForMissionResultWindow.py b/qt_ui/windows/QWaitingForMissionResultWindow.py index bd7f6163..10219007 100644 --- a/qt_ui/windows/QWaitingForMissionResultWindow.py +++ b/qt_ui/windows/QWaitingForMissionResultWindow.py @@ -4,6 +4,7 @@ import json import os import timeit from datetime import timedelta +from typing import Sized from PySide2 import QtCore from PySide2.QtCore import QObject, Qt, Signal @@ -132,38 +133,48 @@ class QWaitingForMissionResultWindow(QDialog): self.layout.addLayout(self.gridLayout, 1, 0) self.setLayout(self.layout) + @staticmethod + def add_update_row(description: str, count: Sized, layout: QGridLayout) -> None: + row = layout.rowCount() + layout.addWidget(QLabel(f"{description}"), row, 0) + layout.addWidget(QLabel(f"{len(count)}"), row, 1) + def updateLayout(self, debriefing: Debriefing) -> None: updateBox = QGroupBox("Mission status") - updateLayout = QGridLayout() - updateBox.setLayout(updateLayout) + update_layout = QGridLayout() + updateBox.setLayout(update_layout) self.debriefing = debriefing - updateLayout.addWidget(QLabel("Aircraft destroyed"), 0, 0) - updateLayout.addWidget( - QLabel(str(len(list(debriefing.air_losses.losses)))), 0, 1 + self.add_update_row( + "Aircraft destroyed", list(debriefing.air_losses.losses), update_layout ) - - updateLayout.addWidget(QLabel("Front line units destroyed"), 1, 0) - updateLayout.addWidget( - QLabel(str(len(list(debriefing.front_line_losses)))), 1, 1 + self.add_update_row( + "Front line units destroyed", + list(debriefing.front_line_losses), + update_layout, ) - - updateLayout.addWidget(QLabel("Convoy units destroyed"), 2, 0) - updateLayout.addWidget(QLabel(str(len(list(debriefing.convoy_losses)))), 2, 1) - - updateLayout.addWidget(QLabel("Airlift cargo destroyed"), 3, 0) - updateLayout.addWidget(QLabel(str(len(list(debriefing.airlift_losses)))), 3, 1) - - updateLayout.addWidget(QLabel("Other ground units destroyed"), 4, 0) - updateLayout.addWidget( - QLabel(str(len(list(debriefing.ground_object_losses)))), 4, 1 + self.add_update_row( + "Convoy units destroyed", list(debriefing.convoy_losses), update_layout + ) + self.add_update_row( + "Shipping cargo destroyed", + list(debriefing.cargo_ship_losses), + update_layout, + ) + self.add_update_row( + "Airlift cargo destroyed", list(debriefing.airlift_losses), update_layout + ) + self.add_update_row( + "Ground units lost at objective areas", + list(debriefing.ground_object_losses), + update_layout, + ) + self.add_update_row( + "Buildings destroyed", list(debriefing.building_losses), update_layout + ) + self.add_update_row( + "Base capture events", list(debriefing.base_capture_events), update_layout ) - - updateLayout.addWidget(QLabel("Buildings destroyed"), 5, 0) - updateLayout.addWidget(QLabel(str(len(list(debriefing.building_losses)))), 5, 1) - - updateLayout.addWidget(QLabel("Base Capture Events"), 6, 0) - updateLayout.addWidget(QLabel(str(len(debriefing.base_capture_events))), 6, 1) # Clear previous content of the window for i in reversed(range(self.gridLayout.count())):