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 ## 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 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. * **[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. * **[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 import db
from game.theater import Airfield, ControlPoint from game.theater import Airfield, ControlPoint
from game.transfers import CargoShip
from game.unitmap import ( from game.unitmap import (
AirliftUnit, AirliftUnit,
Building, Building,
@ -70,6 +71,9 @@ class GroundLosses:
player_convoy: List[ConvoyUnit] = field(default_factory=list) player_convoy: List[ConvoyUnit] = field(default_factory=list)
enemy_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) player_airlifts: List[AirliftUnit] = field(default_factory=list)
enemy_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.player_convoy
yield from self.ground_losses.enemy_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 @property
def airlift_losses(self) -> Iterator[AirliftUnit]: def airlift_losses(self) -> Iterator[AirliftUnit]:
yield from self.ground_losses.player_airlifts yield from self.ground_losses.player_airlifts
@ -181,6 +190,17 @@ class Debriefing:
losses_by_type[loss.unit_type] += 1 losses_by_type[loss.unit_type] += 1
return losses_by_type 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]: def airlift_losses_by_type(self, player: bool) -> Dict[Type[UnitType], int]:
losses_by_type: Dict[Type[UnitType], int] = defaultdict(int) losses_by_type: Dict[Type[UnitType], int] = defaultdict(int)
if player: if player:
@ -237,6 +257,14 @@ class Debriefing:
losses.enemy_convoy.append(convoy_unit) losses.enemy_convoy.append(convoy_unit)
continue 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) ground_object_unit = self.unit_map.ground_object_unit(unit_name)
if ground_object_unit is not None: if ground_object_unit is not None:
if ground_object_unit.ground_object.control_point.captured: 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}") logging.info(f"{unit_type} destroyed in {convoy_name}")
convoy.kill_unit(unit_type) 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 @staticmethod
def commit_airlift_losses(debriefing: Debriefing) -> None: def commit_airlift_losses(debriefing: Debriefing) -> None:
for loss in debriefing.airlift_losses: 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.armor import GroundConflictGenerator, JtacInfo
from gen.beacons import load_beacons_for_terrain from gen.beacons import load_beacons_for_terrain
from gen.briefinggen import BriefingGenerator, MissionInfoGenerator 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.environmentgen import EnvironmentGenerator
from gen.forcedoptionsgen import ForcedOptionsGenerator from gen.forcedoptionsgen import ForcedOptionsGenerator
from gen.groundobjectsgen import GroundObjectsGenerator from gen.groundobjectsgen import GroundObjectsGenerator
@ -304,7 +305,7 @@ class Operation:
# Set mission time and weather conditions. # Set mission time and weather conditions.
EnvironmentGenerator(cls.current_mission, cls.game.conditions).generate() EnvironmentGenerator(cls.current_mission, cls.game.conditions).generate()
cls._generate_ground_units() cls._generate_ground_units()
cls._generate_convoys() cls._generate_transports()
cls._generate_destroyed_units() cls._generate_destroyed_units()
cls._generate_air_units() cls._generate_air_units()
cls.assign_channels_to_flights( cls.assign_channels_to_flights(
@ -426,9 +427,10 @@ class Operation:
cls.jtacs.extend(ground_conflict_gen.jtacs) cls.jtacs.extend(ground_conflict_gen.jtacs)
@classmethod @classmethod
def _generate_convoys(cls) -> None: def _generate_transports(cls) -> None:
"""Generates convoys for unit transfers by road.""" """Generates convoys for unit transfers by road."""
ConvoyGenerator(cls.current_mission, cls.game, cls.unit_map).generate() ConvoyGenerator(cls.current_mission, cls.game, cls.unit_map).generate()
CargoShipGenerator(cls.current_mission, cls.game, cls.unit_map).generate()
@classmethod @classmethod
def reset_naming_ids(cls): def reset_naming_ids(cls):

View File

@ -4,7 +4,7 @@ import logging
from collections import defaultdict from collections import defaultdict
from dataclasses import dataclass, field from dataclasses import dataclass, field
from functools import singledispatchmethod 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.mapping import Point
from dcs.unittype import FlyingType, VehicleType from dcs.unittype import FlyingType, VehicleType
@ -271,6 +271,10 @@ class MultiGroupTransport(MissionTarget, Transport):
pass pass
raise KeyError raise KeyError
def kill_all(self) -> None:
for transfer in self.transfers:
transfer.kill_all()
def disband(self) -> None: def disband(self) -> None:
for transfer in list(self.transfers): for transfer in list(self.transfers):
self.remove_units(transfer) self.remove_units(transfer)
@ -298,14 +302,6 @@ class MultiGroupTransport(MissionTarget, Transport):
def description(self) -> str: def description(self) -> str:
raise NotImplementedError raise NotImplementedError
@property
def route_start(self) -> Point:
raise NotImplementedError
@property
def route_end(self) -> Point:
raise NotImplementedError
class Convoy(MultiGroupTransport): class Convoy(MultiGroupTransport):
def __init__(self, origin: ControlPoint, destination: ControlPoint) -> None: def __init__(self, origin: ControlPoint, destination: ControlPoint) -> None:
@ -345,12 +341,8 @@ class CargoShip(MultiGroupTransport):
yield from super().mission_types(for_player) yield from super().mission_types(for_player)
@property @property
def route_start(self) -> Point: def route(self) -> List[Point]:
return self.origin.shipping_lanes[self.destination][0] return self.origin.shipping_lanes[self.destination]
@property
def route_end(self) -> Point:
return self.destination.shipping_lanes[self.origin][-1]
def description(self) -> str: def description(self) -> str:
return f"On a ship to {self.destination}" return f"On a ship to {self.destination}"
@ -359,16 +351,19 @@ class CargoShip(MultiGroupTransport):
return None return None
class TransportMap: TransportType = TypeVar("TransportType", bound=MultiGroupTransport)
class TransportMap(Generic[TransportType]):
def __init__(self) -> None: def __init__(self) -> None:
# Dict of origin -> destination -> transport. # Dict of origin -> destination -> transport.
self.transports: Dict[ self.transports: Dict[
ControlPoint, Dict[ControlPoint, MultiGroupTransport] ControlPoint, Dict[ControlPoint, TransportType]
] = defaultdict(dict) ] = defaultdict(dict)
def create_transport( def create_transport(
self, origin: ControlPoint, destination: ControlPoint self, origin: ControlPoint, destination: ControlPoint
) -> MultiGroupTransport: ) -> TransportType:
raise NotImplementedError raise NotImplementedError
def transport_exists(self, origin: ControlPoint, destination: ControlPoint) -> bool: def transport_exists(self, origin: ControlPoint, destination: ControlPoint) -> bool:
@ -376,27 +371,27 @@ class TransportMap:
def find_transport( def find_transport(
self, origin: ControlPoint, destination: ControlPoint self, origin: ControlPoint, destination: ControlPoint
) -> Optional[MultiGroupTransport]: ) -> Optional[TransportType]:
return self.transports[origin].get(destination) return self.transports[origin].get(destination)
def find_or_create_transport( def find_or_create_transport(
self, origin: ControlPoint, destination: ControlPoint self, origin: ControlPoint, destination: ControlPoint
) -> MultiGroupTransport: ) -> TransportType:
transport = self.find_transport(origin, destination) transport = self.find_transport(origin, destination)
if transport is None: if transport is None:
transport = self.create_transport(origin, destination) transport = self.create_transport(origin, destination)
self.transports[origin][destination] = transport self.transports[origin][destination] = transport
return 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() 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(): for destination_dict in self.transports.values():
if destination in destination_dict: if destination in destination_dict:
yield destination_dict[destination] yield destination_dict[destination]
def disband_transport(self, transport: MultiGroupTransport) -> None: def disband_transport(self, transport: TransportType) -> None:
transport.disband() transport.disband()
del self.transports[transport.origin][transport.destination] del self.transports[transport.origin][transport.destination]
@ -416,7 +411,7 @@ class TransportMap:
next_stop = self.next_stop_for(transfer) next_stop = self.next_stop_for(transfer)
self.find_or_create_transport(transfer.position, next_stop).add_units(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) transport.remove_units(transfer)
if not transport.transfers: if not transport.transfers:
self.disband_transport(transport) self.disband_transport(transport)
@ -425,7 +420,7 @@ class TransportMap:
for transport in list(self): for transport in list(self):
self.disband_transport(transport) self.disband_transport(transport)
def __iter__(self) -> Iterator[MultiGroupTransport]: def __iter__(self) -> Iterator[TransportType]:
for destination_dict in self.transports.values(): for destination_dict in self.transports.values():
yield from destination_dict.values() yield from destination_dict.values()

View File

@ -9,7 +9,7 @@ from dcs.unittype import VehicleType
from game import db from game import db
from game.theater import Airfield, ControlPoint, TheaterGroundObject from game.theater import Airfield, ControlPoint, TheaterGroundObject
from game.theater.theatergroundobject import BuildingGroundObject 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 from gen.flights.flight import Flight
@ -29,7 +29,7 @@ class GroundObjectUnit:
@dataclass(frozen=True) @dataclass(frozen=True)
class ConvoyUnit: class ConvoyUnit:
unit_type: Type[VehicleType] unit_type: Type[VehicleType]
convoy: MultiGroupTransport convoy: Convoy
@dataclass(frozen=True) @dataclass(frozen=True)
@ -51,6 +51,7 @@ class UnitMap:
self.ground_object_units: Dict[str, GroundObjectUnit] = {} self.ground_object_units: Dict[str, GroundObjectUnit] = {}
self.buildings: Dict[str, Building] = {} self.buildings: Dict[str, Building] = {}
self.convoys: Dict[str, ConvoyUnit] = {} self.convoys: Dict[str, ConvoyUnit] = {}
self.cargo_ships: Dict[str, CargoShip] = {}
self.airlifts: Dict[str, AirliftUnit] = {} self.airlifts: Dict[str, AirliftUnit] = {}
def add_aircraft(self, group: FlyingGroup, flight: Flight) -> None: def add_aircraft(self, group: FlyingGroup, flight: Flight) -> None:
@ -130,7 +131,7 @@ class UnitMap:
def ground_object_unit(self, name: str) -> Optional[GroundObjectUnit]: def ground_object_unit(self, name: str) -> Optional[GroundObjectUnit]:
return self.ground_object_units.get(name, None) 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: for unit in group.units:
# The actual name is a String (the pydcs translatable string), which # The actual name is a String (the pydcs translatable string), which
# doesn't define __eq__. # doesn't define __eq__.
@ -149,6 +150,23 @@ class UnitMap:
def convoy_unit(self, name: str) -> Optional[ConvoyUnit]: def convoy_unit(self, name: str) -> Optional[ConvoyUnit]:
return self.convoys.get(name, None) 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: def add_airlift_units(self, group: FlyingGroup, transfer: TransferOrder) -> None:
for transport, cargo_type in zip(group.units, transfer.iter_units()): for transport, cargo_type in zip(group.units, transfer.iter_units()):
# The actual name is a String (the pydcs translatable string), which # 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.unitgroup import VehicleGroup
from dcs.unittype import VehicleType from dcs.unittype import VehicleType
from game.transfers import MultiGroupTransport from game.transfers import Convoy
from game.unitmap import UnitMap from game.unitmap import UnitMap
from game.utils import kph from game.utils import kph
@ -30,7 +30,7 @@ class ConvoyGenerator:
for convoy in self.game.transfers.convoys: for convoy in self.game.transfers.convoys:
self.generate_convoy(convoy) self.generate_convoy(convoy)
def generate_convoy(self, convoy: MultiGroupTransport) -> VehicleGroup: def generate_convoy(self, convoy: Convoy) -> VehicleGroup:
group = self._create_mixed_unit_group( group = self._create_mixed_unit_group(
convoy.name, convoy.name,
convoy.route_start, convoy.route_start,

View File

@ -1,4 +1,5 @@
import logging import logging
from typing import Callable, Dict, TypeVar
from PySide2.QtGui import QIcon, QPixmap from PySide2.QtGui import QIcon, QPixmap
from PySide2.QtWidgets import ( from PySide2.QtWidgets import (
@ -14,6 +15,57 @@ from game import db
from game.debriefing import Debriefing 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): class QDebriefingWindow(QDialog):
def __init__(self, debriefing: Debriefing): def __init__(self, debriefing: Debriefing):
super(QDebriefingWindow, self).__init__() super(QDebriefingWindow, self).__init__()
@ -24,155 +76,27 @@ class QDebriefingWindow(QDialog):
self.setMinimumSize(300, 200) self.setMinimumSize(300, 200)
self.setWindowIcon(QIcon("./resources/icon.png")) self.setWindowIcon(QIcon("./resources/icon.png"))
self.initUI() layout = QVBoxLayout()
self.setLayout(layout)
def initUI(self):
self.layout = QVBoxLayout()
header = QLabel(self) header = QLabel(self)
header.setGeometry(0, 0, 655, 106) header.setGeometry(0, 0, 655, 106)
pixmap = QPixmap("./resources/ui/debriefing.png") pixmap = QPixmap("./resources/ui/debriefing.png")
header.setPixmap(pixmap) header.setPixmap(pixmap)
self.layout.addWidget(header) layout.addWidget(header)
self.layout.addStretch() layout.addStretch()
title = QLabel("<b>Casualty report</b>") title = QLabel("<b>Casualty report</b>")
self.layout.addWidget(title) layout.addWidget(title)
# Player lost units player_lost_units = QGroupBox(f"{self.debriefing.player_country}'s lost units:")
lostUnits = QGroupBox(f"{self.debriefing.player_country}'s lost units:") player_lost_units.setLayout(LossGrid(debriefing, player=True))
lostUnitsLayout = QGridLayout() layout.addWidget(player_lost_units)
lostUnits.setLayout(lostUnitsLayout)
row = 0 enemy_lost_units = QGroupBox(f"{self.debriefing.enemy_country}'s lost units:")
player_air_losses = self.debriefing.air_losses.by_type(player=True) enemy_lost_units.setLayout(LossGrid(debriefing, player=False))
for unit_type, count in player_air_losses.items(): layout.addWidget(enemy_lost_units)
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")
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 = QPushButton("Okay")
okay.clicked.connect(self.close) okay.clicked.connect(self.close)
self.layout.addWidget(okay) layout.addWidget(okay)
self.setLayout(self.layout)

View File

@ -4,6 +4,7 @@ import json
import os import os
import timeit import timeit
from datetime import timedelta from datetime import timedelta
from typing import Sized
from PySide2 import QtCore from PySide2 import QtCore
from PySide2.QtCore import QObject, Qt, Signal from PySide2.QtCore import QObject, Qt, Signal
@ -132,38 +133,48 @@ class QWaitingForMissionResultWindow(QDialog):
self.layout.addLayout(self.gridLayout, 1, 0) self.layout.addLayout(self.gridLayout, 1, 0)
self.setLayout(self.layout) 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: def updateLayout(self, debriefing: Debriefing) -> None:
updateBox = QGroupBox("Mission status") updateBox = QGroupBox("Mission status")
updateLayout = QGridLayout() update_layout = QGridLayout()
updateBox.setLayout(updateLayout) updateBox.setLayout(update_layout)
self.debriefing = debriefing self.debriefing = debriefing
updateLayout.addWidget(QLabel("<b>Aircraft destroyed</b>"), 0, 0) self.add_update_row(
updateLayout.addWidget( "Aircraft destroyed", list(debriefing.air_losses.losses), update_layout
QLabel(str(len(list(debriefing.air_losses.losses)))), 0, 1
) )
self.add_update_row(
updateLayout.addWidget(QLabel("<b>Front line units destroyed</b>"), 1, 0) "Front line units destroyed",
updateLayout.addWidget( list(debriefing.front_line_losses),
QLabel(str(len(list(debriefing.front_line_losses)))), 1, 1 update_layout,
) )
self.add_update_row(
updateLayout.addWidget(QLabel("<b>Convoy units destroyed</b>"), 2, 0) "Convoy units destroyed", list(debriefing.convoy_losses), update_layout
updateLayout.addWidget(QLabel(str(len(list(debriefing.convoy_losses)))), 2, 1) )
self.add_update_row(
updateLayout.addWidget(QLabel("<b>Airlift cargo destroyed</b>"), 3, 0) "Shipping cargo destroyed",
updateLayout.addWidget(QLabel(str(len(list(debriefing.airlift_losses)))), 3, 1) list(debriefing.cargo_ship_losses),
update_layout,
updateLayout.addWidget(QLabel("<b>Other ground units destroyed</b>"), 4, 0) )
updateLayout.addWidget( self.add_update_row(
QLabel(str(len(list(debriefing.ground_object_losses)))), 4, 1 "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 # Clear previous content of the window
for i in reversed(range(self.gridLayout.count())): for i in reversed(range(self.gridLayout.count())):