mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Split flight plan layout into a separate class.
During package planning we don't care about the details of the flight plan, just the layout (to check if the layout is threatened and we need escorts). Splitting these will allow us to reduce the amount of work that must be done in each loop of the planning phase, potentially caching attempted flight plans between loops.
This commit is contained in:
parent
fa8c0d9660
commit
769fe12159
@ -27,6 +27,7 @@ if TYPE_CHECKING:
|
|||||||
from game.squadrons import Squadron, Pilot
|
from game.squadrons import Squadron, Pilot
|
||||||
from game.theater import ControlPoint, MissionTarget
|
from game.theater import ControlPoint, MissionTarget
|
||||||
from game.transfers import TransferOrder
|
from game.transfers import TransferOrder
|
||||||
|
from .flightplans.flightplan import FlightPlan
|
||||||
from .flighttype import FlightType
|
from .flighttype import FlightType
|
||||||
from .flightwaypoint import FlightWaypoint
|
from .flightwaypoint import FlightWaypoint
|
||||||
from .package import Package
|
from .package import Package
|
||||||
@ -84,10 +85,11 @@ class Flight(SidcDescribable):
|
|||||||
# Will be replaced with a more appropriate FlightPlan by
|
# Will be replaced with a more appropriate FlightPlan by
|
||||||
# FlightPlanBuilder, but an empty flight plan the flight begins with an
|
# FlightPlanBuilder, but an empty flight plan the flight begins with an
|
||||||
# empty flight plan.
|
# empty flight plan.
|
||||||
from game.ato.flightplans.flightplan import FlightPlan
|
from .flightplans.custom import CustomFlightPlan, CustomLayout
|
||||||
from .flightplans.custom import CustomFlightPlan
|
|
||||||
|
|
||||||
self.flight_plan: FlightPlan = CustomFlightPlan(self, [])
|
self.flight_plan: FlightPlan[Any] = CustomFlightPlan(
|
||||||
|
self, CustomLayout(custom_waypoints=[])
|
||||||
|
)
|
||||||
|
|
||||||
def __getstate__(self) -> dict[str, Any]:
|
def __getstate__(self) -> dict[str, Any]:
|
||||||
state = self.__dict__.copy()
|
state = self.__dict__.copy()
|
||||||
@ -196,9 +198,8 @@ class Flight(SidcDescribable):
|
|||||||
def abort(self) -> None:
|
def abort(self) -> None:
|
||||||
from .flightplans.rtb import RtbFlightPlan
|
from .flightplans.rtb import RtbFlightPlan
|
||||||
|
|
||||||
self.flight_plan = RtbFlightPlan.builder_type()(
|
layout = RtbFlightPlan.builder_type()(self, self.coalition.game.theater).build()
|
||||||
self, self.coalition.game.theater
|
self.flight_plan = RtbFlightPlan(self, layout)
|
||||||
).build()
|
|
||||||
|
|
||||||
self.set_state(
|
self.set_state(
|
||||||
Navigating(
|
Navigating(
|
||||||
|
|||||||
@ -4,17 +4,15 @@ from datetime import timedelta
|
|||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
from game.ato.flightplans.ibuilder import IBuilder
|
from game.ato.flightplans.ibuilder import IBuilder
|
||||||
from game.ato.flightplans.patrolling import PatrollingFlightPlan
|
from game.ato.flightplans.patrolling import PatrollingFlightPlan, PatrollingLayout
|
||||||
from game.ato.flightplans.waypointbuilder import WaypointBuilder
|
from game.ato.flightplans.waypointbuilder import WaypointBuilder
|
||||||
from game.utils import Heading, feet, knots, meters, nautical_miles
|
from game.utils import Distance, Heading, Speed, feet, knots, meters, nautical_miles
|
||||||
|
|
||||||
|
|
||||||
class Builder(IBuilder):
|
class Builder(IBuilder):
|
||||||
def build(self) -> AewcFlightPlan:
|
def build(self) -> PatrollingLayout:
|
||||||
racetrack_half_distance = nautical_miles(30).meters
|
racetrack_half_distance = nautical_miles(30).meters
|
||||||
|
|
||||||
patrol_duration = timedelta(hours=4)
|
|
||||||
|
|
||||||
location = self.package.target
|
location = self.package.target
|
||||||
|
|
||||||
closest_boundary = self.threat_zones.closest_boundary(location.position)
|
closest_boundary = self.threat_zones.closest_boundary(location.position)
|
||||||
@ -52,15 +50,9 @@ class Builder(IBuilder):
|
|||||||
else:
|
else:
|
||||||
altitude = feet(25000)
|
altitude = feet(25000)
|
||||||
|
|
||||||
if self.flight.unit_type.preferred_patrol_speed(altitude) is not None:
|
|
||||||
speed = self.flight.unit_type.preferred_patrol_speed(altitude)
|
|
||||||
else:
|
|
||||||
speed = knots(390)
|
|
||||||
|
|
||||||
racetrack = builder.race_track(racetrack_start, racetrack_end, altitude)
|
racetrack = builder.race_track(racetrack_start, racetrack_end, altitude)
|
||||||
|
|
||||||
return AewcFlightPlan(
|
return PatrollingLayout(
|
||||||
flight=self.flight,
|
|
||||||
departure=builder.takeoff(self.flight.departure),
|
departure=builder.takeoff(self.flight.departure),
|
||||||
nav_to=builder.nav_path(
|
nav_to=builder.nav_path(
|
||||||
self.flight.departure.position, racetrack_start, altitude
|
self.flight.departure.position, racetrack_start, altitude
|
||||||
@ -73,15 +65,27 @@ class Builder(IBuilder):
|
|||||||
arrival=builder.land(self.flight.arrival),
|
arrival=builder.land(self.flight.arrival),
|
||||||
divert=builder.divert(self.flight.divert),
|
divert=builder.divert(self.flight.divert),
|
||||||
bullseye=builder.bullseye(),
|
bullseye=builder.bullseye(),
|
||||||
patrol_duration=patrol_duration,
|
|
||||||
patrol_speed=speed,
|
|
||||||
# TODO: Factor out a common base of the combat and non-combat race-tracks.
|
|
||||||
# No harm in setting this, but we ought to clean up a bit.
|
|
||||||
engagement_distance=meters(0),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AewcFlightPlan(PatrollingFlightPlan):
|
class AewcFlightPlan(PatrollingFlightPlan[PatrollingLayout]):
|
||||||
|
@property
|
||||||
|
def patrol_duration(self) -> timedelta:
|
||||||
|
return timedelta(hours=4)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def patrol_speed(self) -> Speed:
|
||||||
|
altitude = self.layout.patrol_start.alt
|
||||||
|
if self.flight.unit_type.preferred_patrol_speed(altitude) is not None:
|
||||||
|
return self.flight.unit_type.preferred_patrol_speed(altitude)
|
||||||
|
return knots(390)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def engagement_distance(self) -> Distance:
|
||||||
|
# TODO: Factor out a common base of the combat and non-combat race-tracks.
|
||||||
|
# No harm in setting this, but we ought to clean up a bit.
|
||||||
|
return meters(0)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def builder_type() -> Type[IBuilder]:
|
def builder_type() -> Type[IBuilder]:
|
||||||
return Builder
|
return Builder
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Iterator
|
from collections.abc import Iterator
|
||||||
|
from dataclasses import dataclass
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import TYPE_CHECKING, Type
|
from typing import TYPE_CHECKING, Type
|
||||||
|
|
||||||
from game.utils import feet
|
from game.utils import feet
|
||||||
from .ibuilder import IBuilder
|
from .ibuilder import IBuilder
|
||||||
from .planningerror import PlanningError
|
from .planningerror import PlanningError
|
||||||
from .standard import StandardFlightPlan
|
from .standard import StandardFlightPlan, StandardLayout
|
||||||
from .waypointbuilder import WaypointBuilder
|
from .waypointbuilder import WaypointBuilder
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -16,7 +17,7 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class Builder(IBuilder):
|
class Builder(IBuilder):
|
||||||
def build(self) -> AirliftFlightPlan:
|
def build(self) -> AirliftLayout:
|
||||||
cargo = self.flight.cargo
|
cargo = self.flight.cargo
|
||||||
if cargo is None:
|
if cargo is None:
|
||||||
raise PlanningError(
|
raise PlanningError(
|
||||||
@ -39,8 +40,7 @@ class Builder(IBuilder):
|
|||||||
altitude_is_agl,
|
altitude_is_agl,
|
||||||
)
|
)
|
||||||
|
|
||||||
return AirliftFlightPlan(
|
return AirliftLayout(
|
||||||
flight=self.flight,
|
|
||||||
departure=builder.takeoff(self.flight.departure),
|
departure=builder.takeoff(self.flight.departure),
|
||||||
nav_to_pickup=nav_to_pickup,
|
nav_to_pickup=nav_to_pickup,
|
||||||
pickup=pickup,
|
pickup=pickup,
|
||||||
@ -63,30 +63,13 @@ class Builder(IBuilder):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AirliftFlightPlan(StandardFlightPlan):
|
@dataclass(frozen=True)
|
||||||
def __init__(
|
class AirliftLayout(StandardLayout):
|
||||||
self,
|
nav_to_pickup: list[FlightWaypoint]
|
||||||
flight: Flight,
|
pickup: FlightWaypoint | None
|
||||||
departure: FlightWaypoint,
|
nav_to_drop_off: list[FlightWaypoint]
|
||||||
nav_to_pickup: list[FlightWaypoint],
|
drop_off: FlightWaypoint
|
||||||
pickup: FlightWaypoint | None,
|
nav_to_home: list[FlightWaypoint]
|
||||||
nav_to_drop_off: list[FlightWaypoint],
|
|
||||||
drop_off: FlightWaypoint,
|
|
||||||
nav_to_home: list[FlightWaypoint],
|
|
||||||
arrival: FlightWaypoint,
|
|
||||||
divert: FlightWaypoint | None,
|
|
||||||
bullseye: FlightWaypoint,
|
|
||||||
) -> None:
|
|
||||||
super().__init__(flight, departure, arrival, divert, bullseye)
|
|
||||||
self.nav_to_pickup = nav_to_pickup
|
|
||||||
self.pickup = pickup
|
|
||||||
self.nav_to_drop_off = nav_to_drop_off
|
|
||||||
self.drop_off = drop_off
|
|
||||||
self.nav_to_home = nav_to_home
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def builder_type() -> Type[Builder]:
|
|
||||||
return Builder
|
|
||||||
|
|
||||||
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
||||||
yield self.departure
|
yield self.departure
|
||||||
@ -101,9 +84,18 @@ class AirliftFlightPlan(StandardFlightPlan):
|
|||||||
yield self.divert
|
yield self.divert
|
||||||
yield self.bullseye
|
yield self.bullseye
|
||||||
|
|
||||||
|
|
||||||
|
class AirliftFlightPlan(StandardFlightPlan[AirliftLayout]):
|
||||||
|
def __init__(self, flight: Flight, layout: AirliftLayout) -> None:
|
||||||
|
super().__init__(flight, layout)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def builder_type() -> Type[Builder]:
|
||||||
|
return Builder
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def tot_waypoint(self) -> FlightWaypoint | None:
|
def tot_waypoint(self) -> FlightWaypoint | None:
|
||||||
return self.drop_off
|
return self.layout.drop_off
|
||||||
|
|
||||||
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
|
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
|
||||||
# TOT planning isn't really useful for transports. They're behind the front
|
# TOT planning isn't really useful for transports. They're behind the front
|
||||||
|
|||||||
@ -4,7 +4,11 @@ from typing import Type
|
|||||||
|
|
||||||
from game.theater import NavalControlPoint
|
from game.theater import NavalControlPoint
|
||||||
from game.theater.theatergroundobject import NavalGroundObject
|
from game.theater.theatergroundobject import NavalGroundObject
|
||||||
from .formationattack import FormationAttackBuilder, FormationAttackFlightPlan
|
from .formationattack import (
|
||||||
|
FormationAttackBuilder,
|
||||||
|
FormationAttackFlightPlan,
|
||||||
|
FormationAttackLayout,
|
||||||
|
)
|
||||||
from .invalidobjectivelocation import InvalidObjectiveLocation
|
from .invalidobjectivelocation import InvalidObjectiveLocation
|
||||||
from .waypointbuilder import StrikeTarget
|
from .waypointbuilder import StrikeTarget
|
||||||
from ..flightwaypointtype import FlightWaypointType
|
from ..flightwaypointtype import FlightWaypointType
|
||||||
@ -16,8 +20,8 @@ class AntiShipFlightPlan(FormationAttackFlightPlan):
|
|||||||
return Builder
|
return Builder
|
||||||
|
|
||||||
|
|
||||||
class Builder(FormationAttackBuilder[AntiShipFlightPlan]):
|
class Builder(FormationAttackBuilder):
|
||||||
def build(self) -> FormationAttackFlightPlan:
|
def build(self) -> FormationAttackLayout:
|
||||||
location = self.package.target
|
location = self.package.target
|
||||||
|
|
||||||
from game.transfers import CargoShip
|
from game.transfers import CargoShip
|
||||||
@ -31,7 +35,7 @@ class Builder(FormationAttackBuilder[AntiShipFlightPlan]):
|
|||||||
else:
|
else:
|
||||||
raise InvalidObjectiveLocation(self.flight.flight_type, location)
|
raise InvalidObjectiveLocation(self.flight.flight_type, location)
|
||||||
|
|
||||||
return self._build(AntiShipFlightPlan, FlightWaypointType.INGRESS_BAI, targets)
|
return self._build(FlightWaypointType.INGRESS_BAI, targets)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def anti_ship_targets_for_tgo(tgo: NavalGroundObject) -> list[StrikeTarget]:
|
def anti_ship_targets_for_tgo(tgo: NavalGroundObject) -> list[StrikeTarget]:
|
||||||
|
|||||||
@ -3,7 +3,11 @@ from __future__ import annotations
|
|||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
from game.theater.theatergroundobject import TheaterGroundObject
|
from game.theater.theatergroundobject import TheaterGroundObject
|
||||||
from .formationattack import FormationAttackBuilder, FormationAttackFlightPlan
|
from .formationattack import (
|
||||||
|
FormationAttackBuilder,
|
||||||
|
FormationAttackFlightPlan,
|
||||||
|
FormationAttackLayout,
|
||||||
|
)
|
||||||
from .invalidobjectivelocation import InvalidObjectiveLocation
|
from .invalidobjectivelocation import InvalidObjectiveLocation
|
||||||
from .waypointbuilder import StrikeTarget
|
from .waypointbuilder import StrikeTarget
|
||||||
from ..flightwaypointtype import FlightWaypointType
|
from ..flightwaypointtype import FlightWaypointType
|
||||||
@ -15,8 +19,8 @@ class BaiFlightPlan(FormationAttackFlightPlan):
|
|||||||
return Builder
|
return Builder
|
||||||
|
|
||||||
|
|
||||||
class Builder(FormationAttackBuilder[BaiFlightPlan]):
|
class Builder(FormationAttackBuilder):
|
||||||
def build(self) -> FormationAttackFlightPlan:
|
def build(self) -> FormationAttackLayout:
|
||||||
location = self.package.target
|
location = self.package.target
|
||||||
|
|
||||||
from game.transfers import Convoy
|
from game.transfers import Convoy
|
||||||
@ -33,4 +37,4 @@ class Builder(FormationAttackBuilder[BaiFlightPlan]):
|
|||||||
else:
|
else:
|
||||||
raise InvalidObjectiveLocation(self.flight.flight_type, location)
|
raise InvalidObjectiveLocation(self.flight.flight_type, location)
|
||||||
|
|
||||||
return self._build(BaiFlightPlan, FlightWaypointType.INGRESS_BAI, targets)
|
return self._build(FlightWaypointType.INGRESS_BAI, targets)
|
||||||
|
|||||||
@ -1,18 +1,19 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import random
|
import random
|
||||||
|
from datetime import timedelta
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
from game.theater import FrontLine
|
from game.theater import FrontLine
|
||||||
from game.utils import feet
|
from game.utils import Distance, Speed, feet
|
||||||
from .capbuilder import CapBuilder
|
from .capbuilder import CapBuilder
|
||||||
from .invalidobjectivelocation import InvalidObjectiveLocation
|
from .invalidobjectivelocation import InvalidObjectiveLocation
|
||||||
from .patrolling import PatrollingFlightPlan
|
from .patrolling import PatrollingFlightPlan, PatrollingLayout
|
||||||
from .waypointbuilder import WaypointBuilder
|
from .waypointbuilder import WaypointBuilder
|
||||||
|
|
||||||
|
|
||||||
class Builder(CapBuilder):
|
class Builder(CapBuilder):
|
||||||
def build(self) -> BarCapFlightPlan:
|
def build(self) -> PatrollingLayout:
|
||||||
location = self.package.target
|
location = self.package.target
|
||||||
|
|
||||||
if isinstance(location, FrontLine):
|
if isinstance(location, FrontLine):
|
||||||
@ -27,16 +28,10 @@ class Builder(CapBuilder):
|
|||||||
min(self.doctrine.max_patrol_altitude, randomized_alt),
|
min(self.doctrine.max_patrol_altitude, randomized_alt),
|
||||||
)
|
)
|
||||||
|
|
||||||
patrol_speed = self.flight.unit_type.preferred_patrol_speed(patrol_alt)
|
|
||||||
|
|
||||||
builder = WaypointBuilder(self.flight, self.coalition)
|
builder = WaypointBuilder(self.flight, self.coalition)
|
||||||
start, end = builder.race_track(start_pos, end_pos, patrol_alt)
|
start, end = builder.race_track(start_pos, end_pos, patrol_alt)
|
||||||
|
|
||||||
return BarCapFlightPlan(
|
return PatrollingLayout(
|
||||||
flight=self.flight,
|
|
||||||
patrol_duration=self.doctrine.cap_duration,
|
|
||||||
patrol_speed=patrol_speed,
|
|
||||||
engagement_distance=self.doctrine.cap_engagement_range,
|
|
||||||
departure=builder.takeoff(self.flight.departure),
|
departure=builder.takeoff(self.flight.departure),
|
||||||
nav_to=builder.nav_path(
|
nav_to=builder.nav_path(
|
||||||
self.flight.departure.position, start.position, patrol_alt
|
self.flight.departure.position, start.position, patrol_alt
|
||||||
@ -52,7 +47,21 @@ class Builder(CapBuilder):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class BarCapFlightPlan(PatrollingFlightPlan):
|
class BarCapFlightPlan(PatrollingFlightPlan[PatrollingLayout]):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def builder_type() -> Type[Builder]:
|
def builder_type() -> Type[Builder]:
|
||||||
return Builder
|
return Builder
|
||||||
|
|
||||||
|
@property
|
||||||
|
def patrol_duration(self) -> timedelta:
|
||||||
|
return self.flight.coalition.doctrine.cap_duration
|
||||||
|
|
||||||
|
@property
|
||||||
|
def patrol_speed(self) -> Speed:
|
||||||
|
return self.flight.unit_type.preferred_patrol_speed(
|
||||||
|
self.layout.patrol_start.alt
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def engagement_distance(self) -> Distance:
|
||||||
|
return self.flight.coalition.doctrine.cap_engagement_range
|
||||||
|
|||||||
@ -1,24 +1,24 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Iterator
|
from collections.abc import Iterator
|
||||||
|
from dataclasses import dataclass
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import TYPE_CHECKING, Type
|
from typing import TYPE_CHECKING, Type
|
||||||
|
|
||||||
from game.theater import FrontLine
|
from game.theater import FrontLine
|
||||||
from game.utils import Distance, Speed, meters
|
from game.utils import Distance, Speed, kph, meters
|
||||||
from .ibuilder import IBuilder
|
from .ibuilder import IBuilder
|
||||||
from .invalidobjectivelocation import InvalidObjectiveLocation
|
from .invalidobjectivelocation import InvalidObjectiveLocation
|
||||||
from .patrolling import PatrollingFlightPlan
|
from .patrolling import PatrollingFlightPlan, PatrollingLayout
|
||||||
from .waypointbuilder import WaypointBuilder
|
from .waypointbuilder import WaypointBuilder
|
||||||
from ..flightwaypointtype import FlightWaypointType
|
from ..flightwaypointtype import FlightWaypointType
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..flight import Flight
|
|
||||||
from ..flightwaypoint import FlightWaypoint
|
from ..flightwaypoint import FlightWaypoint
|
||||||
|
|
||||||
|
|
||||||
class Builder(IBuilder):
|
class Builder(IBuilder):
|
||||||
def build(self) -> CasFlightPlan:
|
def build(self) -> CasLayout:
|
||||||
location = self.package.target
|
location = self.package.target
|
||||||
|
|
||||||
if not isinstance(location, FrontLine):
|
if not isinstance(location, FrontLine):
|
||||||
@ -41,24 +41,13 @@ class Builder(IBuilder):
|
|||||||
|
|
||||||
builder = WaypointBuilder(self.flight, self.coalition)
|
builder = WaypointBuilder(self.flight, self.coalition)
|
||||||
|
|
||||||
# 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
|
|
||||||
is_helo = self.flight.unit_type.dcs_unit_type.helicopter
|
is_helo = self.flight.unit_type.dcs_unit_type.helicopter
|
||||||
ingress_egress_altitude = (
|
ingress_egress_altitude = (
|
||||||
self.doctrine.ingress_altitude if not is_helo else meters(50)
|
self.doctrine.ingress_altitude if not is_helo else meters(50)
|
||||||
)
|
)
|
||||||
patrol_speed = self.flight.unit_type.preferred_patrol_speed(
|
|
||||||
ingress_egress_altitude
|
|
||||||
)
|
|
||||||
use_agl_ingress_egress = is_helo
|
use_agl_ingress_egress = is_helo
|
||||||
|
|
||||||
from game.missiongenerator.frontlineconflictdescription import FRONTLINE_LENGTH
|
return CasLayout(
|
||||||
|
|
||||||
return CasFlightPlan(
|
|
||||||
flight=self.flight,
|
|
||||||
patrol_duration=self.doctrine.cas_duration,
|
|
||||||
patrol_speed=patrol_speed,
|
|
||||||
departure=builder.takeoff(self.flight.departure),
|
departure=builder.takeoff(self.flight.departure),
|
||||||
nav_to=builder.nav_path(
|
nav_to=builder.nav_path(
|
||||||
self.flight.departure.position,
|
self.flight.departure.position,
|
||||||
@ -75,7 +64,6 @@ class Builder(IBuilder):
|
|||||||
patrol_start=builder.ingress(
|
patrol_start=builder.ingress(
|
||||||
FlightWaypointType.INGRESS_CAS, ingress, location
|
FlightWaypointType.INGRESS_CAS, ingress, location
|
||||||
),
|
),
|
||||||
engagement_distance=meters(FRONTLINE_LENGTH) / 2,
|
|
||||||
target=builder.cas(center),
|
target=builder.cas(center),
|
||||||
patrol_end=builder.egress(egress, location),
|
patrol_end=builder.egress(egress, location),
|
||||||
arrival=builder.land(self.flight.arrival),
|
arrival=builder.land(self.flight.arrival),
|
||||||
@ -84,42 +72,9 @@ class Builder(IBuilder):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class CasFlightPlan(PatrollingFlightPlan):
|
@dataclass(frozen=True)
|
||||||
def __init__(
|
class CasLayout(PatrollingLayout):
|
||||||
self,
|
target: FlightWaypoint
|
||||||
flight: Flight,
|
|
||||||
departure: FlightWaypoint,
|
|
||||||
arrival: FlightWaypoint,
|
|
||||||
divert: FlightWaypoint | None,
|
|
||||||
bullseye: FlightWaypoint,
|
|
||||||
nav_to: list[FlightWaypoint],
|
|
||||||
nav_from: list[FlightWaypoint],
|
|
||||||
patrol_start: FlightWaypoint,
|
|
||||||
patrol_end: FlightWaypoint,
|
|
||||||
patrol_duration: timedelta,
|
|
||||||
patrol_speed: Speed,
|
|
||||||
engagement_distance: Distance,
|
|
||||||
target: FlightWaypoint,
|
|
||||||
) -> None:
|
|
||||||
super().__init__(
|
|
||||||
flight,
|
|
||||||
departure,
|
|
||||||
arrival,
|
|
||||||
divert,
|
|
||||||
bullseye,
|
|
||||||
nav_to,
|
|
||||||
nav_from,
|
|
||||||
patrol_start,
|
|
||||||
patrol_end,
|
|
||||||
patrol_duration,
|
|
||||||
patrol_speed,
|
|
||||||
engagement_distance,
|
|
||||||
)
|
|
||||||
self.target = target
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def builder_type() -> Type[Builder]:
|
|
||||||
return Builder
|
|
||||||
|
|
||||||
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
||||||
yield self.departure
|
yield self.departure
|
||||||
@ -133,12 +88,35 @@ class CasFlightPlan(PatrollingFlightPlan):
|
|||||||
yield self.divert
|
yield self.divert
|
||||||
yield self.bullseye
|
yield self.bullseye
|
||||||
|
|
||||||
|
|
||||||
|
class CasFlightPlan(PatrollingFlightPlan[CasLayout]):
|
||||||
|
@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:
|
||||||
|
from game.missiongenerator.frontlineconflictdescription import FRONTLINE_LENGTH
|
||||||
|
|
||||||
|
return meters(FRONTLINE_LENGTH) / 2
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def combat_speed_waypoints(self) -> set[FlightWaypoint]:
|
def combat_speed_waypoints(self) -> set[FlightWaypoint]:
|
||||||
return {self.patrol_start, self.target, self.patrol_end}
|
return {self.layout.patrol_start, self.layout.target, self.layout.patrol_end}
|
||||||
|
|
||||||
def request_escort_at(self) -> FlightWaypoint | None:
|
def request_escort_at(self) -> FlightWaypoint | None:
|
||||||
return self.patrol_start
|
return self.layout.patrol_start
|
||||||
|
|
||||||
def dismiss_escort_at(self) -> FlightWaypoint | None:
|
def dismiss_escort_at(self) -> FlightWaypoint | None:
|
||||||
return self.patrol_end
|
return self.layout.patrol_end
|
||||||
|
|||||||
@ -1,35 +1,36 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Iterator
|
from collections.abc import Iterator
|
||||||
|
from dataclasses import dataclass
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import TYPE_CHECKING, Type
|
from typing import TYPE_CHECKING, Type
|
||||||
|
|
||||||
from .flightplan import FlightPlan
|
from .flightplan import FlightPlan, Layout
|
||||||
from .ibuilder import IBuilder
|
from .ibuilder import IBuilder
|
||||||
from ..flightwaypointtype import FlightWaypointType
|
from ..flightwaypointtype import FlightWaypointType
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..flight import Flight
|
|
||||||
from ..flightwaypoint import FlightWaypoint
|
from ..flightwaypoint import FlightWaypoint
|
||||||
|
|
||||||
|
|
||||||
class Builder(IBuilder):
|
class Builder(IBuilder):
|
||||||
def build(self) -> CustomFlightPlan:
|
def build(self) -> CustomLayout:
|
||||||
return CustomFlightPlan(self.flight, [])
|
return CustomLayout([])
|
||||||
|
|
||||||
|
|
||||||
class CustomFlightPlan(FlightPlan):
|
@dataclass(frozen=True)
|
||||||
def __init__(self, flight: Flight, waypoints: list[FlightWaypoint]) -> None:
|
class CustomLayout(Layout):
|
||||||
super().__init__(flight)
|
custom_waypoints: list[FlightWaypoint]
|
||||||
self.custom_waypoints = waypoints
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def builder_type() -> Type[Builder]:
|
|
||||||
return Builder
|
|
||||||
|
|
||||||
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
||||||
yield from self.custom_waypoints
|
yield from self.custom_waypoints
|
||||||
|
|
||||||
|
|
||||||
|
class CustomFlightPlan(FlightPlan[CustomLayout]):
|
||||||
|
@staticmethod
|
||||||
|
def builder_type() -> Type[Builder]:
|
||||||
|
return Builder
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def tot_waypoint(self) -> FlightWaypoint | None:
|
def tot_waypoint(self) -> FlightWaypoint | None:
|
||||||
target_types = (
|
target_types = (
|
||||||
|
|||||||
@ -7,7 +7,11 @@ from game.theater.theatergroundobject import (
|
|||||||
EwrGroundObject,
|
EwrGroundObject,
|
||||||
SamGroundObject,
|
SamGroundObject,
|
||||||
)
|
)
|
||||||
from .formationattack import FormationAttackBuilder, FormationAttackFlightPlan
|
from .formationattack import (
|
||||||
|
FormationAttackBuilder,
|
||||||
|
FormationAttackFlightPlan,
|
||||||
|
FormationAttackLayout,
|
||||||
|
)
|
||||||
from .invalidobjectivelocation import InvalidObjectiveLocation
|
from .invalidobjectivelocation import InvalidObjectiveLocation
|
||||||
from ..flightwaypointtype import FlightWaypointType
|
from ..flightwaypointtype import FlightWaypointType
|
||||||
|
|
||||||
@ -18,8 +22,8 @@ class DeadFlightPlan(FormationAttackFlightPlan):
|
|||||||
return Builder
|
return Builder
|
||||||
|
|
||||||
|
|
||||||
class Builder(FormationAttackBuilder[DeadFlightPlan]):
|
class Builder(FormationAttackBuilder):
|
||||||
def build(self) -> FormationAttackFlightPlan:
|
def build(self) -> FormationAttackLayout:
|
||||||
location = self.package.target
|
location = self.package.target
|
||||||
|
|
||||||
is_ewr = isinstance(location, EwrGroundObject)
|
is_ewr = isinstance(location, EwrGroundObject)
|
||||||
@ -31,4 +35,4 @@ class Builder(FormationAttackBuilder[DeadFlightPlan]):
|
|||||||
)
|
)
|
||||||
raise InvalidObjectiveLocation(self.flight.flight_type, location)
|
raise InvalidObjectiveLocation(self.flight.flight_type, location)
|
||||||
|
|
||||||
return self._build(DeadFlightPlan, FlightWaypointType.INGRESS_DEAD)
|
return self._build(FlightWaypointType.INGRESS_DEAD)
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from datetime import timedelta
|
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
from .formationattack import FormationAttackBuilder, FormationAttackFlightPlan
|
from .formationattack import (
|
||||||
|
FormationAttackBuilder,
|
||||||
|
FormationAttackFlightPlan,
|
||||||
|
FormationAttackLayout,
|
||||||
|
)
|
||||||
from .waypointbuilder import WaypointBuilder
|
from .waypointbuilder import WaypointBuilder
|
||||||
|
|
||||||
|
|
||||||
@ -13,8 +16,8 @@ class EscortFlightPlan(FormationAttackFlightPlan):
|
|||||||
return Builder
|
return Builder
|
||||||
|
|
||||||
|
|
||||||
class Builder(FormationAttackBuilder[EscortFlightPlan]):
|
class Builder(FormationAttackBuilder):
|
||||||
def build(self) -> FormationAttackFlightPlan:
|
def build(self) -> FormationAttackLayout:
|
||||||
assert self.package.waypoints is not None
|
assert self.package.waypoints is not None
|
||||||
|
|
||||||
builder = WaypointBuilder(self.flight, self.coalition)
|
builder = WaypointBuilder(self.flight, self.coalition)
|
||||||
@ -28,11 +31,9 @@ class Builder(FormationAttackBuilder[EscortFlightPlan]):
|
|||||||
if self.package.waypoints.refuel is not None:
|
if self.package.waypoints.refuel is not None:
|
||||||
refuel = builder.refuel(self.package.waypoints.refuel)
|
refuel = builder.refuel(self.package.waypoints.refuel)
|
||||||
|
|
||||||
return EscortFlightPlan(
|
return FormationAttackLayout(
|
||||||
flight=self.flight,
|
|
||||||
departure=builder.takeoff(self.flight.departure),
|
departure=builder.takeoff(self.flight.departure),
|
||||||
hold=hold,
|
hold=hold,
|
||||||
hold_duration=timedelta(minutes=5),
|
|
||||||
nav_to=builder.nav_path(
|
nav_to=builder.nav_path(
|
||||||
hold.position, join.position, self.doctrine.ingress_altitude
|
hold.position, join.position, self.doctrine.ingress_altitude
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,22 +1,22 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Iterator
|
from collections.abc import Iterator
|
||||||
|
from dataclasses import dataclass
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import TYPE_CHECKING, Type
|
from typing import TYPE_CHECKING, Type
|
||||||
|
|
||||||
from game.utils import feet
|
from game.utils import feet
|
||||||
from .ibuilder import IBuilder
|
from .ibuilder import IBuilder
|
||||||
from .planningerror import PlanningError
|
from .planningerror import PlanningError
|
||||||
from .standard import StandardFlightPlan
|
from .standard import StandardFlightPlan, StandardLayout
|
||||||
from .waypointbuilder import WaypointBuilder
|
from .waypointbuilder import WaypointBuilder
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..flight import Flight
|
|
||||||
from ..flightwaypoint import FlightWaypoint
|
from ..flightwaypoint import FlightWaypoint
|
||||||
|
|
||||||
|
|
||||||
class Builder(IBuilder):
|
class Builder(IBuilder):
|
||||||
def build(self) -> FerryFlightPlan:
|
def build(self) -> FerryLayout:
|
||||||
if self.flight.departure == self.flight.arrival:
|
if self.flight.departure == self.flight.arrival:
|
||||||
raise PlanningError(
|
raise PlanningError(
|
||||||
f"Cannot plan ferry self.flight: departure and arrival are both "
|
f"Cannot plan ferry self.flight: departure and arrival are both "
|
||||||
@ -31,8 +31,7 @@ class Builder(IBuilder):
|
|||||||
)
|
)
|
||||||
|
|
||||||
builder = WaypointBuilder(self.flight, self.coalition)
|
builder = WaypointBuilder(self.flight, self.coalition)
|
||||||
return FerryFlightPlan(
|
return FerryLayout(
|
||||||
flight=self.flight,
|
|
||||||
departure=builder.takeoff(self.flight.departure),
|
departure=builder.takeoff(self.flight.departure),
|
||||||
nav_to_destination=builder.nav_path(
|
nav_to_destination=builder.nav_path(
|
||||||
self.flight.departure.position,
|
self.flight.departure.position,
|
||||||
@ -46,22 +45,9 @@ class Builder(IBuilder):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class FerryFlightPlan(StandardFlightPlan):
|
@dataclass(frozen=True)
|
||||||
def __init__(
|
class FerryLayout(StandardLayout):
|
||||||
self,
|
nav_to_destination: list[FlightWaypoint]
|
||||||
flight: Flight,
|
|
||||||
departure: FlightWaypoint,
|
|
||||||
arrival: FlightWaypoint,
|
|
||||||
divert: FlightWaypoint | None,
|
|
||||||
bullseye: FlightWaypoint,
|
|
||||||
nav_to_destination: list[FlightWaypoint],
|
|
||||||
) -> None:
|
|
||||||
super().__init__(flight, departure, arrival, divert, bullseye)
|
|
||||||
self.nav_to_destination = nav_to_destination
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def builder_type() -> Type[Builder]:
|
|
||||||
return Builder
|
|
||||||
|
|
||||||
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
||||||
yield self.departure
|
yield self.departure
|
||||||
@ -71,9 +57,15 @@ class FerryFlightPlan(StandardFlightPlan):
|
|||||||
yield self.divert
|
yield self.divert
|
||||||
yield self.bullseye
|
yield self.bullseye
|
||||||
|
|
||||||
|
|
||||||
|
class FerryFlightPlan(StandardFlightPlan[FerryLayout]):
|
||||||
|
@staticmethod
|
||||||
|
def builder_type() -> Type[Builder]:
|
||||||
|
return Builder
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def tot_waypoint(self) -> FlightWaypoint | None:
|
def tot_waypoint(self) -> FlightWaypoint | None:
|
||||||
return self.arrival
|
return self.layout.arrival
|
||||||
|
|
||||||
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
|
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
|
||||||
# TOT planning isn't really useful for ferries. They're behind the front
|
# TOT planning isn't really useful for ferries. They're behind the front
|
||||||
|
|||||||
@ -12,7 +12,7 @@ from abc import ABC, abstractmethod
|
|||||||
from collections.abc import Iterator
|
from collections.abc import Iterator
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
from typing import TYPE_CHECKING, Type, TypeGuard
|
from typing import Any, Generic, TYPE_CHECKING, Type, TypeGuard, TypeVar
|
||||||
|
|
||||||
from game.typeguard import self_type_guard
|
from game.typeguard import self_type_guard
|
||||||
from game.utils import Distance, Speed, meters
|
from game.utils import Distance, Speed, meters
|
||||||
@ -41,9 +41,24 @@ INGRESS_TYPES = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class FlightPlan(ABC):
|
class Layout(ABC):
|
||||||
def __init__(self, flight: Flight) -> None:
|
@property
|
||||||
|
def waypoints(self) -> list[FlightWaypoint]:
|
||||||
|
"""A list of all waypoints in the flight plan, in order."""
|
||||||
|
return list(self.iter_waypoints())
|
||||||
|
|
||||||
|
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
||||||
|
"""Iterates over all waypoints in the flight plan, in order."""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
LayoutT = TypeVar("LayoutT", bound=Layout)
|
||||||
|
|
||||||
|
|
||||||
|
class FlightPlan(ABC, Generic[LayoutT]):
|
||||||
|
def __init__(self, flight: Flight, layout: LayoutT) -> None:
|
||||||
self.flight = flight
|
self.flight = flight
|
||||||
|
self.layout = layout
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def package(self) -> Package:
|
def package(self) -> Package:
|
||||||
@ -61,7 +76,7 @@ class FlightPlan(ABC):
|
|||||||
|
|
||||||
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
||||||
"""Iterates over all waypoints in the flight plan, in order."""
|
"""Iterates over all waypoints in the flight plan, in order."""
|
||||||
raise NotImplementedError
|
yield from self.layout.iter_waypoints()
|
||||||
|
|
||||||
def edges(
|
def edges(
|
||||||
self, until: FlightWaypoint | None = None
|
self, until: FlightWaypoint | None = None
|
||||||
@ -296,13 +311,17 @@ class FlightPlan(ABC):
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@self_type_guard
|
@self_type_guard
|
||||||
def is_loiter(self, flight_plan: FlightPlan) -> TypeGuard[LoiterFlightPlan]:
|
def is_loiter(self, flight_plan: FlightPlan[Any]) -> TypeGuard[LoiterFlightPlan]:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@self_type_guard
|
@self_type_guard
|
||||||
def is_patrol(self, flight_plan: FlightPlan) -> TypeGuard[PatrollingFlightPlan]:
|
def is_patrol(
|
||||||
|
self, flight_plan: FlightPlan[Any]
|
||||||
|
) -> TypeGuard[PatrollingFlightPlan[Any]]:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@self_type_guard
|
@self_type_guard
|
||||||
def is_formation(self, flight_plan: FlightPlan) -> TypeGuard[FormationFlightPlan]:
|
def is_formation(
|
||||||
|
self, flight_plan: FlightPlan[Any]
|
||||||
|
) -> TypeGuard[FormationFlightPlan]:
|
||||||
return False
|
return False
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Type
|
from typing import Any, TYPE_CHECKING, Type
|
||||||
|
|
||||||
from game.ato import FlightType
|
from game.ato import FlightType
|
||||||
from game.ato.closestairfields import ObjectiveDistanceCache
|
from game.ato.closestairfields import ObjectiveDistanceCache
|
||||||
@ -82,8 +82,8 @@ class FlightPlanBuilder:
|
|||||||
f"{flight.departure} to {flight.package.target}"
|
f"{flight.departure} to {flight.package.target}"
|
||||||
) from ex
|
) from ex
|
||||||
|
|
||||||
def plan_type(self, task: FlightType) -> Type[FlightPlan] | None:
|
def plan_type(self, task: FlightType) -> Type[FlightPlan[Any]] | None:
|
||||||
plan_type: Type[FlightPlan]
|
plan_type: Type[FlightPlan[Any]]
|
||||||
if task == FlightType.REFUELING:
|
if task == FlightType.REFUELING:
|
||||||
if self.package.target.is_friendly(self.is_player) or isinstance(
|
if self.package.target.is_friendly(self.is_player) or isinstance(
|
||||||
self.package.target, FrontLine
|
self.package.target, FrontLine
|
||||||
@ -91,7 +91,7 @@ class FlightPlanBuilder:
|
|||||||
return TheaterRefuelingFlightPlan
|
return TheaterRefuelingFlightPlan
|
||||||
return PackageRefuelingFlightPlan
|
return PackageRefuelingFlightPlan
|
||||||
|
|
||||||
plan_dict: dict[FlightType, Type[FlightPlan]] = {
|
plan_dict: dict[FlightType, Type[FlightPlan[Any]]] = {
|
||||||
FlightType.ANTISHIP: AntiShipFlightPlan,
|
FlightType.ANTISHIP: AntiShipFlightPlan,
|
||||||
FlightType.BAI: BaiFlightPlan,
|
FlightType.BAI: BaiFlightPlan,
|
||||||
FlightType.BARCAP: BarCapFlightPlan,
|
FlightType.BARCAP: BarCapFlightPlan,
|
||||||
@ -111,13 +111,14 @@ class FlightPlanBuilder:
|
|||||||
}
|
}
|
||||||
return plan_dict.get(task)
|
return plan_dict.get(task)
|
||||||
|
|
||||||
def generate_flight_plan(self, flight: Flight) -> FlightPlan:
|
def generate_flight_plan(self, flight: Flight) -> FlightPlan[Any]:
|
||||||
plan_type = self.plan_type(flight.flight_type)
|
plan_type = self.plan_type(flight.flight_type)
|
||||||
if plan_type is None:
|
if plan_type is None:
|
||||||
raise PlanningError(
|
raise PlanningError(
|
||||||
f"{flight.flight_type} flight plan generation not implemented"
|
f"{flight.flight_type} flight plan generation not implemented"
|
||||||
)
|
)
|
||||||
return plan_type.builder_type()(flight, self.theater).build()
|
layout = plan_type.builder_type()(flight, self.theater).build()
|
||||||
|
return plan_type(flight, layout)
|
||||||
|
|
||||||
def regenerate_flight_plans(self) -> None:
|
def regenerate_flight_plans(self) -> None:
|
||||||
new_flights: list[Flight] = []
|
new_flights: list[Flight] = []
|
||||||
|
|||||||
@ -1,52 +1,31 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
|
from dataclasses import dataclass
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
from typing import TYPE_CHECKING, TypeGuard
|
from typing import Any, TYPE_CHECKING, TypeGuard
|
||||||
|
|
||||||
from game.typeguard import self_type_guard
|
from game.typeguard import self_type_guard
|
||||||
from game.utils import Speed
|
from game.utils import Speed
|
||||||
from .flightplan import FlightPlan
|
from .flightplan import FlightPlan
|
||||||
from .loiter import LoiterFlightPlan
|
from .loiter import LoiterFlightPlan, LoiterLayout
|
||||||
from ..traveltime import GroundSpeed, TravelTime
|
from ..traveltime import GroundSpeed, TravelTime
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..flight import Flight
|
|
||||||
from ..flightwaypoint import FlightWaypoint
|
from ..flightwaypoint import FlightWaypoint
|
||||||
|
|
||||||
|
|
||||||
class FormationFlightPlan(LoiterFlightPlan, ABC):
|
@dataclass(frozen=True)
|
||||||
def __init__(
|
class FormationLayout(LoiterLayout, ABC):
|
||||||
self,
|
nav_to: list[FlightWaypoint]
|
||||||
flight: Flight,
|
join: FlightWaypoint
|
||||||
departure: FlightWaypoint,
|
split: FlightWaypoint
|
||||||
arrival: FlightWaypoint,
|
refuel: FlightWaypoint
|
||||||
divert: FlightWaypoint | None,
|
nav_from: list[FlightWaypoint]
|
||||||
bullseye: FlightWaypoint,
|
|
||||||
nav_to: list[FlightWaypoint],
|
|
||||||
nav_from: list[FlightWaypoint],
|
|
||||||
hold: FlightWaypoint,
|
|
||||||
hold_duration: timedelta,
|
|
||||||
join: FlightWaypoint,
|
|
||||||
split: FlightWaypoint,
|
|
||||||
refuel: FlightWaypoint,
|
|
||||||
) -> None:
|
|
||||||
super().__init__(
|
|
||||||
flight,
|
|
||||||
departure,
|
|
||||||
arrival,
|
|
||||||
divert,
|
|
||||||
bullseye,
|
|
||||||
nav_to,
|
|
||||||
nav_from,
|
|
||||||
hold,
|
|
||||||
hold_duration,
|
|
||||||
)
|
|
||||||
self.join = join
|
|
||||||
self.split = split
|
|
||||||
self.refuel = refuel
|
|
||||||
|
|
||||||
|
|
||||||
|
class FormationFlightPlan(LoiterFlightPlan, ABC):
|
||||||
@property
|
@property
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def package_speed_waypoints(self) -> set[FlightWaypoint]:
|
def package_speed_waypoints(self) -> set[FlightWaypoint]:
|
||||||
@ -57,10 +36,10 @@ class FormationFlightPlan(LoiterFlightPlan, ABC):
|
|||||||
return self.package_speed_waypoints
|
return self.package_speed_waypoints
|
||||||
|
|
||||||
def request_escort_at(self) -> FlightWaypoint | None:
|
def request_escort_at(self) -> FlightWaypoint | None:
|
||||||
return self.join
|
return self.layout.join
|
||||||
|
|
||||||
def dismiss_escort_at(self) -> FlightWaypoint | None:
|
def dismiss_escort_at(self) -> FlightWaypoint | None:
|
||||||
return self.split
|
return self.layout.split
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def best_flight_formation_speed(self) -> Speed:
|
def best_flight_formation_speed(self) -> Speed:
|
||||||
@ -90,7 +69,7 @@ class FormationFlightPlan(LoiterFlightPlan, ABC):
|
|||||||
@property
|
@property
|
||||||
def travel_time_to_rendezvous(self) -> timedelta:
|
def travel_time_to_rendezvous(self) -> timedelta:
|
||||||
"""The estimated time between the first waypoint and the join point."""
|
"""The estimated time between the first waypoint and the join point."""
|
||||||
return self._travel_time_to_waypoint(self.join)
|
return self._travel_time_to_waypoint(self.layout.join)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
@ -103,18 +82,18 @@ class FormationFlightPlan(LoiterFlightPlan, ABC):
|
|||||||
...
|
...
|
||||||
|
|
||||||
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
|
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
|
||||||
if waypoint == self.join:
|
if waypoint == self.layout.join:
|
||||||
return self.join_time
|
return self.join_time
|
||||||
elif waypoint == self.split:
|
elif waypoint == self.layout.split:
|
||||||
return self.split_time
|
return self.split_time
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def push_time(self) -> timedelta:
|
def push_time(self) -> timedelta:
|
||||||
return self.join_time - TravelTime.between_points(
|
return self.join_time - TravelTime.between_points(
|
||||||
self.hold.position,
|
self.layout.hold.position,
|
||||||
self.join.position,
|
self.layout.join.position,
|
||||||
GroundSpeed.for_flight(self.flight, self.hold.alt),
|
GroundSpeed.for_flight(self.flight, self.layout.hold.alt),
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -122,5 +101,7 @@ class FormationFlightPlan(LoiterFlightPlan, ABC):
|
|||||||
return self.split_time
|
return self.split_time
|
||||||
|
|
||||||
@self_type_guard
|
@self_type_guard
|
||||||
def is_formation(self, flight_plan: FlightPlan) -> TypeGuard[FormationFlightPlan]:
|
def is_formation(
|
||||||
|
self, flight_plan: FlightPlan[Any]
|
||||||
|
) -> TypeGuard[FormationFlightPlan]:
|
||||||
return True
|
return True
|
||||||
|
|||||||
@ -2,15 +2,16 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from abc import ABC
|
from abc import ABC
|
||||||
from collections.abc import Iterator
|
from collections.abc import Iterator
|
||||||
|
from dataclasses import dataclass
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import Generic, TYPE_CHECKING, Type, TypeVar
|
from typing import TYPE_CHECKING, TypeVar
|
||||||
|
|
||||||
from dcs import Point
|
from dcs import Point
|
||||||
|
|
||||||
from game.flightplan import HoldZoneGeometry
|
from game.flightplan import HoldZoneGeometry
|
||||||
from game.theater import MissionTarget
|
from game.theater import MissionTarget
|
||||||
from game.utils import Speed, meters
|
from game.utils import Speed, meters
|
||||||
from .formation import FormationFlightPlan
|
from .formation import FormationFlightPlan, FormationLayout
|
||||||
from .ibuilder import IBuilder
|
from .ibuilder import IBuilder
|
||||||
from .planningerror import PlanningError
|
from .planningerror import PlanningError
|
||||||
from .waypointbuilder import StrikeTarget, WaypointBuilder
|
from .waypointbuilder import StrikeTarget, WaypointBuilder
|
||||||
@ -23,64 +24,16 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class FormationAttackFlightPlan(FormationFlightPlan, ABC):
|
class FormationAttackFlightPlan(FormationFlightPlan, ABC):
|
||||||
def __init__(
|
@property
|
||||||
self,
|
def lead_time(self) -> timedelta:
|
||||||
flight: Flight,
|
return timedelta()
|
||||||
departure: FlightWaypoint,
|
|
||||||
arrival: FlightWaypoint,
|
|
||||||
divert: FlightWaypoint | None,
|
|
||||||
bullseye: FlightWaypoint,
|
|
||||||
nav_to: list[FlightWaypoint],
|
|
||||||
nav_from: list[FlightWaypoint],
|
|
||||||
hold: FlightWaypoint,
|
|
||||||
hold_duration: timedelta,
|
|
||||||
join: FlightWaypoint,
|
|
||||||
split: FlightWaypoint,
|
|
||||||
refuel: FlightWaypoint,
|
|
||||||
ingress: FlightWaypoint,
|
|
||||||
targets: list[FlightWaypoint],
|
|
||||||
lead_time: timedelta = timedelta(),
|
|
||||||
) -> None:
|
|
||||||
super().__init__(
|
|
||||||
flight,
|
|
||||||
departure,
|
|
||||||
arrival,
|
|
||||||
divert,
|
|
||||||
bullseye,
|
|
||||||
nav_to,
|
|
||||||
nav_from,
|
|
||||||
hold,
|
|
||||||
hold_duration,
|
|
||||||
join,
|
|
||||||
split,
|
|
||||||
refuel,
|
|
||||||
)
|
|
||||||
self.ingress = ingress
|
|
||||||
self.targets = targets
|
|
||||||
self.lead_time = lead_time
|
|
||||||
|
|
||||||
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
|
||||||
yield self.departure
|
|
||||||
yield self.hold
|
|
||||||
yield from self.nav_to
|
|
||||||
yield self.join
|
|
||||||
yield self.ingress
|
|
||||||
yield from self.targets
|
|
||||||
yield self.split
|
|
||||||
if self.refuel is not None:
|
|
||||||
yield self.refuel
|
|
||||||
yield from self.nav_from
|
|
||||||
yield self.arrival
|
|
||||||
if self.divert is not None:
|
|
||||||
yield self.divert
|
|
||||||
yield self.bullseye
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def package_speed_waypoints(self) -> set[FlightWaypoint]:
|
def package_speed_waypoints(self) -> set[FlightWaypoint]:
|
||||||
return {
|
return {
|
||||||
self.ingress,
|
self.layout.ingress,
|
||||||
self.split,
|
self.layout.split,
|
||||||
} | set(self.targets)
|
} | set(self.layout.targets)
|
||||||
|
|
||||||
def speed_between_waypoints(self, a: FlightWaypoint, b: FlightWaypoint) -> Speed:
|
def speed_between_waypoints(self, a: FlightWaypoint, b: FlightWaypoint) -> Speed:
|
||||||
# FlightWaypoint is only comparable by identity, so adding
|
# FlightWaypoint is only comparable by identity, so adding
|
||||||
@ -94,7 +47,7 @@ class FormationAttackFlightPlan(FormationFlightPlan, ABC):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def tot_waypoint(self) -> FlightWaypoint:
|
def tot_waypoint(self) -> FlightWaypoint:
|
||||||
return self.targets[0]
|
return self.layout.targets[0]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def tot_offset(self) -> timedelta:
|
def tot_offset(self) -> timedelta:
|
||||||
@ -138,18 +91,20 @@ class FormationAttackFlightPlan(FormationFlightPlan, ABC):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def join_time(self) -> timedelta:
|
def join_time(self) -> timedelta:
|
||||||
travel_time = self.travel_time_between_waypoints(self.join, self.ingress)
|
travel_time = self.travel_time_between_waypoints(
|
||||||
|
self.layout.join, self.layout.ingress
|
||||||
|
)
|
||||||
return self.ingress_time - travel_time
|
return self.ingress_time - travel_time
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def split_time(self) -> timedelta:
|
def split_time(self) -> timedelta:
|
||||||
travel_time_ingress = self.travel_time_between_waypoints(
|
travel_time_ingress = self.travel_time_between_waypoints(
|
||||||
self.ingress, self.target_area_waypoint
|
self.layout.ingress, self.target_area_waypoint
|
||||||
)
|
)
|
||||||
travel_time_egress = self.travel_time_between_waypoints(
|
travel_time_egress = self.travel_time_between_waypoints(
|
||||||
self.target_area_waypoint, self.split
|
self.target_area_waypoint, self.layout.split
|
||||||
)
|
)
|
||||||
minutes_at_target = 0.75 * len(self.targets)
|
minutes_at_target = 0.75 * len(self.layout.targets)
|
||||||
timedelta_at_target = timedelta(minutes=minutes_at_target)
|
timedelta_at_target = timedelta(minutes=minutes_at_target)
|
||||||
return (
|
return (
|
||||||
self.ingress_time
|
self.ingress_time
|
||||||
@ -162,29 +117,49 @@ class FormationAttackFlightPlan(FormationFlightPlan, ABC):
|
|||||||
def ingress_time(self) -> timedelta:
|
def ingress_time(self) -> timedelta:
|
||||||
tot = self.tot
|
tot = self.tot
|
||||||
travel_time = self.travel_time_between_waypoints(
|
travel_time = self.travel_time_between_waypoints(
|
||||||
self.ingress, self.target_area_waypoint
|
self.layout.ingress, self.target_area_waypoint
|
||||||
)
|
)
|
||||||
return tot - travel_time
|
return tot - travel_time
|
||||||
|
|
||||||
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
|
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
|
||||||
if waypoint == self.ingress:
|
if waypoint == self.layout.ingress:
|
||||||
return self.ingress_time
|
return self.ingress_time
|
||||||
elif waypoint in self.targets:
|
elif waypoint in self.layout.targets:
|
||||||
return self.tot
|
return self.tot
|
||||||
return super().tot_for_waypoint(waypoint)
|
return super().tot_for_waypoint(waypoint)
|
||||||
|
|
||||||
|
|
||||||
FlightPlanT = TypeVar("FlightPlanT", bound=FormationAttackFlightPlan)
|
@dataclass(frozen=True)
|
||||||
|
class FormationAttackLayout(FormationLayout):
|
||||||
|
ingress: FlightWaypoint
|
||||||
|
targets: list[FlightWaypoint]
|
||||||
|
|
||||||
|
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
||||||
|
yield self.departure
|
||||||
|
yield self.hold
|
||||||
|
yield from self.nav_to
|
||||||
|
yield self.join
|
||||||
|
yield self.ingress
|
||||||
|
yield from self.targets
|
||||||
|
yield self.split
|
||||||
|
if self.refuel is not None:
|
||||||
|
yield self.refuel
|
||||||
|
yield from self.nav_from
|
||||||
|
yield self.arrival
|
||||||
|
if self.divert is not None:
|
||||||
|
yield self.divert
|
||||||
|
yield self.bullseye
|
||||||
|
|
||||||
|
|
||||||
class FormationAttackBuilder(IBuilder, ABC, Generic[FlightPlanT]):
|
LayoutT = TypeVar("LayoutT", bound=FormationAttackLayout)
|
||||||
|
|
||||||
|
|
||||||
|
class FormationAttackBuilder(IBuilder, ABC):
|
||||||
def _build(
|
def _build(
|
||||||
self,
|
self,
|
||||||
plan_type: Type[FlightPlanT],
|
|
||||||
ingress_type: FlightWaypointType,
|
ingress_type: FlightWaypointType,
|
||||||
targets: list[StrikeTarget] | None = None,
|
targets: list[StrikeTarget] | None = None,
|
||||||
lead_time: timedelta = timedelta(),
|
) -> FormationAttackLayout:
|
||||||
) -> FlightPlanT:
|
|
||||||
assert self.package.waypoints is not None
|
assert self.package.waypoints is not None
|
||||||
builder = WaypointBuilder(self.flight, self.coalition, targets)
|
builder = WaypointBuilder(self.flight, self.coalition, targets)
|
||||||
|
|
||||||
@ -208,11 +183,9 @@ class FormationAttackBuilder(IBuilder, ABC, Generic[FlightPlanT]):
|
|||||||
if self.package.waypoints.refuel is not None:
|
if self.package.waypoints.refuel is not None:
|
||||||
refuel = builder.refuel(self.package.waypoints.refuel)
|
refuel = builder.refuel(self.package.waypoints.refuel)
|
||||||
|
|
||||||
return plan_type(
|
return FormationAttackLayout(
|
||||||
flight=self.flight,
|
|
||||||
departure=builder.takeoff(self.flight.departure),
|
departure=builder.takeoff(self.flight.departure),
|
||||||
hold=hold,
|
hold=hold,
|
||||||
hold_duration=timedelta(minutes=5),
|
|
||||||
nav_to=builder.nav_path(
|
nav_to=builder.nav_path(
|
||||||
hold.position, join.position, self.doctrine.ingress_altitude
|
hold.position, join.position, self.doctrine.ingress_altitude
|
||||||
),
|
),
|
||||||
@ -231,7 +204,6 @@ class FormationAttackBuilder(IBuilder, ABC, Generic[FlightPlanT]):
|
|||||||
arrival=builder.land(self.flight.arrival),
|
arrival=builder.land(self.flight.arrival),
|
||||||
divert=builder.divert(self.flight.divert),
|
divert=builder.divert(self.flight.divert),
|
||||||
bullseye=builder.bullseye(),
|
bullseye=builder.bullseye(),
|
||||||
lead_time=lead_time,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@ -10,7 +10,7 @@ if TYPE_CHECKING:
|
|||||||
from game.threatzones import ThreatZones
|
from game.threatzones import ThreatZones
|
||||||
from ..flight import Flight
|
from ..flight import Flight
|
||||||
from ..package import Package
|
from ..package import Package
|
||||||
from .flightplan import FlightPlan
|
from .flightplan import Layout
|
||||||
|
|
||||||
|
|
||||||
class IBuilder(ABC):
|
class IBuilder(ABC):
|
||||||
@ -19,7 +19,7 @@ class IBuilder(ABC):
|
|||||||
self.theater = theater
|
self.theater = theater
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def build(self) -> FlightPlan:
|
def build(self) -> Layout:
|
||||||
...
|
...
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@ -1,36 +1,27 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
|
from dataclasses import dataclass
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import TYPE_CHECKING, TypeGuard
|
from typing import Any, TYPE_CHECKING, TypeGuard
|
||||||
|
|
||||||
from game.typeguard import self_type_guard
|
from game.typeguard import self_type_guard
|
||||||
from .flightplan import FlightPlan
|
from .flightplan import FlightPlan
|
||||||
from .standard import StandardFlightPlan
|
from .standard import StandardFlightPlan, StandardLayout
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..flight import Flight
|
|
||||||
from ..flightwaypoint import FlightWaypoint
|
from ..flightwaypoint import FlightWaypoint
|
||||||
|
|
||||||
|
|
||||||
class LoiterFlightPlan(StandardFlightPlan, ABC):
|
@dataclass(frozen=True)
|
||||||
def __init__(
|
class LoiterLayout(StandardLayout, ABC):
|
||||||
self,
|
hold: FlightWaypoint
|
||||||
flight: Flight,
|
|
||||||
departure: FlightWaypoint,
|
|
||||||
arrival: FlightWaypoint,
|
class LoiterFlightPlan(StandardFlightPlan[Any], ABC):
|
||||||
divert: FlightWaypoint | None,
|
@property
|
||||||
bullseye: FlightWaypoint,
|
def hold_duration(self) -> timedelta:
|
||||||
nav_to: list[FlightWaypoint],
|
return timedelta(minutes=5)
|
||||||
nav_from: list[FlightWaypoint],
|
|
||||||
hold: FlightWaypoint,
|
|
||||||
hold_duration: timedelta,
|
|
||||||
) -> None:
|
|
||||||
super().__init__(flight, departure, arrival, divert, bullseye)
|
|
||||||
self.nav_to = nav_to
|
|
||||||
self.nav_from = nav_from
|
|
||||||
self.hold = hold
|
|
||||||
self.hold_duration = hold_duration
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
@ -38,7 +29,7 @@ class LoiterFlightPlan(StandardFlightPlan, ABC):
|
|||||||
...
|
...
|
||||||
|
|
||||||
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
|
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
|
||||||
if waypoint == self.hold:
|
if waypoint == self.layout.hold:
|
||||||
return self.push_time
|
return self.push_time
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -46,10 +37,10 @@ class LoiterFlightPlan(StandardFlightPlan, ABC):
|
|||||||
self, a: FlightWaypoint, b: FlightWaypoint
|
self, a: FlightWaypoint, b: FlightWaypoint
|
||||||
) -> timedelta:
|
) -> timedelta:
|
||||||
travel_time = super().travel_time_between_waypoints(a, b)
|
travel_time = super().travel_time_between_waypoints(a, b)
|
||||||
if a != self.hold:
|
if a != self.layout.hold:
|
||||||
return travel_time
|
return travel_time
|
||||||
return travel_time + self.hold_duration
|
return travel_time + self.hold_duration
|
||||||
|
|
||||||
@self_type_guard
|
@self_type_guard
|
||||||
def is_loiter(self, flight_plan: FlightPlan) -> TypeGuard[LoiterFlightPlan]:
|
def is_loiter(self, flight_plan: FlightPlan[Any]) -> TypeGuard[LoiterFlightPlan]:
|
||||||
return True
|
return True
|
||||||
|
|||||||
@ -4,7 +4,11 @@ import logging
|
|||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
from game.theater import Airfield
|
from game.theater import Airfield
|
||||||
from .formationattack import FormationAttackBuilder, FormationAttackFlightPlan
|
from .formationattack import (
|
||||||
|
FormationAttackBuilder,
|
||||||
|
FormationAttackFlightPlan,
|
||||||
|
FormationAttackLayout,
|
||||||
|
)
|
||||||
from .invalidobjectivelocation import InvalidObjectiveLocation
|
from .invalidobjectivelocation import InvalidObjectiveLocation
|
||||||
from ..flightwaypointtype import FlightWaypointType
|
from ..flightwaypointtype import FlightWaypointType
|
||||||
|
|
||||||
@ -15,8 +19,8 @@ class OcaAircraftFlightPlan(FormationAttackFlightPlan):
|
|||||||
return Builder
|
return Builder
|
||||||
|
|
||||||
|
|
||||||
class Builder(FormationAttackBuilder[OcaAircraftFlightPlan]):
|
class Builder(FormationAttackBuilder):
|
||||||
def build(self) -> FormationAttackFlightPlan:
|
def build(self) -> FormationAttackLayout:
|
||||||
location = self.package.target
|
location = self.package.target
|
||||||
|
|
||||||
if not isinstance(location, Airfield):
|
if not isinstance(location, Airfield):
|
||||||
@ -26,6 +30,4 @@ class Builder(FormationAttackBuilder[OcaAircraftFlightPlan]):
|
|||||||
)
|
)
|
||||||
raise InvalidObjectiveLocation(self.flight.flight_type, location)
|
raise InvalidObjectiveLocation(self.flight.flight_type, location)
|
||||||
|
|
||||||
return self._build(
|
return self._build(FlightWaypointType.INGRESS_OCA_AIRCRAFT)
|
||||||
OcaAircraftFlightPlan, FlightWaypointType.INGRESS_OCA_AIRCRAFT
|
|
||||||
)
|
|
||||||
|
|||||||
@ -4,7 +4,11 @@ import logging
|
|||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
from game.theater import Airfield
|
from game.theater import Airfield
|
||||||
from .formationattack import FormationAttackBuilder, FormationAttackFlightPlan
|
from .formationattack import (
|
||||||
|
FormationAttackBuilder,
|
||||||
|
FormationAttackFlightPlan,
|
||||||
|
FormationAttackLayout,
|
||||||
|
)
|
||||||
from .invalidobjectivelocation import InvalidObjectiveLocation
|
from .invalidobjectivelocation import InvalidObjectiveLocation
|
||||||
from ..flightwaypointtype import FlightWaypointType
|
from ..flightwaypointtype import FlightWaypointType
|
||||||
|
|
||||||
@ -15,8 +19,8 @@ class OcaRunwayFlightPlan(FormationAttackFlightPlan):
|
|||||||
return Builder
|
return Builder
|
||||||
|
|
||||||
|
|
||||||
class Builder(FormationAttackBuilder[OcaRunwayFlightPlan]):
|
class Builder(FormationAttackBuilder):
|
||||||
def build(self) -> FormationAttackFlightPlan:
|
def build(self) -> FormationAttackLayout:
|
||||||
location = self.package.target
|
location = self.package.target
|
||||||
|
|
||||||
if not isinstance(location, Airfield):
|
if not isinstance(location, Airfield):
|
||||||
@ -26,4 +30,4 @@ class Builder(FormationAttackBuilder[OcaRunwayFlightPlan]):
|
|||||||
)
|
)
|
||||||
raise InvalidObjectiveLocation(self.flight.flight_type, location)
|
raise InvalidObjectiveLocation(self.flight.flight_type, location)
|
||||||
|
|
||||||
return self._build(OcaRunwayFlightPlan, FlightWaypointType.INGRESS_OCA_RUNWAY)
|
return self._build(FlightWaypointType.INGRESS_OCA_RUNWAY)
|
||||||
|
|||||||
@ -5,7 +5,8 @@ from typing import Type
|
|||||||
|
|
||||||
from dcs import Point
|
from dcs import Point
|
||||||
|
|
||||||
from game.utils import Distance, Heading, feet, knots, meters
|
from game.utils import Distance, Heading, feet, meters
|
||||||
|
from .patrolling import PatrollingLayout
|
||||||
from .theaterrefueling import (
|
from .theaterrefueling import (
|
||||||
Builder as TheaterRefuelingBuilder,
|
Builder as TheaterRefuelingBuilder,
|
||||||
TheaterRefuelingFlightPlan,
|
TheaterRefuelingFlightPlan,
|
||||||
@ -16,18 +17,11 @@ from ..flightwaypointtype import FlightWaypointType
|
|||||||
|
|
||||||
|
|
||||||
class Builder(TheaterRefuelingBuilder):
|
class Builder(TheaterRefuelingBuilder):
|
||||||
def build(self) -> PackageRefuelingFlightPlan:
|
def build(self) -> PatrollingLayout:
|
||||||
package_waypoints = self.package.waypoints
|
package_waypoints = self.package.waypoints
|
||||||
assert package_waypoints is not None
|
assert package_waypoints is not None
|
||||||
|
|
||||||
racetrack_half_distance = Distance.from_nautical_miles(20).meters
|
racetrack_half_distance = Distance.from_nautical_miles(20).meters
|
||||||
# TODO: Only consider aircraft that can refuel with this tanker type.
|
|
||||||
refuel_time_minutes = 5
|
|
||||||
for self.flight in self.package.flights:
|
|
||||||
flight_size = self.flight.roster.max_size
|
|
||||||
refuel_time_minutes = refuel_time_minutes + 4 * flight_size + 1
|
|
||||||
|
|
||||||
patrol_duration = timedelta(minutes=refuel_time_minutes)
|
|
||||||
|
|
||||||
racetrack_center = package_waypoints.refuel
|
racetrack_center = package_waypoints.refuel
|
||||||
|
|
||||||
@ -52,17 +46,9 @@ class Builder(TheaterRefuelingBuilder):
|
|||||||
else:
|
else:
|
||||||
altitude = feet(21000)
|
altitude = feet(21000)
|
||||||
|
|
||||||
# TODO: Could use self.flight.unit_type.preferred_patrol_speed(altitude).
|
|
||||||
if tanker_type.patrol_speed is not None:
|
|
||||||
speed = tanker_type.patrol_speed
|
|
||||||
else:
|
|
||||||
# ~280 knots IAS at 21000.
|
|
||||||
speed = knots(400)
|
|
||||||
|
|
||||||
racetrack = builder.race_track(racetrack_start, racetrack_end, altitude)
|
racetrack = builder.race_track(racetrack_start, racetrack_end, altitude)
|
||||||
|
|
||||||
return PackageRefuelingFlightPlan(
|
return PatrollingLayout(
|
||||||
flight=self.flight,
|
|
||||||
departure=builder.takeoff(self.flight.departure),
|
departure=builder.takeoff(self.flight.departure),
|
||||||
nav_to=builder.nav_path(
|
nav_to=builder.nav_path(
|
||||||
self.flight.departure.position, racetrack_start, altitude
|
self.flight.departure.position, racetrack_start, altitude
|
||||||
@ -75,11 +61,6 @@ class Builder(TheaterRefuelingBuilder):
|
|||||||
arrival=builder.land(self.flight.arrival),
|
arrival=builder.land(self.flight.arrival),
|
||||||
divert=builder.divert(self.flight.divert),
|
divert=builder.divert(self.flight.divert),
|
||||||
bullseye=builder.bullseye(),
|
bullseye=builder.bullseye(),
|
||||||
patrol_duration=patrol_duration,
|
|
||||||
patrol_speed=speed,
|
|
||||||
# TODO: Factor out a common base of the combat and non-combat race-tracks.
|
|
||||||
# No harm in setting this, but we ought to clean up a bit.
|
|
||||||
engagement_distance=meters(0),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -88,6 +69,16 @@ class PackageRefuelingFlightPlan(TheaterRefuelingFlightPlan):
|
|||||||
def builder_type() -> Type[Builder]:
|
def builder_type() -> Type[Builder]:
|
||||||
return Builder
|
return Builder
|
||||||
|
|
||||||
|
@property
|
||||||
|
def patrol_duration(self) -> timedelta:
|
||||||
|
# TODO: Only consider aircraft that can refuel with this tanker type.
|
||||||
|
refuel_time_minutes = 5
|
||||||
|
for self.flight in self.package.flights:
|
||||||
|
flight_size = self.flight.roster.max_size
|
||||||
|
refuel_time_minutes = refuel_time_minutes + 4 * flight_size + 1
|
||||||
|
|
||||||
|
return timedelta(minutes=refuel_time_minutes)
|
||||||
|
|
||||||
def target_area_waypoint(self) -> FlightWaypoint:
|
def target_area_waypoint(self) -> FlightWaypoint:
|
||||||
return FlightWaypoint(
|
return FlightWaypoint(
|
||||||
"TARGET AREA",
|
"TARGET AREA",
|
||||||
|
|||||||
@ -1,53 +1,63 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from abc import ABC
|
from abc import ABC, abstractmethod
|
||||||
from collections.abc import Iterator
|
from collections.abc import Iterator
|
||||||
|
from dataclasses import dataclass
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import TYPE_CHECKING, TypeGuard
|
from typing import Any, TYPE_CHECKING, TypeGuard, TypeVar
|
||||||
|
|
||||||
from game.ato.flightplans.standard import StandardFlightPlan
|
from game.ato.flightplans.standard import StandardFlightPlan, StandardLayout
|
||||||
from game.typeguard import self_type_guard
|
from game.typeguard import self_type_guard
|
||||||
from game.utils import Distance, Speed
|
from game.utils import Distance, Speed
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..flight import Flight
|
|
||||||
from ..flightwaypoint import FlightWaypoint
|
from ..flightwaypoint import FlightWaypoint
|
||||||
from .flightplan import FlightPlan
|
from .flightplan import FlightPlan
|
||||||
|
|
||||||
|
|
||||||
class PatrollingFlightPlan(StandardFlightPlan, ABC):
|
@dataclass(frozen=True)
|
||||||
def __init__(
|
class PatrollingLayout(StandardLayout):
|
||||||
self,
|
nav_to: list[FlightWaypoint]
|
||||||
flight: Flight,
|
patrol_start: FlightWaypoint
|
||||||
departure: FlightWaypoint,
|
patrol_end: FlightWaypoint
|
||||||
arrival: FlightWaypoint,
|
nav_from: list[FlightWaypoint]
|
||||||
divert: FlightWaypoint | None,
|
|
||||||
bullseye: FlightWaypoint,
|
|
||||||
nav_to: list[FlightWaypoint],
|
|
||||||
nav_from: list[FlightWaypoint],
|
|
||||||
patrol_start: FlightWaypoint,
|
|
||||||
patrol_end: FlightWaypoint,
|
|
||||||
patrol_duration: timedelta,
|
|
||||||
patrol_speed: Speed,
|
|
||||||
engagement_distance: Distance,
|
|
||||||
) -> None:
|
|
||||||
super().__init__(flight, departure, arrival, divert, bullseye)
|
|
||||||
self.nav_to = nav_to
|
|
||||||
self.nav_from = nav_from
|
|
||||||
self.patrol_start = patrol_start
|
|
||||||
self.patrol_end = patrol_end
|
|
||||||
|
|
||||||
# Maximum time to remain on station.
|
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
||||||
self.patrol_duration = patrol_duration
|
yield self.departure
|
||||||
|
yield from self.nav_to
|
||||||
|
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
|
||||||
|
|
||||||
# Racetrack speed TAS.
|
|
||||||
self.patrol_speed = patrol_speed
|
|
||||||
|
|
||||||
# The engagement range of any Search Then Engage task, or the radius of a
|
LayoutT = TypeVar("LayoutT", bound=PatrollingLayout)
|
||||||
# Search Then Engage in Zone task. Any enemies of the appropriate type for
|
|
||||||
# this mission within this range of the flight's current position (or the
|
|
||||||
# center of the zone) will be engaged by the flight.
|
class PatrollingFlightPlan(StandardFlightPlan[LayoutT], ABC):
|
||||||
self.engagement_distance = engagement_distance
|
@property
|
||||||
|
@abstractmethod
|
||||||
|
def patrol_duration(self) -> timedelta:
|
||||||
|
"""Maximum time to remain on station."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abstractmethod
|
||||||
|
def patrol_speed(self) -> Speed:
|
||||||
|
"""Racetrack speed TAS."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abstractmethod
|
||||||
|
def engagement_distance(self) -> Distance:
|
||||||
|
"""The maximum engagement distance.
|
||||||
|
|
||||||
|
The engagement range of any Search Then Engage task, or the radius of a Search
|
||||||
|
Then Engage in Zone task. Any enemies of the appropriate type for this mission
|
||||||
|
within this range of the flight's current position (or the center of the zone)
|
||||||
|
will be engaged by the flight.
|
||||||
|
"""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def patrol_start_time(self) -> timedelta:
|
def patrol_start_time(self) -> timedelta:
|
||||||
@ -61,38 +71,29 @@ class PatrollingFlightPlan(StandardFlightPlan, ABC):
|
|||||||
return self.patrol_start_time + self.patrol_duration
|
return self.patrol_start_time + self.patrol_duration
|
||||||
|
|
||||||
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
|
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
|
||||||
if waypoint == self.patrol_start:
|
if waypoint == self.layout.patrol_start:
|
||||||
return self.patrol_start_time
|
return self.patrol_start_time
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
|
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
|
||||||
if waypoint == self.patrol_end:
|
if waypoint == self.layout.patrol_end:
|
||||||
return self.patrol_end_time
|
return self.patrol_end_time
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
|
||||||
yield self.departure
|
|
||||||
yield from self.nav_to
|
|
||||||
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
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def package_speed_waypoints(self) -> set[FlightWaypoint]:
|
def package_speed_waypoints(self) -> set[FlightWaypoint]:
|
||||||
return {self.patrol_start, self.patrol_end}
|
return {self.layout.patrol_start, self.layout.patrol_end}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def tot_waypoint(self) -> FlightWaypoint | None:
|
def tot_waypoint(self) -> FlightWaypoint | None:
|
||||||
return self.patrol_start
|
return self.layout.patrol_start
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mission_departure_time(self) -> timedelta:
|
def mission_departure_time(self) -> timedelta:
|
||||||
return self.patrol_end_time
|
return self.patrol_end_time
|
||||||
|
|
||||||
@self_type_guard
|
@self_type_guard
|
||||||
def is_patrol(self, flight_plan: FlightPlan) -> TypeGuard[PatrollingFlightPlan]:
|
def is_patrol(
|
||||||
|
self, flight_plan: FlightPlan[Any]
|
||||||
|
) -> TypeGuard[PatrollingFlightPlan[Any]]:
|
||||||
return True
|
return True
|
||||||
|
|||||||
@ -1,22 +1,22 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Iterator
|
from collections.abc import Iterator
|
||||||
|
from dataclasses import dataclass
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import TYPE_CHECKING, Type
|
from typing import TYPE_CHECKING, Type
|
||||||
|
|
||||||
from game.utils import feet
|
from game.utils import feet
|
||||||
from .ibuilder import IBuilder
|
from .ibuilder import IBuilder
|
||||||
from .standard import StandardFlightPlan
|
from .standard import StandardFlightPlan, StandardLayout
|
||||||
from .waypointbuilder import WaypointBuilder
|
from .waypointbuilder import WaypointBuilder
|
||||||
from ..flightstate import InFlight
|
from ..flightstate import InFlight
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..flight import Flight
|
|
||||||
from ..flightwaypoint import FlightWaypoint
|
from ..flightwaypoint import FlightWaypoint
|
||||||
|
|
||||||
|
|
||||||
class Builder(IBuilder):
|
class Builder(IBuilder):
|
||||||
def build(self) -> RtbFlightPlan:
|
def build(self) -> RtbLayout:
|
||||||
if not isinstance(self.flight.state, InFlight):
|
if not isinstance(self.flight.state, InFlight):
|
||||||
raise RuntimeError(f"Cannot abort {self} because it is not in flight")
|
raise RuntimeError(f"Cannot abort {self} because it is not in flight")
|
||||||
|
|
||||||
@ -36,8 +36,7 @@ class Builder(IBuilder):
|
|||||||
abort_point.name = "ABORT AND RTB"
|
abort_point.name = "ABORT AND RTB"
|
||||||
abort_point.pretty_name = "Abort and RTB"
|
abort_point.pretty_name = "Abort and RTB"
|
||||||
abort_point.description = "Abort mission and return to base"
|
abort_point.description = "Abort mission and return to base"
|
||||||
return RtbFlightPlan(
|
return RtbLayout(
|
||||||
flight=self.flight,
|
|
||||||
departure=builder.takeoff(self.flight.departure),
|
departure=builder.takeoff(self.flight.departure),
|
||||||
abort_location=abort_point,
|
abort_location=abort_point,
|
||||||
nav_to_destination=builder.nav_path(
|
nav_to_destination=builder.nav_path(
|
||||||
@ -52,24 +51,10 @@ class Builder(IBuilder):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class RtbFlightPlan(StandardFlightPlan):
|
@dataclass(frozen=True)
|
||||||
def __init__(
|
class RtbLayout(StandardLayout):
|
||||||
self,
|
abort_location: FlightWaypoint
|
||||||
flight: Flight,
|
nav_to_destination: list[FlightWaypoint]
|
||||||
departure: FlightWaypoint,
|
|
||||||
arrival: FlightWaypoint,
|
|
||||||
divert: FlightWaypoint | None,
|
|
||||||
bullseye: FlightWaypoint,
|
|
||||||
abort_location: FlightWaypoint,
|
|
||||||
nav_to_destination: list[FlightWaypoint],
|
|
||||||
) -> None:
|
|
||||||
super().__init__(flight, departure, arrival, divert, bullseye)
|
|
||||||
self.abort_location = abort_location
|
|
||||||
self.nav_to_destination = nav_to_destination
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def builder_type() -> Type[Builder]:
|
|
||||||
return Builder
|
|
||||||
|
|
||||||
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
||||||
yield self.departure
|
yield self.departure
|
||||||
@ -80,6 +65,12 @@ class RtbFlightPlan(StandardFlightPlan):
|
|||||||
yield self.divert
|
yield self.divert
|
||||||
yield self.bullseye
|
yield self.bullseye
|
||||||
|
|
||||||
|
|
||||||
|
class RtbFlightPlan(StandardFlightPlan[RtbLayout]):
|
||||||
|
@staticmethod
|
||||||
|
def builder_type() -> Type[Builder]:
|
||||||
|
return Builder
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def abort_index(self) -> int:
|
def abort_index(self) -> int:
|
||||||
return 1
|
return 1
|
||||||
|
|||||||
@ -3,20 +3,28 @@ from __future__ import annotations
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
from .formationattack import FormationAttackBuilder, FormationAttackFlightPlan
|
from .formationattack import (
|
||||||
|
FormationAttackBuilder,
|
||||||
|
FormationAttackFlightPlan,
|
||||||
|
FormationAttackLayout,
|
||||||
|
)
|
||||||
|
from .. import Flight
|
||||||
from ..flightwaypointtype import FlightWaypointType
|
from ..flightwaypointtype import FlightWaypointType
|
||||||
|
|
||||||
|
|
||||||
class SeadFlightPlan(FormationAttackFlightPlan):
|
class SeadFlightPlan(FormationAttackFlightPlan):
|
||||||
|
def __init__(self, flight: Flight, layout: FormationAttackLayout) -> None:
|
||||||
|
super().__init__(flight, layout)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def builder_type() -> Type[Builder]:
|
def builder_type() -> Type[Builder]:
|
||||||
return Builder
|
return Builder
|
||||||
|
|
||||||
|
@property
|
||||||
|
def lead_time(self) -> timedelta:
|
||||||
|
return timedelta(minutes=1)
|
||||||
|
|
||||||
class Builder(FormationAttackBuilder[SeadFlightPlan]):
|
|
||||||
def build(self) -> FormationAttackFlightPlan:
|
class Builder(FormationAttackBuilder):
|
||||||
return self._build(
|
def build(self) -> FormationAttackLayout:
|
||||||
SeadFlightPlan,
|
return self._build(FlightWaypointType.INGRESS_SEAD)
|
||||||
FlightWaypointType.INGRESS_SEAD,
|
|
||||||
lead_time=timedelta(minutes=1),
|
|
||||||
)
|
|
||||||
|
|||||||
@ -1,33 +1,30 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from abc import ABC
|
from abc import ABC
|
||||||
from typing import TYPE_CHECKING
|
from dataclasses import dataclass
|
||||||
|
from typing import TYPE_CHECKING, TypeVar
|
||||||
|
|
||||||
from game.ato.flightplans.flightplan import FlightPlan
|
from game.ato.flightplans.flightplan import FlightPlan, Layout
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..flight import Flight
|
|
||||||
from ..flightwaypoint import FlightWaypoint
|
from ..flightwaypoint import FlightWaypoint
|
||||||
|
|
||||||
|
|
||||||
class StandardFlightPlan(FlightPlan, ABC):
|
@dataclass(frozen=True)
|
||||||
|
class StandardLayout(Layout, ABC):
|
||||||
|
departure: FlightWaypoint
|
||||||
|
arrival: FlightWaypoint
|
||||||
|
divert: FlightWaypoint | None
|
||||||
|
bullseye: FlightWaypoint
|
||||||
|
|
||||||
|
|
||||||
|
LayoutT = TypeVar("LayoutT", bound=StandardLayout)
|
||||||
|
|
||||||
|
|
||||||
|
class StandardFlightPlan(FlightPlan[LayoutT], ABC):
|
||||||
"""Base type for all non-custom flight plans.
|
"""Base type for all non-custom flight plans.
|
||||||
|
|
||||||
We can't reason about custom flight plans so they get special treatment, but all
|
We can't reason about custom flight plans so they get special treatment, but all
|
||||||
others are guaranteed to have certain properties like departure and arrival points,
|
others are guaranteed to have certain properties like departure and arrival points,
|
||||||
potentially a divert field, and a bullseye
|
potentially a divert field, and a bullseye
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
flight: Flight,
|
|
||||||
departure: FlightWaypoint,
|
|
||||||
arrival: FlightWaypoint,
|
|
||||||
divert: FlightWaypoint | None,
|
|
||||||
bullseye: FlightWaypoint,
|
|
||||||
) -> None:
|
|
||||||
super().__init__(flight)
|
|
||||||
self.departure = departure
|
|
||||||
self.arrival = arrival
|
|
||||||
self.divert = divert
|
|
||||||
self.bullseye = bullseye
|
|
||||||
|
|||||||
@ -3,7 +3,11 @@ from __future__ import annotations
|
|||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
from game.theater import TheaterGroundObject
|
from game.theater import TheaterGroundObject
|
||||||
from .formationattack import FormationAttackBuilder, FormationAttackFlightPlan
|
from .formationattack import (
|
||||||
|
FormationAttackBuilder,
|
||||||
|
FormationAttackFlightPlan,
|
||||||
|
FormationAttackLayout,
|
||||||
|
)
|
||||||
from .invalidobjectivelocation import InvalidObjectiveLocation
|
from .invalidobjectivelocation import InvalidObjectiveLocation
|
||||||
from .waypointbuilder import StrikeTarget
|
from .waypointbuilder import StrikeTarget
|
||||||
from ..flightwaypointtype import FlightWaypointType
|
from ..flightwaypointtype import FlightWaypointType
|
||||||
@ -15,8 +19,8 @@ class StrikeFlightPlan(FormationAttackFlightPlan):
|
|||||||
return Builder
|
return Builder
|
||||||
|
|
||||||
|
|
||||||
class Builder(FormationAttackBuilder[StrikeFlightPlan]):
|
class Builder(FormationAttackBuilder):
|
||||||
def build(self) -> FormationAttackFlightPlan:
|
def build(self) -> FormationAttackLayout:
|
||||||
location = self.package.target
|
location = self.package.target
|
||||||
|
|
||||||
if not isinstance(location, TheaterGroundObject):
|
if not isinstance(location, TheaterGroundObject):
|
||||||
@ -26,4 +30,4 @@ class Builder(FormationAttackBuilder[StrikeFlightPlan]):
|
|||||||
for idx, unit in enumerate(location.strike_targets):
|
for idx, unit in enumerate(location.strike_targets):
|
||||||
targets.append(StrikeTarget(f"{unit.type.id} #{idx}", unit))
|
targets.append(StrikeTarget(f"{unit.type.id} #{idx}", unit))
|
||||||
|
|
||||||
return self._build(StrikeFlightPlan, FlightWaypointType.INGRESS_STRIKE, targets)
|
return self._build(FlightWaypointType.INGRESS_STRIKE, targets)
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import Iterator, TYPE_CHECKING, Type
|
from typing import Iterator, TYPE_CHECKING, Type
|
||||||
|
|
||||||
@ -7,18 +8,17 @@ from dcs import Point
|
|||||||
|
|
||||||
from game.utils import Heading
|
from game.utils import Heading
|
||||||
from .ibuilder import IBuilder
|
from .ibuilder import IBuilder
|
||||||
from .loiter import LoiterFlightPlan
|
from .loiter import LoiterFlightPlan, LoiterLayout
|
||||||
from .waypointbuilder import WaypointBuilder
|
from .waypointbuilder import WaypointBuilder
|
||||||
from ..traveltime import GroundSpeed, TravelTime
|
from ..traveltime import GroundSpeed, TravelTime
|
||||||
from ...flightplan import HoldZoneGeometry
|
from ...flightplan import HoldZoneGeometry
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..flight import Flight
|
|
||||||
from ..flightwaypoint import FlightWaypoint
|
from ..flightwaypoint import FlightWaypoint
|
||||||
|
|
||||||
|
|
||||||
class Builder(IBuilder):
|
class Builder(IBuilder):
|
||||||
def build(self) -> SweepFlightPlan:
|
def build(self) -> SweepLayout:
|
||||||
assert self.package.waypoints is not None
|
assert self.package.waypoints is not None
|
||||||
target = self.package.target.position
|
target = self.package.target.position
|
||||||
heading = Heading.from_degrees(
|
heading = Heading.from_degrees(
|
||||||
@ -38,12 +38,9 @@ class Builder(IBuilder):
|
|||||||
if self.package.waypoints is not None:
|
if self.package.waypoints is not None:
|
||||||
refuel = builder.refuel(self.package.waypoints.refuel)
|
refuel = builder.refuel(self.package.waypoints.refuel)
|
||||||
|
|
||||||
return SweepFlightPlan(
|
return SweepLayout(
|
||||||
flight=self.flight,
|
|
||||||
lead_time=timedelta(minutes=5),
|
|
||||||
departure=builder.takeoff(self.flight.departure),
|
departure=builder.takeoff(self.flight.departure),
|
||||||
hold=hold,
|
hold=hold,
|
||||||
hold_duration=timedelta(minutes=5),
|
|
||||||
nav_to=builder.nav_path(
|
nav_to=builder.nav_path(
|
||||||
hold.position, start.position, self.doctrine.ingress_altitude
|
hold.position, start.position, self.doctrine.ingress_altitude
|
||||||
),
|
),
|
||||||
@ -71,42 +68,13 @@ class Builder(IBuilder):
|
|||||||
).find_best_hold_point()
|
).find_best_hold_point()
|
||||||
|
|
||||||
|
|
||||||
class SweepFlightPlan(LoiterFlightPlan):
|
@dataclass(frozen=True)
|
||||||
def __init__(
|
class SweepLayout(LoiterLayout):
|
||||||
self,
|
nav_to: list[FlightWaypoint]
|
||||||
flight: Flight,
|
sweep_start: FlightWaypoint
|
||||||
departure: FlightWaypoint,
|
sweep_end: FlightWaypoint
|
||||||
arrival: FlightWaypoint,
|
refuel: FlightWaypoint | None
|
||||||
divert: FlightWaypoint | None,
|
nav_from: list[FlightWaypoint]
|
||||||
bullseye: FlightWaypoint,
|
|
||||||
nav_to: list[FlightWaypoint],
|
|
||||||
nav_from: list[FlightWaypoint],
|
|
||||||
hold: FlightWaypoint,
|
|
||||||
hold_duration: timedelta,
|
|
||||||
sweep_start: FlightWaypoint,
|
|
||||||
sweep_end: FlightWaypoint,
|
|
||||||
refuel: FlightWaypoint,
|
|
||||||
lead_time: timedelta,
|
|
||||||
) -> None:
|
|
||||||
super().__init__(
|
|
||||||
flight,
|
|
||||||
departure,
|
|
||||||
arrival,
|
|
||||||
divert,
|
|
||||||
bullseye,
|
|
||||||
nav_to,
|
|
||||||
nav_from,
|
|
||||||
hold,
|
|
||||||
hold_duration,
|
|
||||||
)
|
|
||||||
self.sweep_start = sweep_start
|
|
||||||
self.sweep_end = sweep_end
|
|
||||||
self.refuel = refuel
|
|
||||||
self.lead_time = lead_time
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def builder_type() -> Type[Builder]:
|
|
||||||
return Builder
|
|
||||||
|
|
||||||
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
||||||
yield self.departure
|
yield self.departure
|
||||||
@ -122,13 +90,23 @@ class SweepFlightPlan(LoiterFlightPlan):
|
|||||||
yield self.divert
|
yield self.divert
|
||||||
yield self.bullseye
|
yield self.bullseye
|
||||||
|
|
||||||
|
|
||||||
|
class SweepFlightPlan(LoiterFlightPlan):
|
||||||
|
@property
|
||||||
|
def lead_time(self) -> timedelta:
|
||||||
|
return timedelta(minutes=5)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def builder_type() -> Type[Builder]:
|
||||||
|
return Builder
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def combat_speed_waypoints(self) -> set[FlightWaypoint]:
|
def combat_speed_waypoints(self) -> set[FlightWaypoint]:
|
||||||
return {self.sweep_end}
|
return {self.layout.sweep_end}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def tot_waypoint(self) -> FlightWaypoint | None:
|
def tot_waypoint(self) -> FlightWaypoint | None:
|
||||||
return self.sweep_end
|
return self.layout.sweep_end
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def tot_offset(self) -> timedelta:
|
def tot_offset(self) -> timedelta:
|
||||||
@ -137,7 +115,7 @@ class SweepFlightPlan(LoiterFlightPlan):
|
|||||||
@property
|
@property
|
||||||
def sweep_start_time(self) -> timedelta:
|
def sweep_start_time(self) -> timedelta:
|
||||||
travel_time = self.travel_time_between_waypoints(
|
travel_time = self.travel_time_between_waypoints(
|
||||||
self.sweep_start, self.sweep_end
|
self.layout.sweep_start, self.layout.sweep_end
|
||||||
)
|
)
|
||||||
return self.sweep_end_time - travel_time
|
return self.sweep_end_time - travel_time
|
||||||
|
|
||||||
@ -146,23 +124,23 @@ class SweepFlightPlan(LoiterFlightPlan):
|
|||||||
return self.tot
|
return self.tot
|
||||||
|
|
||||||
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
|
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
|
||||||
if waypoint == self.sweep_start:
|
if waypoint == self.layout.sweep_start:
|
||||||
return self.sweep_start_time
|
return self.sweep_start_time
|
||||||
if waypoint == self.sweep_end:
|
if waypoint == self.layout.sweep_end:
|
||||||
return self.sweep_end_time
|
return self.sweep_end_time
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
|
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
|
||||||
if waypoint == self.hold:
|
if waypoint == self.layout.hold:
|
||||||
return self.push_time
|
return self.push_time
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def push_time(self) -> timedelta:
|
def push_time(self) -> timedelta:
|
||||||
return self.sweep_end_time - TravelTime.between_points(
|
return self.sweep_end_time - TravelTime.between_points(
|
||||||
self.hold.position,
|
self.layout.hold.position,
|
||||||
self.sweep_end.position,
|
self.layout.sweep_end.position,
|
||||||
GroundSpeed.for_flight(self.flight, self.hold.alt),
|
GroundSpeed.for_flight(self.flight, self.layout.hold.alt),
|
||||||
)
|
)
|
||||||
|
|
||||||
def mission_departure_time(self) -> timedelta:
|
def mission_departure_time(self) -> timedelta:
|
||||||
|
|||||||
@ -2,21 +2,21 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import random
|
import random
|
||||||
from collections.abc import Iterator
|
from collections.abc import Iterator
|
||||||
|
from dataclasses import dataclass
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import TYPE_CHECKING, Type
|
from typing import TYPE_CHECKING, Type
|
||||||
|
|
||||||
from game.utils import Distance, Speed, feet
|
from game.utils import Distance, Speed, feet
|
||||||
from .capbuilder import CapBuilder
|
from .capbuilder import CapBuilder
|
||||||
from .patrolling import PatrollingFlightPlan
|
from .patrolling import PatrollingFlightPlan, PatrollingLayout
|
||||||
from .waypointbuilder import WaypointBuilder
|
from .waypointbuilder import WaypointBuilder
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..flight import Flight
|
|
||||||
from ..flightwaypoint import FlightWaypoint
|
from ..flightwaypoint import FlightWaypoint
|
||||||
|
|
||||||
|
|
||||||
class Builder(CapBuilder):
|
class Builder(CapBuilder):
|
||||||
def build(self) -> TarCapFlightPlan:
|
def build(self) -> TarCapLayout:
|
||||||
location = self.package.target
|
location = self.package.target
|
||||||
|
|
||||||
preferred_alt = self.flight.unit_type.preferred_patrol_altitude
|
preferred_alt = self.flight.unit_type.preferred_patrol_altitude
|
||||||
@ -25,7 +25,6 @@ class Builder(CapBuilder):
|
|||||||
self.doctrine.min_patrol_altitude,
|
self.doctrine.min_patrol_altitude,
|
||||||
min(self.doctrine.max_patrol_altitude, randomized_alt),
|
min(self.doctrine.max_patrol_altitude, randomized_alt),
|
||||||
)
|
)
|
||||||
patrol_speed = self.flight.unit_type.preferred_patrol_speed(patrol_alt)
|
|
||||||
|
|
||||||
builder = WaypointBuilder(self.flight, self.coalition)
|
builder = WaypointBuilder(self.flight, self.coalition)
|
||||||
orbit0p, orbit1p = self.cap_racetrack_for_objective(location, barcap=False)
|
orbit0p, orbit1p = self.cap_racetrack_for_objective(location, barcap=False)
|
||||||
@ -37,16 +36,7 @@ class Builder(CapBuilder):
|
|||||||
if self.package.waypoints is not None:
|
if self.package.waypoints is not None:
|
||||||
refuel = builder.refuel(self.package.waypoints.refuel)
|
refuel = builder.refuel(self.package.waypoints.refuel)
|
||||||
|
|
||||||
return TarCapFlightPlan(
|
return TarCapLayout(
|
||||||
flight=self.flight,
|
|
||||||
lead_time=timedelta(minutes=2),
|
|
||||||
# Note that this duration only has an effect if there are no
|
|
||||||
# flights in the package that have requested escort. If the package
|
|
||||||
# requests an escort the CAP self.flight will remain on station for the
|
|
||||||
# duration of the escorted mission, or until it is winchester/bingo.
|
|
||||||
patrol_duration=self.doctrine.cap_duration,
|
|
||||||
patrol_speed=patrol_speed,
|
|
||||||
engagement_distance=self.doctrine.cap_engagement_range,
|
|
||||||
departure=builder.takeoff(self.flight.departure),
|
departure=builder.takeoff(self.flight.departure),
|
||||||
nav_to=builder.nav_path(
|
nav_to=builder.nav_path(
|
||||||
self.flight.departure.position, orbit0p, patrol_alt
|
self.flight.departure.position, orbit0p, patrol_alt
|
||||||
@ -63,44 +53,9 @@ class Builder(CapBuilder):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TarCapFlightPlan(PatrollingFlightPlan):
|
@dataclass(frozen=True)
|
||||||
def __init__(
|
class TarCapLayout(PatrollingLayout):
|
||||||
self,
|
refuel: FlightWaypoint | None
|
||||||
flight: Flight,
|
|
||||||
departure: FlightWaypoint,
|
|
||||||
arrival: FlightWaypoint,
|
|
||||||
divert: FlightWaypoint | None,
|
|
||||||
bullseye: FlightWaypoint,
|
|
||||||
nav_to: list[FlightWaypoint],
|
|
||||||
nav_from: list[FlightWaypoint],
|
|
||||||
patrol_start: FlightWaypoint,
|
|
||||||
patrol_end: FlightWaypoint,
|
|
||||||
patrol_duration: timedelta,
|
|
||||||
patrol_speed: Speed,
|
|
||||||
engagement_distance: Distance,
|
|
||||||
refuel: FlightWaypoint | None,
|
|
||||||
lead_time: timedelta,
|
|
||||||
) -> None:
|
|
||||||
super().__init__(
|
|
||||||
flight,
|
|
||||||
departure,
|
|
||||||
arrival,
|
|
||||||
divert,
|
|
||||||
bullseye,
|
|
||||||
nav_to,
|
|
||||||
nav_from,
|
|
||||||
patrol_start,
|
|
||||||
patrol_end,
|
|
||||||
patrol_duration,
|
|
||||||
patrol_speed,
|
|
||||||
engagement_distance,
|
|
||||||
)
|
|
||||||
self.refuel = refuel
|
|
||||||
self.lead_time = lead_time
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def builder_type() -> Type[Builder]:
|
|
||||||
return Builder
|
|
||||||
|
|
||||||
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
||||||
yield self.departure
|
yield self.departure
|
||||||
@ -115,16 +70,44 @@ class TarCapFlightPlan(PatrollingFlightPlan):
|
|||||||
yield self.divert
|
yield self.divert
|
||||||
yield self.bullseye
|
yield self.bullseye
|
||||||
|
|
||||||
|
|
||||||
|
class TarCapFlightPlan(PatrollingFlightPlan[TarCapLayout]):
|
||||||
|
@property
|
||||||
|
def lead_time(self) -> timedelta:
|
||||||
|
return timedelta(minutes=2)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def patrol_duration(self) -> timedelta:
|
||||||
|
# Note that this duration only has an effect if there are no
|
||||||
|
# flights in the package that have requested escort. If the package
|
||||||
|
# requests an escort the CAP self.flight will remain on station for the
|
||||||
|
# duration of the escorted mission, or until it is winchester/bingo.
|
||||||
|
return self.flight.coalition.doctrine.cap_duration
|
||||||
|
|
||||||
|
@property
|
||||||
|
def patrol_speed(self) -> Speed:
|
||||||
|
return self.flight.unit_type.preferred_patrol_speed(
|
||||||
|
self.layout.patrol_start.alt
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def engagement_distance(self) -> Distance:
|
||||||
|
return self.flight.coalition.doctrine.cap_engagement_range
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def builder_type() -> Type[Builder]:
|
||||||
|
return Builder
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def combat_speed_waypoints(self) -> set[FlightWaypoint]:
|
def combat_speed_waypoints(self) -> set[FlightWaypoint]:
|
||||||
return {self.patrol_start, self.patrol_end}
|
return {self.layout.patrol_start, self.layout.patrol_end}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def tot_offset(self) -> timedelta:
|
def tot_offset(self) -> timedelta:
|
||||||
return -self.lead_time
|
return -self.lead_time
|
||||||
|
|
||||||
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
|
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
|
||||||
if waypoint == self.patrol_end:
|
if waypoint == self.layout.patrol_end:
|
||||||
return self.patrol_end_time
|
return self.patrol_end_time
|
||||||
return super().depart_time_for_waypoint(waypoint)
|
return super().depart_time_for_waypoint(waypoint)
|
||||||
|
|
||||||
|
|||||||
@ -3,18 +3,16 @@ from __future__ import annotations
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
from game.utils import Heading, feet, knots, meters, nautical_miles
|
from game.utils import Distance, Heading, Speed, feet, knots, meters, nautical_miles
|
||||||
from .ibuilder import IBuilder
|
from .ibuilder import IBuilder
|
||||||
from .patrolling import PatrollingFlightPlan
|
from .patrolling import PatrollingFlightPlan, PatrollingLayout
|
||||||
from .waypointbuilder import WaypointBuilder
|
from .waypointbuilder import WaypointBuilder
|
||||||
|
|
||||||
|
|
||||||
class Builder(IBuilder):
|
class Builder(IBuilder):
|
||||||
def build(self) -> TheaterRefuelingFlightPlan:
|
def build(self) -> PatrollingLayout:
|
||||||
racetrack_half_distance = nautical_miles(20).meters
|
racetrack_half_distance = nautical_miles(20).meters
|
||||||
|
|
||||||
patrol_duration = timedelta(hours=1)
|
|
||||||
|
|
||||||
location = self.package.target
|
location = self.package.target
|
||||||
|
|
||||||
closest_boundary = self.threat_zones.closest_boundary(location.position)
|
closest_boundary = self.threat_zones.closest_boundary(location.position)
|
||||||
@ -53,17 +51,9 @@ class Builder(IBuilder):
|
|||||||
else:
|
else:
|
||||||
altitude = feet(21000)
|
altitude = feet(21000)
|
||||||
|
|
||||||
# TODO: Could use self.flight.unit_type.preferred_patrol_speed(altitude).
|
|
||||||
if tanker_type.patrol_speed is not None:
|
|
||||||
speed = tanker_type.patrol_speed
|
|
||||||
else:
|
|
||||||
# ~280 knots IAS at 21000.
|
|
||||||
speed = knots(400)
|
|
||||||
|
|
||||||
racetrack = builder.race_track(racetrack_start, racetrack_end, altitude)
|
racetrack = builder.race_track(racetrack_start, racetrack_end, altitude)
|
||||||
|
|
||||||
return TheaterRefuelingFlightPlan(
|
return PatrollingLayout(
|
||||||
flight=self.flight,
|
|
||||||
departure=builder.takeoff(self.flight.departure),
|
departure=builder.takeoff(self.flight.departure),
|
||||||
nav_to=builder.nav_path(
|
nav_to=builder.nav_path(
|
||||||
self.flight.departure.position, racetrack_start, altitude
|
self.flight.departure.position, racetrack_start, altitude
|
||||||
@ -76,15 +66,28 @@ class Builder(IBuilder):
|
|||||||
arrival=builder.land(self.flight.arrival),
|
arrival=builder.land(self.flight.arrival),
|
||||||
divert=builder.divert(self.flight.divert),
|
divert=builder.divert(self.flight.divert),
|
||||||
bullseye=builder.bullseye(),
|
bullseye=builder.bullseye(),
|
||||||
patrol_duration=patrol_duration,
|
|
||||||
patrol_speed=speed,
|
|
||||||
# TODO: Factor out a common base of the combat and non-combat race-tracks.
|
|
||||||
# No harm in setting this, but we ought to clean up a bit.
|
|
||||||
engagement_distance=meters(0),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TheaterRefuelingFlightPlan(PatrollingFlightPlan):
|
class TheaterRefuelingFlightPlan(PatrollingFlightPlan[PatrollingLayout]):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def builder_type() -> Type[Builder]:
|
def builder_type() -> Type[Builder]:
|
||||||
return Builder
|
return Builder
|
||||||
|
|
||||||
|
@property
|
||||||
|
def patrol_duration(self) -> timedelta:
|
||||||
|
return timedelta(hours=1)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def patrol_speed(self) -> Speed:
|
||||||
|
# TODO: Could use self.flight.unit_type.preferred_patrol_speed(altitude).
|
||||||
|
if self.flight.unit_type.patrol_speed is not None:
|
||||||
|
return self.flight.unit_type.patrol_speed
|
||||||
|
# ~280 knots IAS at 21000.
|
||||||
|
return knots(400)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def engagement_distance(self) -> Distance:
|
||||||
|
# TODO: Factor out a common base of the combat and non-combat race-tracks.
|
||||||
|
# No harm in setting this, but we ought to clean up a bit.
|
||||||
|
return meters(0)
|
||||||
|
|||||||
@ -13,7 +13,7 @@ class CasIngressBuilder(PydcsWaypointBuilder):
|
|||||||
if isinstance(self.flight.flight_plan, CasFlightPlan):
|
if isinstance(self.flight.flight_plan, CasFlightPlan):
|
||||||
waypoint.add_task(
|
waypoint.add_task(
|
||||||
EngageTargetsInZone(
|
EngageTargetsInZone(
|
||||||
position=self.flight.flight_plan.target.position,
|
position=self.flight.flight_plan.layout.target.position,
|
||||||
radius=int(self.flight.flight_plan.engagement_distance.meters),
|
radius=int(self.flight.flight_plan.engagement_distance.meters),
|
||||||
targets=[
|
targets=[
|
||||||
Targets.All.GroundUnits.GroundVehicles,
|
Targets.All.GroundUnits.GroundVehicles,
|
||||||
|
|||||||
@ -4,8 +4,8 @@ from fastapi import APIRouter, Depends
|
|||||||
from shapely.geometry import LineString, Point as ShapelyPoint
|
from shapely.geometry import LineString, Point as ShapelyPoint
|
||||||
|
|
||||||
from game import Game
|
from game import Game
|
||||||
from game.ato.flightplans.patrolling import PatrollingFlightPlan
|
|
||||||
from game.ato.flightplans.cas import CasFlightPlan
|
from game.ato.flightplans.cas import CasFlightPlan
|
||||||
|
from game.ato.flightplans.patrolling import PatrollingFlightPlan
|
||||||
from game.server import GameContext
|
from game.server import GameContext
|
||||||
from game.server.flights.models import FlightJs
|
from game.server.flights.models import FlightJs
|
||||||
from game.server.leaflet import LeafletPoly, ShapelyUtil
|
from game.server.leaflet import LeafletPoly, ShapelyUtil
|
||||||
@ -41,10 +41,10 @@ def commit_boundary(
|
|||||||
flight = game.db.flights.get(flight_id)
|
flight = game.db.flights.get(flight_id)
|
||||||
if not isinstance(flight.flight_plan, PatrollingFlightPlan):
|
if not isinstance(flight.flight_plan, PatrollingFlightPlan):
|
||||||
return []
|
return []
|
||||||
start = flight.flight_plan.patrol_start
|
start = flight.flight_plan.layout.patrol_start
|
||||||
end = flight.flight_plan.patrol_end
|
end = flight.flight_plan.layout.patrol_end
|
||||||
if isinstance(flight.flight_plan, CasFlightPlan):
|
if isinstance(flight.flight_plan, CasFlightPlan):
|
||||||
center = flight.flight_plan.target.position
|
center = flight.flight_plan.layout.target.position
|
||||||
commit_center = ShapelyPoint(center.x, center.y)
|
commit_center = ShapelyPoint(center.x, center.y)
|
||||||
else:
|
else:
|
||||||
commit_center = LineString(
|
commit_center = LineString(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user