Add cargo ships to the sim, track kills.

Not targetable yet.

https://github.com/Khopa/dcs_liberation/issues/826
This commit is contained in:
Dan Albert 2021-04-25 14:12:59 -07:00
parent ba8fafcc95
commit 7e40d58d04
10 changed files with 233 additions and 199 deletions

View File

@ -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.

View File

@ -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:

View File

@ -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:

View File

@ -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):

View File

@ -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()

View File

@ -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

47
gen/cargoshipgen.py Normal file
View File

@ -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

View File

@ -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,

View File

@ -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("<b>Casualty report</b>")
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)

View File

@ -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"<b>{description}</b>"), 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("<b>Aircraft destroyed</b>"), 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("<b>Front line units destroyed</b>"), 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("<b>Convoy units destroyed</b>"), 2, 0)
updateLayout.addWidget(QLabel(str(len(list(debriefing.convoy_losses)))), 2, 1)
updateLayout.addWidget(QLabel("<b>Airlift cargo destroyed</b>"), 3, 0)
updateLayout.addWidget(QLabel(str(len(list(debriefing.airlift_losses)))), 3, 1)
updateLayout.addWidget(QLabel("<b>Other ground units destroyed</b>"), 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("<b>Buildings destroyed</b>"), 5, 0)
updateLayout.addWidget(QLabel(str(len(list(debriefing.building_losses)))), 5, 1)
updateLayout.addWidget(QLabel("<b>Base Capture Events</b>"), 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())):