mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
* Helicopter waypoint altitude configurable Added a new option in Settings: Helicopter waypoint altitude (feet AGL). It sets the waypoint altitude for helicopters in feet AGL. In campaigns in more mountainous areas, you might want to increase this setting to avoid the AI flying into the terrain. * black? * Distinguish cruise/combat altitudes for helicopters Also includes a refactor for WaypointBuilder so it doesn't need a coalition. It can already reference the coalition from the flight. * Update changelog.md --------- Co-authored-by: Raffson <Raffson@users.noreply.github.com>
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) -> 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)
|