Load two units per cargo plane.

Fixes https://github.com/dcs-liberation/dcs_liberation/issues/1029
This commit is contained in:
Dan Albert 2021-06-05 12:21:34 -07:00
parent cb159e3341
commit 96cdea2a94
6 changed files with 56 additions and 40 deletions

View File

@ -24,7 +24,7 @@ from game import db
from game.theater import Airfield, ControlPoint from game.theater import Airfield, ControlPoint
from game.transfers import CargoShip from game.transfers import CargoShip
from game.unitmap import ( from game.unitmap import (
AirliftUnit, AirliftUnits,
Building, Building,
ConvoyUnit, ConvoyUnit,
FrontLineUnit, FrontLineUnit,
@ -75,8 +75,8 @@ class GroundLosses:
player_cargo_ships: List[CargoShip] = field(default_factory=list) player_cargo_ships: List[CargoShip] = field(default_factory=list)
enemy_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[AirliftUnits] = field(default_factory=list)
enemy_airlifts: List[AirliftUnit] = field(default_factory=list) enemy_airlifts: List[AirliftUnits] = field(default_factory=list)
player_ground_objects: List[GroundObjectUnit] = field(default_factory=list) player_ground_objects: List[GroundObjectUnit] = field(default_factory=list)
enemy_ground_objects: List[GroundObjectUnit] = field(default_factory=list) enemy_ground_objects: List[GroundObjectUnit] = field(default_factory=list)
@ -160,7 +160,7 @@ class Debriefing:
yield from self.ground_losses.enemy_cargo_ships yield from self.ground_losses.enemy_cargo_ships
@property @property
def airlift_losses(self) -> Iterator[AirliftUnit]: def airlift_losses(self) -> Iterator[AirliftUnits]:
yield from self.ground_losses.player_airlifts yield from self.ground_losses.player_airlifts
yield from self.ground_losses.enemy_airlifts yield from self.ground_losses.enemy_airlifts
@ -220,7 +220,8 @@ class Debriefing:
else: else:
losses = self.ground_losses.enemy_airlifts losses = self.ground_losses.enemy_airlifts
for loss in losses: for loss in losses:
losses_by_type[loss.unit_type] += 1 for unit_type in loss.cargo:
losses_by_type[unit_type] += 1
return losses_by_type return losses_by_type
def building_losses_by_type(self, player: bool) -> Dict[str, int]: def building_losses_by_type(self, player: bool) -> Dict[str, int]:

View File

@ -202,19 +202,17 @@ class Event:
@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:
unit_type = loss.unit_type
transfer = loss.transfer transfer = loss.transfer
available = loss.transfer.units.get(unit_type, 0)
airlift_name = f"airlift from {transfer.origin} to {transfer.destination}" airlift_name = f"airlift from {transfer.origin} to {transfer.destination}"
if available <= 0: for unit_type in loss.cargo:
logging.error( try:
f"Found killed {unit_type} in {airlift_name} but that airlift has "
"none available."
)
continue
logging.info(f"{unit_type} destroyed in {airlift_name}")
transfer.kill_unit(unit_type) transfer.kill_unit(unit_type)
logging.info(f"{unit_type} destroyed in {airlift_name}")
except KeyError:
logging.exception(
f"Found killed {unit_type} in {airlift_name} but that airlift "
"has none available."
)
@staticmethod @staticmethod
def commit_ground_object_losses(debriefing: Debriefing) -> None: def commit_ground_object_losses(debriefing: Debriefing) -> None:

View File

@ -1,6 +1,7 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
import math
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
@ -89,10 +90,9 @@ class TransferOrder:
self.units.clear() self.units.clear()
def kill_unit(self, unit_type: Type[VehicleType]) -> None: def kill_unit(self, unit_type: Type[VehicleType]) -> None:
if unit_type in self.units: if unit_type not in self.units or not self.units[unit_type]:
raise KeyError(f"{self.destination} has no {unit_type} remaining")
self.units[unit_type] -= 1 self.units[unit_type] -= 1
return
raise KeyError
@property @property
def size(self) -> int: def size(self) -> int:
@ -254,11 +254,13 @@ class AirliftPlanner:
self, squadron: Squadron, inventory: ControlPointAircraftInventory self, squadron: Squadron, inventory: ControlPointAircraftInventory
) -> int: ) -> int:
available = inventory.available(squadron.aircraft) available = inventory.available(squadron.aircraft)
# 4 is the max flight size in DCS. capacity_each = 1 if squadron.aircraft.helicopter else 2
flight_size = min(self.transfer.size, available, 4) required = math.ceil(self.transfer.size / capacity_each)
flight_size = min(required, available, squadron.aircraft.group_size_max)
capacity = flight_size * capacity_each
if flight_size < self.transfer.size: if capacity < self.transfer.size:
transfer = self.game.transfers.split_transfer(self.transfer, flight_size) transfer = self.game.transfers.split_transfer(self.transfer, capacity)
else: else:
transfer = self.transfer transfer = self.transfer

View File

@ -1,4 +1,6 @@
"""Maps generated units back to their Liberation types.""" """Maps generated units back to their Liberation types."""
import itertools
import math
from dataclasses import dataclass from dataclasses import dataclass
from typing import Dict, Optional, Type from typing import Dict, Optional, Type
@ -40,8 +42,8 @@ class ConvoyUnit:
@dataclass(frozen=True) @dataclass(frozen=True)
class AirliftUnit: class AirliftUnits:
unit_type: Type[VehicleType] cargo: tuple[Type[VehicleType], ...]
transfer: TransferOrder transfer: TransferOrder
@ -59,7 +61,7 @@ class UnitMap:
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.cargo_ships: Dict[str, CargoShip] = {}
self.airlifts: Dict[str, AirliftUnit] = {} self.airlifts: Dict[str, AirliftUnits] = {}
def add_aircraft(self, group: FlyingGroup, flight: Flight) -> None: def add_aircraft(self, group: FlyingGroup, flight: Flight) -> None:
for pilot, unit in zip(flight.roster.pilots, group.units): for pilot, unit in zip(flight.roster.pilots, group.units):
@ -177,15 +179,26 @@ class UnitMap:
return self.cargo_ships.get(name, None) 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()): capacity_each = math.ceil(transfer.size / len(group.units))
for idx, transport in enumerate(group.units):
# Slice the units in groups based on the capacity of each unit. Cargo is
# assigned arbitrarily to units in the order of the group. The last unit in
# the group will receive a partial load if there is not enough cargo to fill
# every transport.
base_idx = idx * capacity_each
cargo = tuple(
itertools.islice(
transfer.iter_units(), base_idx, base_idx + capacity_each
)
)
# 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__.
name = str(transport.name) name = str(transport.name)
if name in self.airlifts: if name in self.airlifts:
raise RuntimeError(f"Duplicate airlift unit: {name}") raise RuntimeError(f"Duplicate airlift unit: {name}")
self.airlifts[name] = AirliftUnit(cargo_type, transfer) self.airlifts[name] = AirliftUnits(cargo, transfer)
def airlift_unit(self, name: str) -> Optional[AirliftUnit]: def airlift_unit(self, name: str) -> Optional[AirliftUnits]:
return self.airlifts.get(name, None) return self.airlifts.get(name, None)
def add_building(self, ground_object: BuildingGroundObject, group: Group) -> None: def add_building(self, ground_object: BuildingGroundObject, group: Group) -> None:

View File

@ -133,10 +133,10 @@ class QWaitingForMissionResultWindow(QDialog):
self.setLayout(self.layout) self.setLayout(self.layout)
@staticmethod @staticmethod
def add_update_row(description: str, count: Sized, layout: QGridLayout) -> None: def add_update_row(description: str, count: int, layout: QGridLayout) -> None:
row = layout.rowCount() row = layout.rowCount()
layout.addWidget(QLabel(f"<b>{description}</b>"), row, 0) layout.addWidget(QLabel(f"<b>{description}</b>"), row, 0)
layout.addWidget(QLabel(f"{len(count)}"), row, 1) layout.addWidget(QLabel(f"{count}"), row, 1)
def updateLayout(self, debriefing: Debriefing) -> None: def updateLayout(self, debriefing: Debriefing) -> None:
updateBox = QGroupBox("Mission status") updateBox = QGroupBox("Mission status")
@ -145,34 +145,36 @@ class QWaitingForMissionResultWindow(QDialog):
self.debriefing = debriefing self.debriefing = debriefing
self.add_update_row( self.add_update_row(
"Aircraft destroyed", list(debriefing.air_losses.losses), update_layout "Aircraft destroyed", len(list(debriefing.air_losses.losses)), update_layout
) )
self.add_update_row( self.add_update_row(
"Front line units destroyed", "Front line units destroyed",
list(debriefing.front_line_losses), len(list(debriefing.front_line_losses)),
update_layout, update_layout,
) )
self.add_update_row( self.add_update_row(
"Convoy units destroyed", list(debriefing.convoy_losses), update_layout "Convoy units destroyed", len(list(debriefing.convoy_losses)), update_layout
) )
self.add_update_row( self.add_update_row(
"Shipping cargo destroyed", "Shipping cargo destroyed",
list(debriefing.cargo_ship_losses), len(list(debriefing.cargo_ship_losses)),
update_layout, update_layout,
) )
self.add_update_row( self.add_update_row(
"Airlift cargo destroyed", list(debriefing.airlift_losses), update_layout "Airlift cargo destroyed",
sum(len(loss.cargo) for loss in debriefing.airlift_losses),
update_layout,
) )
self.add_update_row( self.add_update_row(
"Ground units lost at objective areas", "Ground units lost at objective areas",
list(debriefing.ground_object_losses), len(list(debriefing.ground_object_losses)),
update_layout, update_layout,
) )
self.add_update_row( self.add_update_row(
"Buildings destroyed", list(debriefing.building_losses), update_layout "Buildings destroyed", len(list(debriefing.building_losses)), update_layout
) )
self.add_update_row( self.add_update_row(
"Base capture events", debriefing.base_captures, update_layout "Base capture events", len(debriefing.base_captures), update_layout
) )
# Clear previous content of the window # Clear previous content of the window