mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Redo convoy attack flight plans.
The previous flight plan only makes sense if the convoy will make it a significant distance from its starting point. At road speeds over the typical mission duration this is not true, so we can actually plan this as if it was a strike mission near the origin point and that's close enough. There's some cleanup work to do here that I've added todos for. Fixes https://github.com/Khopa/dcs_liberation/issues/996
This commit is contained in:
@@ -72,7 +72,7 @@ from dcs.task import (
|
||||
)
|
||||
from dcs.terrain.terrain import Airport, NoParkingSlotError
|
||||
from dcs.triggers import Event, TriggerOnce, TriggerRule
|
||||
from dcs.unitgroup import FlyingGroup, ShipGroup, StaticGroup
|
||||
from dcs.unitgroup import FlyingGroup, ShipGroup, StaticGroup, VehicleGroup
|
||||
from dcs.unittype import FlyingType, UnitType
|
||||
|
||||
from game import db
|
||||
@@ -88,6 +88,7 @@ from game.theater.controlpoint import (
|
||||
OffMapSpawn,
|
||||
)
|
||||
from game.theater.theatergroundobject import TheaterGroundObject
|
||||
from game.transfers import Convoy, RoadTransferOrder
|
||||
from game.unitmap import UnitMap
|
||||
from game.utils import Distance, meters, nautical_miles
|
||||
from gen.ato import AirTaskingOrder, Package
|
||||
@@ -1691,25 +1692,30 @@ class BaiIngressBuilder(PydcsWaypointBuilder):
|
||||
def build(self) -> MovingPoint:
|
||||
waypoint = super().build()
|
||||
|
||||
# TODO: Add common "UnitGroupTarget" base type.
|
||||
target_group = self.package.target
|
||||
if isinstance(target_group, TheaterGroundObject):
|
||||
tgroup = self.mission.find_group(target_group.group_name)
|
||||
if tgroup is not None:
|
||||
task = AttackGroup(tgroup.id, weapon_type=WeaponType.Auto)
|
||||
task.params["attackQtyLimit"] = False
|
||||
task.params["directionEnabled"] = False
|
||||
task.params["altitudeEnabled"] = False
|
||||
task.params["groupAttack"] = True
|
||||
waypoint.tasks.append(task)
|
||||
else:
|
||||
logging.error(
|
||||
"Could not find group for BAI mission %s", target_group.group_name
|
||||
)
|
||||
group_name = target_group.group_name
|
||||
elif isinstance(target_group, Convoy):
|
||||
group_name = target_group.transfer.name
|
||||
else:
|
||||
logging.error(
|
||||
"Unexpected target type for BAI mission: %s",
|
||||
target_group.__class__.__name__,
|
||||
)
|
||||
return waypoint
|
||||
|
||||
group = self.mission.find_group(group_name)
|
||||
if group is None:
|
||||
logging.error("Could not find group for BAI mission %s", group_name)
|
||||
return waypoint
|
||||
|
||||
task = AttackGroup(group.id, weapon_type=WeaponType.Auto)
|
||||
task.params["attackQtyLimit"] = False
|
||||
task.params["directionEnabled"] = False
|
||||
task.params["altitudeEnabled"] = False
|
||||
task.params["groupAttack"] = True
|
||||
waypoint.tasks.append(task)
|
||||
return waypoint
|
||||
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ from dcs.unittype import VehicleType
|
||||
|
||||
from game.transfers import RoadTransferOrder
|
||||
from game.unitmap import UnitMap
|
||||
from game.utils import kph
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game import Game
|
||||
@@ -26,24 +27,26 @@ class ConvoyGenerator:
|
||||
|
||||
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:
|
||||
def generate_convoy_for(self, transfer: RoadTransferOrder) -> VehicleGroup:
|
||||
next_hop = transfer.path()[0]
|
||||
origin = transfer.position.convoy_spawns[next_hop]
|
||||
destination = next_hop.convoy_spawns[transfer.position]
|
||||
|
||||
group = self._create_mixed_unit_group(
|
||||
f"Convoy {next(self.count)}",
|
||||
transfer.name,
|
||||
origin,
|
||||
transfer.units,
|
||||
transfer.player,
|
||||
)
|
||||
group.add_waypoint(destination, move_formation=PointAction.OnRoad)
|
||||
group.add_waypoint(
|
||||
destination, speed=kph(40).kph, move_formation=PointAction.OnRoad
|
||||
)
|
||||
self.make_drivable(group)
|
||||
self.unit_map.add_convoy_units(group, transfer)
|
||||
return group
|
||||
|
||||
def _create_mixed_unit_group(
|
||||
self,
|
||||
|
||||
@@ -30,8 +30,8 @@ from game.theater import (
|
||||
SamGroundObject,
|
||||
TheaterGroundObject,
|
||||
)
|
||||
from game.theater.supplyroutes import SupplyRouteLink
|
||||
from game.theater.theatergroundobject import EwrGroundObject
|
||||
from game.transfers import Convoy
|
||||
from game.utils import Distance, Speed, feet, meters, nautical_miles
|
||||
from .closestairfields import ObjectiveDistanceCache
|
||||
from .flight import Flight, FlightType, FlightWaypoint, FlightWaypointType
|
||||
@@ -467,25 +467,6 @@ class CasFlightPlan(PatrollingFlightPlan):
|
||||
return self.patrol_end
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ConvoyInterdictionFlightPlan(PatrollingFlightPlan):
|
||||
takeoff: FlightWaypoint
|
||||
land: FlightWaypoint
|
||||
divert: Optional[FlightWaypoint]
|
||||
|
||||
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
||||
yield self.takeoff
|
||||
yield from self.nav_to
|
||||
yield from [
|
||||
self.patrol_start,
|
||||
self.patrol_end,
|
||||
]
|
||||
yield from self.nav_from
|
||||
yield self.land
|
||||
if self.divert is not None:
|
||||
yield self.divert
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TarCapFlightPlan(PatrollingFlightPlan):
|
||||
takeoff: FlightWaypoint
|
||||
@@ -1042,75 +1023,22 @@ class FlightPlanBuilder:
|
||||
"""
|
||||
location = self.package.target
|
||||
|
||||
if isinstance(location, SupplyRouteLink):
|
||||
return self.generate_supply_route_bai(flight, location)
|
||||
|
||||
if not isinstance(location, TheaterGroundObject):
|
||||
raise InvalidObjectiveLocation(flight.flight_type, location)
|
||||
|
||||
targets: List[StrikeTarget] = []
|
||||
for group in location.groups:
|
||||
if group.units:
|
||||
targets.append(StrikeTarget(f"{group.name} at {location.name}", group))
|
||||
if isinstance(location, TheaterGroundObject):
|
||||
for group in location.groups:
|
||||
if group.units:
|
||||
targets.append(
|
||||
StrikeTarget(f"{group.name} at {location.name}", group)
|
||||
)
|
||||
elif isinstance(location, Convoy):
|
||||
targets.append(StrikeTarget(location.name, location))
|
||||
else:
|
||||
raise InvalidObjectiveLocation(flight.flight_type, location)
|
||||
|
||||
return self.strike_flightplan(
|
||||
flight, location, FlightWaypointType.INGRESS_BAI, targets
|
||||
)
|
||||
|
||||
def generate_supply_route_bai(
|
||||
self, flight: Flight, location: SupplyRouteLink
|
||||
) -> ConvoyInterdictionFlightPlan:
|
||||
"""Generates a BAI flight plan for attacking a supply route.
|
||||
|
||||
These flight plans are extremely rough because we do not know where the roads
|
||||
are. For now they're mostly only usable by players. The flight plan includes a
|
||||
start and end patrol point matching the end points of the convoy's route and a
|
||||
30 minute time on station. It is up to the player to find the target.
|
||||
|
||||
Args:
|
||||
flight: The flight to generate the flight plan for.
|
||||
location: The supply route link to attack.
|
||||
"""
|
||||
|
||||
origin = self.package_airfield()
|
||||
a_dist = origin.distance_to(location.control_point_a)
|
||||
b_dist = origin.distance_to(location.control_point_b)
|
||||
if a_dist < b_dist:
|
||||
near = location.control_point_a
|
||||
far = location.control_point_b
|
||||
else:
|
||||
near = location.control_point_b
|
||||
far = location.control_point_a
|
||||
|
||||
patrol_alt = meters(
|
||||
random.randint(
|
||||
int(self.doctrine.min_patrol_altitude.meters),
|
||||
int(self.doctrine.max_patrol_altitude.meters),
|
||||
)
|
||||
)
|
||||
|
||||
builder = WaypointBuilder(flight, self.game, self.is_player)
|
||||
start, end = builder.convoy_search(near, far, patrol_alt)
|
||||
|
||||
return ConvoyInterdictionFlightPlan(
|
||||
self.package,
|
||||
flight,
|
||||
takeoff=builder.takeoff(flight.departure),
|
||||
nav_to=builder.nav_path(
|
||||
flight.departure.position, near.position, patrol_alt
|
||||
),
|
||||
nav_from=builder.nav_path(
|
||||
far.position, flight.arrival.position, patrol_alt
|
||||
),
|
||||
patrol_start=start,
|
||||
patrol_end=end,
|
||||
land=builder.land(flight.arrival),
|
||||
divert=builder.divert(flight.divert),
|
||||
# Not relevant because player only.
|
||||
engagement_distance=meters(0),
|
||||
patrol_duration=timedelta(minutes=30),
|
||||
)
|
||||
|
||||
def generate_anti_ship(self, flight: Flight) -> StrikeFlightPlan:
|
||||
"""Generates an anti-ship flight plan.
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ from dcs.mapping import Point
|
||||
from dcs.unit import Unit
|
||||
from dcs.unitgroup import Group, VehicleGroup
|
||||
|
||||
from game.transfers import Convoy
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game import Game
|
||||
|
||||
@@ -32,7 +34,7 @@ from .flight import Flight, FlightWaypoint, FlightWaypointType
|
||||
@dataclass(frozen=True)
|
||||
class StrikeTarget:
|
||||
name: str
|
||||
target: Union[VehicleGroup, TheaterGroundObject, Unit, Group]
|
||||
target: Union[VehicleGroup, TheaterGroundObject, Unit, Group, Convoy]
|
||||
|
||||
|
||||
class WaypointBuilder:
|
||||
@@ -349,63 +351,6 @@ class WaypointBuilder:
|
||||
self.race_track_end(end, altitude),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def convoy_search_start(
|
||||
control_point: ControlPoint, altitude: Distance
|
||||
) -> FlightWaypoint:
|
||||
"""Creates a convoy search start waypoint.
|
||||
|
||||
Args:
|
||||
control_point: Control point for the beginning of the search.
|
||||
altitude: Altitude of the racetrack.
|
||||
"""
|
||||
waypoint = FlightWaypoint(
|
||||
FlightWaypointType.INGRESS_BAI,
|
||||
control_point.position.x,
|
||||
control_point.position.y,
|
||||
altitude,
|
||||
)
|
||||
waypoint.name = control_point.name
|
||||
waypoint.description = "Beginning of convoy search area"
|
||||
waypoint.pretty_name = "Search start"
|
||||
return waypoint
|
||||
|
||||
@staticmethod
|
||||
def convoy_search_end(
|
||||
control_point: ControlPoint, altitude: Distance
|
||||
) -> FlightWaypoint:
|
||||
"""Creates a convoy search start waypoint.
|
||||
|
||||
Args:
|
||||
control_point: Control point for the beginning of the search.
|
||||
altitude: Altitude of the racetrack.
|
||||
"""
|
||||
waypoint = FlightWaypoint(
|
||||
FlightWaypointType.EGRESS,
|
||||
control_point.position.x,
|
||||
control_point.position.y,
|
||||
altitude,
|
||||
)
|
||||
waypoint.name = control_point.name
|
||||
waypoint.description = "End of convoy search area"
|
||||
waypoint.pretty_name = "Search end"
|
||||
return waypoint
|
||||
|
||||
def convoy_search(
|
||||
self, start: ControlPoint, end: ControlPoint, altitude: Distance
|
||||
) -> Tuple[FlightWaypoint, FlightWaypoint]:
|
||||
"""Creates two waypoint for a convoy search path.
|
||||
|
||||
Args:
|
||||
start: The beginning convoy search waypoint.
|
||||
end: The ending convoy search waypoint.
|
||||
altitude: The convoy search altitude.
|
||||
"""
|
||||
return (
|
||||
self.convoy_search_start(start, altitude),
|
||||
self.convoy_search_end(end, altitude),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def orbit(start: Point, altitude: Distance) -> FlightWaypoint:
|
||||
"""Creates an circular orbit point.
|
||||
@@ -463,7 +408,7 @@ class WaypointBuilder:
|
||||
end: The end of the sweep.
|
||||
altitude: The sweep altitude.
|
||||
"""
|
||||
return (self.sweep_start(start, altitude), self.sweep_end(end, altitude))
|
||||
return self.sweep_start(start, altitude), self.sweep_end(end, altitude)
|
||||
|
||||
def escort(
|
||||
self, ingress: Point, target: MissionTarget, egress: Point
|
||||
|
||||
@@ -250,6 +250,7 @@ class NameGenerator:
|
||||
number = 0
|
||||
infantry_number = 0
|
||||
aircraft_number = 0
|
||||
convoy_number = 0
|
||||
|
||||
ANIMALS = ANIMALS
|
||||
existing_alphas: List[str] = []
|
||||
@@ -258,6 +259,7 @@ class NameGenerator:
|
||||
def reset(cls):
|
||||
cls.number = 0
|
||||
cls.infantry_number = 0
|
||||
cls.convoy_number = 0
|
||||
cls.ANIMALS = ANIMALS
|
||||
cls.existing_alphas = []
|
||||
|
||||
@@ -266,6 +268,7 @@ class NameGenerator:
|
||||
cls.number = 0
|
||||
cls.infantry_number = 0
|
||||
cls.aircraft_number = 0
|
||||
cls.convoy_number = 0
|
||||
|
||||
@classmethod
|
||||
def next_aircraft_name(cls, country: Country, parent_base_id: int, flight: Flight):
|
||||
@@ -327,6 +330,11 @@ class NameGenerator:
|
||||
cls.number += 1
|
||||
return "carrier|{}|{}|0|".format(country.id, cls.number)
|
||||
|
||||
@classmethod
|
||||
def next_convoy_name(cls) -> str:
|
||||
cls.convoy_number += 1
|
||||
return f"Convoy {cls.convoy_number:04}"
|
||||
|
||||
@classmethod
|
||||
def random_objective_name(cls):
|
||||
if len(cls.ANIMALS) == 0:
|
||||
|
||||
Reference in New Issue
Block a user