mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Putting the ingress point directly on one end of the FLOT means that AI flights won't start searching and engaging targets until they reach that point. If the front line has advanced toward the flight's departure airfield, it might overfly targets on its way to the IP. Instead, place an IP for CAS the same way we place any other IP. The AI will fly to that and start searching from there. This also: * Removes the midpoint waypoint, since it didn't serve any real purpose * Names the FLOT boundary waypoints for what they actually are Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2231.
181 lines
6.2 KiB
Python
181 lines
6.2 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from datetime import timedelta
|
|
from typing import Iterator, TYPE_CHECKING, Type
|
|
|
|
from game.theater.controlpoint import ControlPointType
|
|
from game.theater.missiontarget import MissionTarget
|
|
from game.utils import Distance, feet, meters
|
|
from ._common_ctld import generate_random_ctld_point
|
|
from .formationattack import (
|
|
FormationAttackLayout,
|
|
FormationAttackBuilder,
|
|
FormationAttackFlightPlan,
|
|
)
|
|
from .planningerror import PlanningError
|
|
from .uizonedisplay import UiZone, UiZoneDisplay
|
|
from .waypointbuilder import WaypointBuilder
|
|
from ..flightwaypoint import FlightWaypointType
|
|
from ...theater.interfaces.CTLD import CTLD
|
|
|
|
if TYPE_CHECKING:
|
|
from dcs import Point
|
|
from ..flightwaypoint import FlightWaypoint
|
|
|
|
|
|
@dataclass
|
|
class AirAssaultLayout(FormationAttackLayout):
|
|
# The pickup point is optional because we don't always need to load the cargo. When
|
|
# departing from a carrier, LHA, or off-map spawn, the cargo is pre-loaded.
|
|
pickup: FlightWaypoint | None = None
|
|
drop_off: FlightWaypoint | None = None
|
|
# This is an implementation detail used by CTLD. The aircraft will not go to this
|
|
# waypoint. It is used by CTLD as the destination for unloaded troops.
|
|
|
|
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
|
yield self.departure
|
|
if self.pickup is not None:
|
|
yield self.pickup
|
|
yield from self.nav_to
|
|
yield self.ingress
|
|
if self.drop_off is not None:
|
|
yield self.drop_off
|
|
yield self.targets[0]
|
|
yield from self.nav_from
|
|
yield self.arrival
|
|
if self.divert is not None:
|
|
yield self.divert
|
|
yield self.bullseye
|
|
|
|
|
|
class AirAssaultFlightPlan(FormationAttackFlightPlan, UiZoneDisplay):
|
|
@staticmethod
|
|
def builder_type() -> Type[Builder]:
|
|
return Builder
|
|
|
|
@property
|
|
def is_airassault(self) -> bool:
|
|
return True
|
|
|
|
@property
|
|
def tot_waypoint(self) -> FlightWaypoint:
|
|
if self.flight.is_helo and self.layout.drop_off is not None:
|
|
return self.layout.drop_off
|
|
return self.layout.targets[0]
|
|
|
|
@property
|
|
def ingress_time(self) -> timedelta:
|
|
tot = self.tot
|
|
travel_time = self.travel_time_between_waypoints(
|
|
self.layout.ingress, self.tot_waypoint
|
|
)
|
|
return tot - travel_time
|
|
|
|
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
|
|
if waypoint is self.tot_waypoint:
|
|
return self.tot
|
|
elif waypoint is self.layout.ingress:
|
|
return self.ingress_time
|
|
return None
|
|
|
|
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
|
|
return None
|
|
|
|
@property
|
|
def ctld_target_zone_radius(self) -> Distance:
|
|
return meters(2500)
|
|
|
|
@property
|
|
def mission_departure_time(self) -> timedelta:
|
|
return self.package.time_over_target
|
|
|
|
def ui_zone(self) -> UiZone:
|
|
return UiZone(
|
|
[self.layout.targets[0].position],
|
|
self.ctld_target_zone_radius,
|
|
)
|
|
|
|
|
|
class Builder(FormationAttackBuilder[AirAssaultFlightPlan, AirAssaultLayout]):
|
|
def layout(self) -> AirAssaultLayout:
|
|
if not self.flight.is_helo and not self.flight.is_hercules:
|
|
raise PlanningError(
|
|
"Air assault is only usable by helicopters and Anubis' C-130 mod"
|
|
)
|
|
assert self.package.waypoints is not None
|
|
|
|
heli_alt = feet(self.coalition.game.settings.heli_cruise_alt_agl)
|
|
altitude = heli_alt if self.flight.is_helo else self.doctrine.ingress_altitude
|
|
altitude_is_agl = self.flight.is_helo
|
|
|
|
builder = WaypointBuilder(self.flight)
|
|
|
|
if self.flight.is_hercules or self.flight.departure.cptype in [
|
|
ControlPointType.AIRCRAFT_CARRIER_GROUP,
|
|
ControlPointType.LHA_GROUP,
|
|
ControlPointType.OFF_MAP,
|
|
]:
|
|
# Off_Map spawns will be preloaded
|
|
# Carrier operations load the logistics directly from the carrier
|
|
pickup = None
|
|
pickup_position = self.flight.departure.position
|
|
else:
|
|
pickup = builder.pickup_zone(
|
|
MissionTarget(
|
|
"Pickup Zone",
|
|
self._generate_ctld_pickup(),
|
|
)
|
|
)
|
|
pickup_position = pickup.position
|
|
assault_area = builder.assault_area(self.package.target)
|
|
heading = self.package.target.position.heading_between_point(pickup_position)
|
|
if self.flight.is_hercules:
|
|
assault_area.only_for_player = False
|
|
assault_area.alt = feet(1000)
|
|
|
|
# TODO: define CTLD dropoff zones in campaign miz?
|
|
drop_off_zone = MissionTarget(
|
|
"Dropoff zone",
|
|
self.package.target.position.point_from_heading(heading, 1200),
|
|
)
|
|
dz = builder.dropoff_zone(drop_off_zone) if self.flight.is_helo else None
|
|
|
|
return AirAssaultLayout(
|
|
departure=builder.takeoff(self.flight.departure),
|
|
pickup=pickup,
|
|
nav_to=builder.nav_path(
|
|
pickup_position,
|
|
self.package.waypoints.ingress,
|
|
altitude,
|
|
altitude_is_agl,
|
|
),
|
|
ingress=builder.ingress(
|
|
FlightWaypointType.INGRESS_AIR_ASSAULT,
|
|
self.package.waypoints.ingress,
|
|
self.package.target,
|
|
),
|
|
drop_off=dz,
|
|
targets=[assault_area],
|
|
nav_from=builder.nav_path(
|
|
drop_off_zone.position,
|
|
self.flight.arrival.position,
|
|
altitude,
|
|
altitude_is_agl,
|
|
),
|
|
arrival=builder.land(self.flight.arrival),
|
|
divert=builder.divert(self.flight.divert),
|
|
bullseye=builder.bullseye(),
|
|
hold=None,
|
|
join=builder.join(pickup_position),
|
|
split=builder.split(self.package.waypoints.split),
|
|
refuel=None,
|
|
)
|
|
|
|
def build(self, dump_debug_info: bool = False) -> AirAssaultFlightPlan:
|
|
return AirAssaultFlightPlan(self.flight, self.layout())
|
|
|
|
def _generate_ctld_pickup(self) -> Point:
|
|
assert isinstance(self.flight.departure, CTLD)
|
|
return generate_random_ctld_point(self.flight.departure)
|