mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Airlift support.
UI isn't finished. Bulk transfers where the player doesn't care what aircraft get used work (though they're chosen with no thought at all), but being able to plan your own airlift flight isn't here yet. Cargo planes are not implemented yet. No way to view the cargo of a flight (will come with the cargo flight planning UI). The airlift flight/package creation should probably be moved out of the UI and into the game code. AI doesn't use these yet. https://github.com/Khopa/dcs_liberation/issues/825
This commit is contained in:
@@ -8,6 +8,8 @@ example, the package to strike an enemy airfield may contain an escort flight,
|
||||
a SEAD flight, and the strike aircraft themselves. CAP packages may contain only
|
||||
the single CAP flight.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass, field
|
||||
@@ -172,6 +174,7 @@ class Package:
|
||||
FlightType.OCA_RUNWAY,
|
||||
FlightType.BAI,
|
||||
FlightType.DEAD,
|
||||
FlightType.TRANSPORT,
|
||||
FlightType.SEAD,
|
||||
FlightType.TARCAP,
|
||||
FlightType.BARCAP,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import defaultdict
|
||||
from datetime import timedelta
|
||||
from enum import Enum
|
||||
from typing import Dict, List, Optional, TYPE_CHECKING, Type
|
||||
@@ -15,6 +14,7 @@ from game.theater.controlpoint import ControlPoint, MissionTarget
|
||||
from game.utils import Distance, meters
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game.transfers import AirliftOrder
|
||||
from gen.ato import Package
|
||||
from gen.flights.flightplan import FlightPlan
|
||||
|
||||
@@ -43,6 +43,7 @@ class FlightType(Enum):
|
||||
OCA_RUNWAY = "OCA/Runway"
|
||||
OCA_AIRCRAFT = "OCA/Aircraft"
|
||||
AEWC = "AEW&C"
|
||||
TRANSPORT = "Transport"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.value
|
||||
@@ -75,6 +76,8 @@ class FlightWaypointType(Enum):
|
||||
DIVERT = 23
|
||||
INGRESS_OCA_RUNWAY = 24
|
||||
INGRESS_OCA_AIRCRAFT = 25
|
||||
PICKUP = 26
|
||||
DROP_OFF = 27
|
||||
|
||||
|
||||
class FlightWaypoint:
|
||||
@@ -164,6 +167,7 @@ class Flight:
|
||||
arrival: ControlPoint,
|
||||
divert: Optional[ControlPoint],
|
||||
custom_name: Optional[str] = None,
|
||||
cargo: Optional[AirliftOrder] = None,
|
||||
) -> None:
|
||||
self.package = package
|
||||
self.country = country
|
||||
@@ -181,6 +185,9 @@ class Flight:
|
||||
self.client_count = 0
|
||||
self.custom_name = custom_name
|
||||
|
||||
# Only used by transport missions.
|
||||
self.cargo = cargo
|
||||
|
||||
# Will be replaced with a more appropriate FlightPlan by
|
||||
# FlightPlanBuilder, but an empty flight plan the flight begins with an
|
||||
# empty flight plan.
|
||||
|
||||
@@ -31,7 +31,6 @@ from game.theater import (
|
||||
TheaterGroundObject,
|
||||
)
|
||||
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
|
||||
@@ -42,6 +41,7 @@ from ..conflictgen import Conflict, FRONTLINE_LENGTH
|
||||
if TYPE_CHECKING:
|
||||
from game import Game
|
||||
from gen.ato import Package
|
||||
from game.transfers import Convoy
|
||||
|
||||
INGRESS_TYPES = {
|
||||
FlightWaypointType.INGRESS_CAS,
|
||||
@@ -736,6 +736,46 @@ class AwacsFlightPlan(LoiterFlightPlan):
|
||||
return self.push_time
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AirliftFlightPlan(FlightPlan):
|
||||
takeoff: FlightWaypoint
|
||||
nav_to_pickup: List[FlightWaypoint]
|
||||
pickup: Optional[FlightWaypoint]
|
||||
nav_to_drop_off: List[FlightWaypoint]
|
||||
drop_off: FlightWaypoint
|
||||
nav_to_home: List[FlightWaypoint]
|
||||
land: FlightWaypoint
|
||||
divert: Optional[FlightWaypoint]
|
||||
|
||||
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
||||
yield self.takeoff
|
||||
yield from self.nav_to_pickup
|
||||
if self.pickup:
|
||||
yield self.pickup
|
||||
yield from self.nav_to_drop_off
|
||||
yield self.drop_off
|
||||
yield from self.nav_to_home
|
||||
yield self.land
|
||||
if self.divert is not None:
|
||||
yield self.divert
|
||||
|
||||
@property
|
||||
def tot_waypoint(self) -> Optional[FlightWaypoint]:
|
||||
return self.drop_off
|
||||
|
||||
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||
# TOT planning isn't really useful for transports. They're behind the front
|
||||
# lines so no need to wait for escorts or for other missions to complete.
|
||||
return None
|
||||
|
||||
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||
return None
|
||||
|
||||
@property
|
||||
def mission_departure_time(self) -> timedelta:
|
||||
return self.package.time_over_target
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CustomFlightPlan(FlightPlan):
|
||||
custom_waypoints: List[FlightWaypoint]
|
||||
@@ -844,6 +884,8 @@ class FlightPlanBuilder:
|
||||
return self.generate_tarcap(flight)
|
||||
elif task == FlightType.AEWC:
|
||||
return self.generate_aewc(flight)
|
||||
elif task == FlightType.TRANSPORT:
|
||||
return self.generate_transport(flight)
|
||||
raise PlanningError(f"{task} flight plan generation not implemented")
|
||||
|
||||
def regenerate_package_waypoints(self) -> None:
|
||||
@@ -1023,6 +1065,8 @@ class FlightPlanBuilder:
|
||||
"""
|
||||
location = self.package.target
|
||||
|
||||
from game.transfers import Convoy
|
||||
|
||||
targets: List[StrikeTarget] = []
|
||||
if isinstance(location, TheaterGroundObject):
|
||||
for group in location.groups:
|
||||
@@ -1141,6 +1185,57 @@ class FlightPlanBuilder:
|
||||
divert=builder.divert(flight.divert),
|
||||
)
|
||||
|
||||
def generate_transport(self, flight: Flight) -> AirliftFlightPlan:
|
||||
"""Generate an airlift flight at a given location.
|
||||
|
||||
Args:
|
||||
flight: The flight to generate the flight plan for.
|
||||
"""
|
||||
cargo = flight.cargo
|
||||
if cargo is None:
|
||||
raise PlanningError(
|
||||
"Cannot plan transport mission for flight with no cargo."
|
||||
)
|
||||
|
||||
altitude = feet(1500)
|
||||
altitude_is_agl = True
|
||||
|
||||
builder = WaypointBuilder(flight, self.game, self.is_player)
|
||||
|
||||
pickup = None
|
||||
nav_to_pickup = []
|
||||
if cargo.origin != flight.departure:
|
||||
pickup = builder.pickup(cargo.origin)
|
||||
nav_to_pickup = builder.nav_path(
|
||||
flight.departure.position,
|
||||
cargo.origin.position,
|
||||
altitude,
|
||||
altitude_is_agl,
|
||||
)
|
||||
|
||||
return AirliftFlightPlan(
|
||||
package=self.package,
|
||||
flight=flight,
|
||||
takeoff=builder.takeoff(flight.departure),
|
||||
nav_to_pickup=nav_to_pickup,
|
||||
pickup=pickup,
|
||||
nav_to_drop_off=builder.nav_path(
|
||||
cargo.origin.position,
|
||||
cargo.destination.position,
|
||||
altitude,
|
||||
altitude_is_agl,
|
||||
),
|
||||
drop_off=builder.drop_off(cargo.destination),
|
||||
nav_to_home=builder.nav_path(
|
||||
cargo.origin.position,
|
||||
flight.arrival.position,
|
||||
altitude,
|
||||
altitude_is_agl,
|
||||
),
|
||||
land=builder.land(flight.arrival),
|
||||
divert=builder.divert(flight.divert),
|
||||
)
|
||||
|
||||
def racetrack_for_objective(
|
||||
self, location: MissionTarget, barcap: bool
|
||||
) -> Tuple[Point, Point]:
|
||||
|
||||
@@ -16,10 +16,9 @@ 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
|
||||
from game.transfers import Convoy
|
||||
|
||||
from game.theater import (
|
||||
ControlPoint,
|
||||
@@ -444,24 +443,69 @@ class WaypointBuilder:
|
||||
return ingress, waypoint, egress
|
||||
|
||||
@staticmethod
|
||||
def nav(position: Point, altitude: Distance) -> FlightWaypoint:
|
||||
def pickup(control_point: ControlPoint) -> FlightWaypoint:
|
||||
"""Creates a cargo pickup waypoint.
|
||||
|
||||
Args:
|
||||
control_point: Pick up location.
|
||||
"""
|
||||
waypoint = FlightWaypoint(
|
||||
FlightWaypointType.PICKUP,
|
||||
control_point.position.x,
|
||||
control_point.position.y,
|
||||
meters(0),
|
||||
)
|
||||
waypoint.alt_type = "RADIO"
|
||||
waypoint.name = "PICKUP"
|
||||
waypoint.description = f"Pick up cargo from {control_point}"
|
||||
waypoint.pretty_name = "Pick up location"
|
||||
return waypoint
|
||||
|
||||
@staticmethod
|
||||
def drop_off(control_point: ControlPoint) -> FlightWaypoint:
|
||||
"""Creates a cargo drop-off waypoint.
|
||||
|
||||
Args:
|
||||
control_point: Drop-off location.
|
||||
"""
|
||||
waypoint = FlightWaypoint(
|
||||
FlightWaypointType.PICKUP,
|
||||
control_point.position.x,
|
||||
control_point.position.y,
|
||||
meters(0),
|
||||
)
|
||||
waypoint.alt_type = "RADIO"
|
||||
waypoint.name = "DROP OFF"
|
||||
waypoint.description = f"Drop off cargo at {control_point}"
|
||||
waypoint.pretty_name = "Drop off location"
|
||||
return waypoint
|
||||
|
||||
@staticmethod
|
||||
def nav(
|
||||
position: Point, altitude: Distance, altitude_is_agl: bool = False
|
||||
) -> FlightWaypoint:
|
||||
"""Creates a navigation point.
|
||||
|
||||
Args:
|
||||
position: Position of the waypoint.
|
||||
altitude: Altitude of the waypoint.
|
||||
altitude_is_agl: True for altitude is AGL. False if altitude is MSL.
|
||||
"""
|
||||
waypoint = FlightWaypoint(
|
||||
FlightWaypointType.NAV, position.x, position.y, altitude
|
||||
)
|
||||
if altitude_is_agl:
|
||||
waypoint.alt_type = "RADIO"
|
||||
waypoint.name = "NAV"
|
||||
waypoint.description = "NAV"
|
||||
waypoint.pretty_name = "Nav"
|
||||
return waypoint
|
||||
|
||||
def nav_path(self, a: Point, b: Point, altitude: Distance) -> List[FlightWaypoint]:
|
||||
def nav_path(
|
||||
self, a: Point, b: Point, altitude: Distance, altitude_is_agl: bool = False
|
||||
) -> List[FlightWaypoint]:
|
||||
path = self.clean_nav_points(self.navmesh.shortest_path(a, b))
|
||||
return [self.nav(self.perturb(p), altitude) for p in path]
|
||||
return [self.nav(self.perturb(p), altitude, altitude_is_agl) for p in path]
|
||||
|
||||
def clean_nav_points(self, points: Iterable[Point]) -> Iterator[Point]:
|
||||
# Examine a sliding window of three waypoints. `current` is the waypoint
|
||||
|
||||
Reference in New Issue
Block a user