Add shipping lane support to campaign files.

These don't actually do anything yet, this is just the campaign support
and UI.

https://github.com/Khopa/dcs_liberation/issues/826
This commit is contained in:
Dan Albert
2021-04-24 21:32:37 -07:00
parent 97b73e1a01
commit 5e67ce0ab2
6 changed files with 173 additions and 40 deletions

View File

@@ -5,12 +5,8 @@ import json
import logging
from dataclasses import dataclass
from functools import cached_property
from itertools import tee
from pathlib import Path
from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Union, cast
from shapely import geometry
from shapely import ops
from typing import Any, Dict, Iterator, List, Optional, Tuple, Union, cast
from dcs import Mission
from dcs.countries import (
@@ -21,9 +17,10 @@ from dcs.country import Country
from dcs.mapping import Point
from dcs.planes import F_15C
from dcs.ships import (
Bulker_Handy_Wind,
CVN_74_John_C__Stennis,
LHA_1_Tarawa,
DDG_Arleigh_Burke_IIa,
LHA_1_Tarawa,
)
from dcs.statics import Fortification
from dcs.terrain import (
@@ -43,20 +40,21 @@ from dcs.unitgroup import (
VehicleGroup,
)
from dcs.vehicles import AirDefence, Armor, MissilesSS, Unarmed
from shapely import geometry, ops
from gen.flights.flight import FlightType
from .controlpoint import (
Airfield,
Carrier,
ControlPoint,
Fob,
Lha,
MissionTarget,
OffMapSpawn,
Fob,
)
from .landmap import Landmap, load_landmap, poly_contains
from ..point_with_heading import PointWithHeading
from ..utils import Distance, meters, nautical_miles
from ..utils import Distance, meters, nautical_miles, pairwise
Numeric = Union[int, float]
@@ -73,16 +71,6 @@ IMPORTANCE_HIGH = 1.4
FRONTLINE_MIN_CP_DISTANCE = 5000
def pairwise(iterable):
"""
itertools recipe
s -> (s0,s1), (s1,s2), (s2, s3), ...
"""
a, b = tee(iterable)
next(b, None)
return zip(a, b)
class MizCampaignLoader:
BLUE_COUNTRY = CombinedJointTaskForcesBlue()
RED_COUNTRY = CombinedJointTaskForcesRed()
@@ -92,6 +80,7 @@ class MizCampaignLoader:
CV_UNIT_TYPE = CVN_74_John_C__Stennis.id
LHA_UNIT_TYPE = LHA_1_Tarawa.id
FRONT_LINE_UNIT_TYPE = Armor.APC_M113.id
SHIPPING_LANE_UNIT_TYPE = Bulker_Handy_Wind.id
FOB_UNIT_TYPE = Unarmed.Truck_SKP_11_Mobile_ATC.id
FARP_HELIPAD = "SINGLE_HELIPAD"
@@ -315,6 +304,12 @@ class MizCampaignLoader:
if group.units[0].type == self.FRONT_LINE_UNIT_TYPE:
yield group
@property
def shipping_lane_groups(self) -> Iterator[ShipGroup]:
for group in self.country(blue=True).ship_group:
if group.units[0].type == self.SHIPPING_LANE_UNIT_TYPE:
yield group
@cached_property
def front_lines(self) -> Dict[str, ComplexFrontLine]:
# Dict of front line ID to a front line.
@@ -351,6 +346,28 @@ class MizCampaignLoader:
)
return front_lines
def add_shipping_lanes(self) -> None:
for group in self.shipping_lane_groups:
# The unit will have its first waypoint at the source CP and the final
# waypoint at the destination CP. Each waypoint defines the path of the
# cargo ship.
waypoints = [p.position for p in group.points]
origin = self.theater.closest_control_point(waypoints[0])
if origin is None:
raise RuntimeError(
f"No control point near the first waypoint of {group.name}"
)
destination = self.theater.closest_control_point(waypoints[-1])
if destination is None:
raise RuntimeError(
f"No control point near the final waypoint of {group.name}"
)
self.control_points[origin.id].create_shipping_lane(destination, waypoints)
self.control_points[destination.id].create_shipping_lane(
origin, list(reversed(waypoints))
)
def objective_info(self, group: Group) -> Tuple[ControlPoint, Distance]:
closest = self.theater.closest_control_point(group.position)
distance = meters(closest.position.distance_to_point(group.position))
@@ -446,6 +463,7 @@ class MizCampaignLoader:
for control_point in self.control_points.values():
self.theater.add_controlpoint(control_point)
self.add_preset_locations()
self.add_shipping_lanes()
self.theater.set_frontline_data(self.front_lines)
@@ -877,6 +895,12 @@ class FrontLine(MissionTarget):
"""
return self.point_from_a(self._position_distance)
@property
def points(self) -> Iterator[Point]:
yield self.segments[0].point_a
for segment in self.segments:
yield segment.point_b
@property
def control_points(self) -> Tuple[ControlPoint, ControlPoint]:
"""Returns a tuple of the two control points."""

View File

@@ -267,6 +267,7 @@ class ControlPoint(MissionTarget, ABC):
# TODO: Should be Airbase specific.
self.has_frontline = has_frontline
self.connected_points: List[ControlPoint] = []
self.shipping_lanes: Dict[ControlPoint, List[Point]] = {}
self.convoy_spawns: Dict[ControlPoint, Point] = {}
self.base: Base = Base()
self.cptype = cptype
@@ -397,6 +398,9 @@ class ControlPoint(MissionTarget, ABC):
self.convoy_spawns[to] = convoy_location
self.stances[to.id] = CombatStance.DEFENSIVE
def create_shipping_lane(self, to: ControlPoint, waypoints: List[Point]) -> None:
self.shipping_lanes[to] = waypoints
@abstractmethod
def runway_is_operational(self) -> bool:
"""

View File

@@ -1,5 +1,6 @@
from __future__ import annotations
import itertools
import math
from dataclasses import dataclass
from typing import Union
@@ -178,3 +179,13 @@ def mach(value: float, altitude: Distance) -> Speed:
SPEED_OF_SOUND_AT_SEA_LEVEL = knots(661.5)
def pairwise(iterable):
"""
itertools recipe
s -> (s0,s1), (s1,s2), (s2, s3), ...
"""
a, b = itertools.tee(iterable)
next(b, None)
return zip(a, b)

View File

@@ -39,4 +39,9 @@ VERSION = _build_version_string()
#: * Factories (Workshop_A) define factory objectives. Only control points with
#: factories will be able to recruit ground units, so they should exist in sufficient
#: number and be protected by IADS.
CAMPAIGN_FORMAT_VERSION = 2
#:
#: Version 3
#: * Bulker Handy Winds define shipping lanes. They should be placed in port areas that
#: are navigable by ships and have a route to another port area. DCS ships *will not*
#: avoid driving into islands, so ensure that their waypoints plot a navigable route.
CAMPAIGN_FORMAT_VERSION = 3