Spawn convoys for transfers.

Destroying these units currently has no effect.

https://github.com/Khopa/dcs_liberation/issues/824
This commit is contained in:
Dan Albert 2021-04-17 20:04:48 -07:00
parent bd9cbf5e3b
commit 5dd7ea3060
4 changed files with 134 additions and 1 deletions

View File

@ -22,6 +22,7 @@ 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.environmentgen import EnvironmentGenerator
from gen.forcedoptionsgen import ForcedOptionsGenerator
from gen.groundobjectsgen import GroundObjectsGenerator
@ -314,6 +315,7 @@ class Operation:
cls.airgen.flights, cls.airsupportgen.air_support
)
cls._generate_ground_conflicts()
cls._generate_convoys()
# Triggers
triggersgen = TriggersGenerator(cls.current_mission, cls.game)
@ -428,6 +430,11 @@ class Operation:
ground_conflict_gen.generate()
cls.jtacs.extend(ground_conflict_gen.jtacs)
@classmethod
def _generate_convoys(cls) -> None:
"""Generates convoys for unit transfers by road."""
ConvoyGenerator(cls.current_mission, cls.game, cls.unit_map).generate()
@classmethod
def reset_naming_ids(cls):
namegen.reset_numbers()

View File

@ -1,6 +1,6 @@
import logging
from dataclasses import dataclass, field
from typing import Dict, List, Type
from typing import Dict, Iterator, List, Type
from dcs.unittype import VehicleType
from game.theater import ControlPoint
@ -49,6 +49,9 @@ class PendingTransfers:
def __init__(self) -> None:
self.pending_transfers: List[RoadTransferOrder] = []
def __iter__(self) -> Iterator[RoadTransferOrder]:
yield from self.pending_transfers
@property
def pending_transfer_count(self) -> int:
return len(self.pending_transfers)

View File

@ -9,6 +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 RoadTransferOrder
from gen.flights.flight import Flight
@ -25,6 +26,12 @@ class GroundObjectUnit:
unit: Unit
@dataclass(frozen=True)
class ConvoyUnit:
unit_type: Type[VehicleType]
transfer: RoadTransferOrder
@dataclass(frozen=True)
class Building:
ground_object: BuildingGroundObject
@ -37,6 +44,7 @@ class UnitMap:
self.front_line_units: Dict[str, FrontLineUnit] = {}
self.ground_object_units: Dict[str, GroundObjectUnit] = {}
self.buildings: Dict[str, Building] = {}
self.convoys: Dict[str, ConvoyUnit] = {}
def add_aircraft(self, group: FlyingGroup, flight: Flight) -> None:
for unit in group.units:
@ -113,6 +121,25 @@ 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, transfer: RoadTransferOrder) -> None:
for unit in group.units:
# The actual name is a String (the pydcs translatable string), which
# doesn't define __eq__.
name = str(unit.name)
if name in self.convoys:
raise RuntimeError(f"Duplicate convoy unit: {name}")
unit_type = db.unit_type_from_name(unit.type)
if unit_type is None:
raise RuntimeError(f"Unknown unit type: {unit.type}")
if not issubclass(unit_type, VehicleType):
raise RuntimeError(
f"{name} is a {unit_type.__name__}, expected a VehicleType"
)
self.convoys[name] = ConvoyUnit(unit_type, transfer)
def convoy_unit(self, name: str) -> Optional[ConvoyUnit]:
return self.convoys.get(name, None)
def add_building(self, ground_object: BuildingGroundObject, group: Group) -> None:
# The actual name is a String (the pydcs translatable string), which
# doesn't define __eq__.

96
gen/convoys.py Normal file
View File

@ -0,0 +1,96 @@
from __future__ import annotations
import itertools
from typing import Dict, TYPE_CHECKING, Type
from dcs import Mission
from dcs.mapping import Point
from dcs.point import PointAction
from dcs.unit import Vehicle
from dcs.unitgroup import VehicleGroup
from dcs.unittype import VehicleType
from game.transfers import RoadTransferOrder
from game.unitmap import UnitMap
if TYPE_CHECKING:
from game import Game
class ConvoyGenerator:
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.
self.count = itertools.count()
for transfer in self.game.transfers:
self.generate_convoy_for(transfer)
def generate_convoy_for(self, transfer: RoadTransferOrder) -> None:
# TODO: Add convoy spawn points to campaign so these can start on/near a road.
# Groups that start with an on-road waypoint that are not on a road will move to
# the road one at a time. Spawning them arbitrarily at the control point spawns
# them on the runway (or in a FOB structure) and they'll take forever to get to
# a road.
origin = transfer.position.position
next_hop = transfer.path()[0]
destination = next_hop.position
group = self._create_mixed_unit_group(
f"Convoy {next(self.count)}",
origin,
transfer.units,
transfer.player,
)
group.add_waypoint(destination, move_formation=PointAction.OnRoad)
self.make_drivable(group)
self.unit_map.add_convoy_units(group, transfer)
def _create_mixed_unit_group(
self,
name: str,
position: Point,
units: Dict[Type[VehicleType], int],
for_player: bool,
) -> VehicleGroup:
country = self.mission.country(
self.game.player_country if for_player else self.game.enemy_country
)
unit_types = list(units.items())
main_unit_type, main_unit_count = unit_types[0]
group = self.mission.vehicle_group(
country,
name,
main_unit_type,
position=position,
group_size=main_unit_count,
move_formation=PointAction.OnRoad,
)
unit_name_counter = itertools.count(main_unit_count + 1)
# pydcs spreads units out by 20 in the Y axis by default. Pick up where it left
# off.
y = itertools.count(position.y + main_unit_count * 20, 20)
for unit_type, count in unit_types[1:]:
for i in range(count):
v = self.mission.vehicle(
f"{name} Unit #{next(unit_name_counter)}", unit_type
)
v.position.x = position.x
v.position.y = next(y)
v.heading = 0
group.add_unit(v)
return group
@staticmethod
def make_drivable(group: VehicleGroup) -> None:
for v in group.units:
if isinstance(v, Vehicle):
v.player_can_drive = True