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

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