mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
176 lines
6.0 KiB
Python
176 lines
6.0 KiB
Python
from __future__ import annotations
|
|
|
|
from collections.abc import Iterator
|
|
from dataclasses import dataclass
|
|
from datetime import timedelta
|
|
from typing import TYPE_CHECKING, Type
|
|
|
|
from game.theater import FrontLine
|
|
from game.utils import Distance, Speed, kph, dcs_to_shapely_point
|
|
from game.utils import feet, nautical_miles
|
|
from .ibuilder import IBuilder
|
|
from .invalidobjectivelocation import InvalidObjectiveLocation
|
|
from .patrolling import PatrollingFlightPlan, PatrollingLayout
|
|
from .uizonedisplay import UiZone, UiZoneDisplay
|
|
from .waypointbuilder import WaypointBuilder
|
|
from ..flightwaypointtype import FlightWaypointType
|
|
from ...flightplan.ipsolver import IpSolver
|
|
from ...persistency import waypoint_debug_directory
|
|
|
|
if TYPE_CHECKING:
|
|
from ..flightwaypoint import FlightWaypoint
|
|
|
|
|
|
@dataclass
|
|
class CasLayout(PatrollingLayout):
|
|
ingress: FlightWaypoint
|
|
|
|
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
|
yield self.departure
|
|
yield from self.nav_to
|
|
if self.ingress:
|
|
yield self.ingress
|
|
yield self.patrol_start
|
|
yield self.patrol_end
|
|
yield from self.nav_from
|
|
yield self.arrival
|
|
if self.divert is not None:
|
|
yield self.divert
|
|
yield self.bullseye
|
|
yield from self.custom_waypoints
|
|
|
|
|
|
class CasFlightPlan(PatrollingFlightPlan[CasLayout], UiZoneDisplay):
|
|
@staticmethod
|
|
def builder_type() -> Type[Builder]:
|
|
return Builder
|
|
|
|
@property
|
|
def patrol_duration(self) -> timedelta:
|
|
return self.flight.coalition.doctrine.cas_duration
|
|
|
|
@property
|
|
def patrol_speed(self) -> Speed:
|
|
# 2021-08-02: patrol_speed will currently have no effect because
|
|
# CAS doesn't use OrbitAction. But all PatrollingFlightPlan are expected
|
|
# to have patrol_speed
|
|
return kph(0)
|
|
|
|
@property
|
|
def engagement_distance(self) -> Distance:
|
|
return nautical_miles(
|
|
self.flight.coalition.game.settings.cas_engagement_range_distance
|
|
)
|
|
|
|
@property
|
|
def combat_speed_waypoints(self) -> set[FlightWaypoint]:
|
|
return {self.layout.ingress, self.layout.patrol_start, self.layout.patrol_end}
|
|
|
|
def request_escort_at(self) -> FlightWaypoint | None:
|
|
return self.layout.patrol_start
|
|
|
|
def dismiss_escort_at(self) -> FlightWaypoint | None:
|
|
return self.layout.patrol_end
|
|
|
|
def ui_zone(self) -> UiZone:
|
|
midpoint = (
|
|
self.layout.patrol_start.position + self.layout.patrol_end.position
|
|
) / 2
|
|
return UiZone(
|
|
[midpoint],
|
|
self.engagement_distance,
|
|
)
|
|
|
|
|
|
class Builder(IBuilder[CasFlightPlan, CasLayout]):
|
|
def layout(self, dump_debug_info: bool) -> CasLayout:
|
|
location = self.package.target
|
|
|
|
if not isinstance(location, FrontLine):
|
|
raise InvalidObjectiveLocation(self.flight.flight_type, location)
|
|
|
|
from game.missiongenerator.frontlineconflictdescription import (
|
|
FrontLineConflictDescription,
|
|
)
|
|
|
|
bounds = FrontLineConflictDescription.frontline_bounds(location, self.theater)
|
|
patrol_start = bounds.left_position
|
|
patrol_end = bounds.right_position
|
|
|
|
start_distance = patrol_start.distance_to_point(self.flight.departure.position)
|
|
end_distance = patrol_end.distance_to_point(self.flight.departure.position)
|
|
if end_distance < start_distance:
|
|
patrol_start, patrol_end = patrol_end, patrol_start
|
|
|
|
builder = WaypointBuilder(self.flight)
|
|
|
|
is_helo = self.flight.unit_type.dcs_unit_type.helicopter
|
|
ingress_egress_altitude = (
|
|
self.doctrine.ingress_altitude
|
|
if not is_helo
|
|
else feet(self.coalition.game.settings.heli_combat_alt_agl)
|
|
)
|
|
use_agl_patrol_altitude = is_helo
|
|
|
|
ip_solver = IpSolver(
|
|
dcs_to_shapely_point(self.flight.departure.position),
|
|
dcs_to_shapely_point(patrol_start),
|
|
self.doctrine,
|
|
self.threat_zones.all,
|
|
)
|
|
ip_solver.set_debug_properties(
|
|
waypoint_debug_directory() / "IP", self.theater.terrain
|
|
)
|
|
ingress_point_shapely = ip_solver.solve()
|
|
if dump_debug_info:
|
|
ip_solver.dump_debug_info()
|
|
|
|
ingress_point = patrol_start.new_in_same_map(
|
|
ingress_point_shapely.x, ingress_point_shapely.y
|
|
)
|
|
|
|
patrol_start_waypoint = builder.nav(
|
|
patrol_start, ingress_egress_altitude, use_agl_patrol_altitude
|
|
)
|
|
patrol_start_waypoint.name = "FLOT START"
|
|
patrol_start_waypoint.pretty_name = "FLOT start"
|
|
patrol_start_waypoint.description = "FLOT boundary"
|
|
|
|
patrol_end_waypoint = builder.nav(
|
|
patrol_end, ingress_egress_altitude, use_agl_patrol_altitude
|
|
)
|
|
patrol_end_waypoint.name = "FLOT END"
|
|
patrol_end_waypoint.pretty_name = "FLOT end"
|
|
patrol_end_waypoint.description = "FLOT boundary"
|
|
|
|
ingress = builder.ingress(
|
|
FlightWaypointType.INGRESS_CAS, ingress_point, location
|
|
)
|
|
ingress.description = f"Ingress to provide CAS at {location}"
|
|
|
|
return CasLayout(
|
|
departure=builder.takeoff(self.flight.departure),
|
|
nav_to=builder.nav_path(
|
|
self.flight.departure.position,
|
|
ingress_point,
|
|
ingress_egress_altitude,
|
|
use_agl_patrol_altitude,
|
|
),
|
|
nav_from=builder.nav_path(
|
|
patrol_end,
|
|
self.flight.arrival.position,
|
|
ingress_egress_altitude,
|
|
use_agl_patrol_altitude,
|
|
),
|
|
ingress=ingress,
|
|
patrol_start=patrol_start_waypoint,
|
|
patrol_end=patrol_end_waypoint,
|
|
arrival=builder.land(self.flight.arrival),
|
|
divert=builder.divert(self.flight.divert),
|
|
bullseye=builder.bullseye(),
|
|
custom_waypoints=list(),
|
|
)
|
|
|
|
def build(self, dump_debug_info: bool = False) -> CasFlightPlan:
|
|
return CasFlightPlan(self.flight, self.layout(dump_debug_info))
|