mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Support for convoy "spawn-route" in campaign design
Resolves #91 'Assault On Damascus' was revamped using this new technique
This commit is contained in:
parent
057ea58d8e
commit
45b8757593
@ -25,6 +25,7 @@
|
||||
* **[Squadrons]** Warning messages when opening up a squadron through the air wing dialog, indicating squadrons that potentially won't fit w.r.t. parking space.
|
||||
* **[Squadrons Transfers]** Determine number of available parking slots more accurately w.r.t. squadron transfers, taking aircraft dimensions into account which should prevent forced air-starts.
|
||||
* **[UX]** Allow usage of CTRL/SHIFT modifiers in ground unit transfer window.
|
||||
* **[Campaign Design]** Ability to define "spawn-routes" for convoys, allowing them to start from the road without having to edit the mission
|
||||
|
||||
## Fixes
|
||||
* **[New Game Wizard]** Settings would not persist when going back to a previous page (obsolete due to overhaul).
|
||||
|
||||
@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
import itertools
|
||||
from functools import cached_property
|
||||
from pathlib import Path
|
||||
from typing import Iterator, List, TYPE_CHECKING, Tuple
|
||||
from typing import Iterator, List, TYPE_CHECKING, Tuple, Optional
|
||||
from uuid import UUID
|
||||
|
||||
from dcs import Mission
|
||||
@ -28,7 +28,7 @@ from game.theater.controlpoint import (
|
||||
OffMapSpawn,
|
||||
)
|
||||
from game.theater.presetlocation import PresetLocation
|
||||
from game.utils import Distance, meters
|
||||
from game.utils import Distance, meters, feet
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game.theater.conflicttheater import ConflictTheater
|
||||
@ -45,6 +45,7 @@ class MizCampaignLoader:
|
||||
LHA_UNIT_TYPE = LHA_Tarawa.id
|
||||
FRONT_LINE_UNIT_TYPE = Armor.M_113.id
|
||||
SHIPPING_LANE_UNIT_TYPE = HandyWind.id
|
||||
CP_CONVOY_SPAWN_TYPE = Armor.M1043_HMMWV_Armament.id
|
||||
|
||||
FOB_UNIT_TYPE = Unarmed.SKP_11.id
|
||||
FARP_HELIPADS_TYPE = ["Invisible FARP", "SINGLE_HELIPAD"]
|
||||
@ -317,6 +318,50 @@ class MizCampaignLoader:
|
||||
if group.units[0].type in self.POWER_SOURCE_UNIT_TYPE:
|
||||
yield group
|
||||
|
||||
@property
|
||||
def cp_convoy_spawns(self) -> Iterator[VehicleGroup]:
|
||||
for group in self.country(blue=True).vehicle_group:
|
||||
if group.units[0].type == self.CP_CONVOY_SPAWN_TYPE:
|
||||
yield group
|
||||
|
||||
def _construct_cp_spawnpoints(self, start: Point) -> Tuple[Point, ...]:
|
||||
closest = self._find_closest_cp_spawn(start)
|
||||
if closest:
|
||||
return self._interpolate_points(closest, start)
|
||||
return tuple()
|
||||
|
||||
@staticmethod
|
||||
def _interpolate_points(closest: VehicleGroup, start: Point) -> Tuple[Point, ...]:
|
||||
points = [start]
|
||||
waypoints = points + [wpt.position for wpt in closest.points]
|
||||
last = waypoints[0]
|
||||
meters100ft = feet(100).meters
|
||||
residual = 0.0
|
||||
for wpt in waypoints[1:]:
|
||||
dist = wpt.distance_to_point(last)
|
||||
fraction = meters100ft / dist # 100ft separation
|
||||
interpol_count = int((dist + residual) / meters100ft - 1)
|
||||
offset = (meters100ft - residual) / dist
|
||||
if offset <= 1:
|
||||
points.append(last.lerp(wpt, offset))
|
||||
if offset + fraction <= 1:
|
||||
for i in range(1, interpol_count + 1):
|
||||
points.append(last.lerp(wpt, i * fraction + offset))
|
||||
residual = (residual + dist - meters100ft * interpol_count) % meters100ft
|
||||
last = wpt
|
||||
return tuple(points)
|
||||
|
||||
def _find_closest_cp_spawn(self, start: Point) -> Optional[VehicleGroup]:
|
||||
closest: Optional[VehicleGroup] = None
|
||||
for spawn in self.cp_convoy_spawns:
|
||||
if closest is None:
|
||||
closest = spawn
|
||||
continue
|
||||
dist = start.distance_to_point(closest.position)
|
||||
if start.distance_to_point(spawn.position) < dist:
|
||||
closest = spawn
|
||||
return closest
|
||||
|
||||
def add_supply_routes(self) -> None:
|
||||
for group in self.front_line_path_groups:
|
||||
# The unit will have its first waypoint at the source CP and the final
|
||||
@ -334,9 +379,14 @@ class MizCampaignLoader:
|
||||
f"No control point near the final waypoint of {group.name}"
|
||||
)
|
||||
|
||||
self.control_points[origin.id].create_convoy_route(destination, waypoints)
|
||||
o_spawns = self._construct_cp_spawnpoints(waypoints[0])
|
||||
d_spawns = self._construct_cp_spawnpoints(waypoints[-1])
|
||||
|
||||
self.control_points[origin.id].create_convoy_route(
|
||||
destination, waypoints, o_spawns
|
||||
)
|
||||
self.control_points[destination.id].create_convoy_route(
|
||||
origin, list(reversed(waypoints))
|
||||
origin, list(reversed(waypoints)), d_spawns
|
||||
)
|
||||
|
||||
def add_shipping_lanes(self) -> None:
|
||||
|
||||
@ -75,6 +75,7 @@ class Migrator:
|
||||
try_set_attr(cp, "icls_channel")
|
||||
try_set_attr(cp, "icls_name")
|
||||
try_set_attr(cp, "link4")
|
||||
try_set_attr(cp, "convoy_spawns", {})
|
||||
|
||||
def _update_flights(self) -> None:
|
||||
for f in self.game.db.flights.objects.values():
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import itertools
|
||||
from typing import TYPE_CHECKING
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, List
|
||||
|
||||
from dcs import Mission
|
||||
from dcs.mapping import Point
|
||||
@ -40,23 +41,46 @@ class ConvoyGenerator:
|
||||
convoy.units,
|
||||
convoy.player_owned,
|
||||
)
|
||||
group.manualHeading = True
|
||||
|
||||
spawns_tuple = convoy.origin.convoy_spawns.get(convoy.destination)
|
||||
|
||||
if spawns_tuple:
|
||||
spawns = list(spawns_tuple)
|
||||
last_unit = group.units[0]
|
||||
spawns.pop(0)
|
||||
for u in group.units[1:]:
|
||||
if spawns:
|
||||
u.position = spawns.pop(0)
|
||||
u.heading = u.position.heading_between_point(last_unit.position)
|
||||
if last_unit.heading == 0:
|
||||
last_unit.heading = u.heading
|
||||
last_unit = u
|
||||
else:
|
||||
logging.warning(
|
||||
f"Insufficient convoy spawns at {convoy.origin.name} "
|
||||
f"with destination {convoy.destination.name}, "
|
||||
"convoy may experience issues at mission start"
|
||||
)
|
||||
break
|
||||
|
||||
wpts: List[Point] = []
|
||||
route = convoy.origin.convoy_route_to(convoy.destination)
|
||||
if self.game.settings.convoys_travel_full_distance:
|
||||
end_point = convoy.route_end
|
||||
wpts.extend(route)
|
||||
else:
|
||||
# convoys_travel_full_distance is disabled, so have the convoy only move the
|
||||
# first segment on the route. This option aims to remove long routes for
|
||||
# ground vehicles between control points, since the CPU load for pathfinding
|
||||
# long routes on DCS can be pretty heavy.
|
||||
route = convoy.origin.convoy_route_to(convoy.destination)
|
||||
|
||||
# Select the first route segment from the origin towards the destination so
|
||||
# the convoy spawns at the origin CP. This allows the convoy to be targeted
|
||||
# by BAI flights and starts it within the protection umbrella of the CP.
|
||||
end_point = route[1]
|
||||
wpts.append(route[1])
|
||||
|
||||
for wpt in wpts:
|
||||
group.add_waypoint(
|
||||
end_point,
|
||||
wpt,
|
||||
speed=kph(40).kph,
|
||||
move_formation=PointAction.OnRoad,
|
||||
)
|
||||
|
||||
@ -347,6 +347,7 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
|
||||
# TODO: Should be Airbase specific.
|
||||
self.connected_points: List[ControlPoint] = []
|
||||
self.convoy_routes: Dict[ControlPoint, Tuple[Point, ...]] = {}
|
||||
self.convoy_spawns: Dict[ControlPoint, Tuple[Point, ...]] = {}
|
||||
self.shipping_lanes: Dict[ControlPoint, Tuple[Point, ...]] = {}
|
||||
self.base: Base = Base()
|
||||
self.cptype = cptype
|
||||
@ -604,10 +605,13 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
|
||||
def convoy_route_to(self, destination: ControlPoint) -> Sequence[Point]:
|
||||
return self.convoy_routes[destination]
|
||||
|
||||
def create_convoy_route(self, to: ControlPoint, waypoints: Iterable[Point]) -> None:
|
||||
def create_convoy_route(
|
||||
self, to: ControlPoint, waypoints: Iterable[Point], spawns: Iterable[Point]
|
||||
) -> None:
|
||||
self.connected_points.append(to)
|
||||
self.stances[to.id] = CombatStance.DEFENSIVE
|
||||
self.convoy_routes[to] = tuple(waypoints)
|
||||
self.convoy_spawns[to] = tuple(spawns)
|
||||
|
||||
def create_shipping_lane(
|
||||
self, to: ControlPoint, waypoints: Iterable[Point]
|
||||
|
||||
@ -182,5 +182,7 @@ VERSION = _build_version_string()
|
||||
#:
|
||||
#: Version 10.7
|
||||
#: * Support for defining squadron sizes.
|
||||
#: * Definition of "spawn-routes" allowing convoys to spawn on the road
|
||||
#: please note that an insufficiently long route can cause trouble in case of large convoys
|
||||
|
||||
CAMPAIGN_FORMAT_VERSION = (10, 7)
|
||||
|
||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user