mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Un-pydantic FlightWaypoint.
Apparently it's a bad idea to try to make the core data pydantic models, and those should really be treated more as a view-model. Doing otherwise causes odd patterns (like the UI info I had leaked into the core type), and makes it harder to interop with third-party types.
This commit is contained in:
parent
c5ab0431a9
commit
3e08e0e8b6
@ -1,93 +1,31 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from dataclasses import field
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import timedelta
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
from typing import Literal, TYPE_CHECKING
|
||||
|
||||
from dcs import Point
|
||||
from dcs.unit import Unit
|
||||
from pydantic.dataclasses import dataclass
|
||||
|
||||
from game.ato.flightwaypointtype import FlightWaypointType
|
||||
from game.theater import LatLon
|
||||
from game.theater.theatergroup import TheaterUnit
|
||||
from game.utils import Distance, meters
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game.theater import ConflictTheater, ControlPoint, MissionTarget
|
||||
from game.theater import ControlPoint, MissionTarget
|
||||
|
||||
|
||||
AltitudeReference = Literal["BARO", "RADIO"]
|
||||
|
||||
|
||||
@dataclass
|
||||
class BaseFlightWaypoint:
|
||||
class FlightWaypoint:
|
||||
name: str
|
||||
waypoint_type: FlightWaypointType
|
||||
x: float
|
||||
y: float
|
||||
alt: Distance
|
||||
alt_type: str
|
||||
|
||||
is_movable: bool = field(init=False)
|
||||
should_mark: bool = field(init=False)
|
||||
include_in_path: bool = field(init=False)
|
||||
|
||||
# Do not use unless you're sure it's up to date. Pydantic doesn't have support for
|
||||
# serializing lazy properties so this needs to be stored in the class, but because
|
||||
# updating it requires a reference to the ConflictTheater it may not always be set,
|
||||
# or up to date. Call update_latlng to refresh.
|
||||
latlng: LatLon | None = None
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
# Target *points* are the exact location of a unit, whereas the target area is
|
||||
# only the center of the objective. Allow moving the latter since its exact
|
||||
# location isn't very important.
|
||||
#
|
||||
# Landing, and divert should be changed in the flight settings UI, takeoff
|
||||
# cannot be changed because that's where the plane is.
|
||||
#
|
||||
# Moving the bullseye reference only makes it wrong.
|
||||
self.is_movable = self.waypoint_type not in {
|
||||
FlightWaypointType.BULLSEYE,
|
||||
FlightWaypointType.DIVERT,
|
||||
FlightWaypointType.LANDING_POINT,
|
||||
FlightWaypointType.TAKEOFF,
|
||||
FlightWaypointType.TARGET_POINT,
|
||||
}
|
||||
|
||||
# We don't need a marker for the departure waypoint (and it's likely
|
||||
# coincident with the landing waypoint, so hard to see). We do want to draw
|
||||
# the path from it though.
|
||||
#
|
||||
# We also don't need the landing waypoint since we'll be drawing that path
|
||||
# as well, and it's clear what it is, and only obscured the CP icon.
|
||||
#
|
||||
# The divert waypoint also obscures the CP. We don't draw the path to it,
|
||||
# but it can be seen in the flight settings page, so it's not really a
|
||||
# problem to exclude it.
|
||||
#
|
||||
# Bullseye ought to be (but currently isn't) drawn *once* rather than as a
|
||||
# flight waypoint.
|
||||
self.should_mark = self.waypoint_type not in {
|
||||
FlightWaypointType.BULLSEYE,
|
||||
FlightWaypointType.DIVERT,
|
||||
FlightWaypointType.LANDING_POINT,
|
||||
FlightWaypointType.TAKEOFF,
|
||||
}
|
||||
|
||||
self.include_in_path = self.waypoint_type not in {
|
||||
FlightWaypointType.BULLSEYE,
|
||||
FlightWaypointType.DIVERT,
|
||||
}
|
||||
|
||||
@property
|
||||
def position(self) -> Point:
|
||||
return Point(self.x, self.y)
|
||||
|
||||
def update_latlng(self, theater: ConflictTheater) -> None:
|
||||
self.latlng = theater.point_to_ll(self.position)
|
||||
|
||||
|
||||
class FlightWaypoint(BaseFlightWaypoint):
|
||||
alt: Distance = meters(0)
|
||||
alt_type: AltitudeReference = "BARO"
|
||||
control_point: ControlPoint | None = None
|
||||
|
||||
# TODO: Merge with pretty_name.
|
||||
@ -95,7 +33,7 @@ class FlightWaypoint(BaseFlightWaypoint):
|
||||
# having three names. A short and long form is enough.
|
||||
description: str = ""
|
||||
|
||||
targets: Sequence[MissionTarget | TheaterUnit] = []
|
||||
targets: Sequence[MissionTarget | TheaterUnit] = field(default_factory=list)
|
||||
obj_name: str = ""
|
||||
pretty_name: str = ""
|
||||
only_for_player: bool = False
|
||||
@ -111,29 +49,9 @@ class FlightWaypoint(BaseFlightWaypoint):
|
||||
tot: timedelta | None = None
|
||||
departure_time: timedelta | None = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
waypoint_type: FlightWaypointType,
|
||||
x: float,
|
||||
y: float,
|
||||
alt: Distance = meters(0),
|
||||
control_point: Optional[ControlPoint] = None,
|
||||
) -> None:
|
||||
"""Creates a flight waypoint.
|
||||
|
||||
Args:
|
||||
waypoint_type: The waypoint type.
|
||||
x: X coordinate of the waypoint.
|
||||
y: Y coordinate of the waypoint.
|
||||
alt: Altitude of the waypoint. By default this is MSL, but it can be
|
||||
changed to AGL by setting alt_type to "RADIO"
|
||||
control_point: The control point to associate with this waypoint. Needed for
|
||||
landing points.
|
||||
"""
|
||||
super().__init__(
|
||||
name="", waypoint_type=waypoint_type, x=x, y=y, alt=alt, alt_type="BARO"
|
||||
)
|
||||
self.control_point = control_point
|
||||
@property
|
||||
def position(self) -> Point:
|
||||
return Point(self.x, self.y)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(id(self))
|
||||
|
||||
73
game/server/waypoints/models.py
Normal file
73
game/server/waypoints/models.py
Normal file
@ -0,0 +1,73 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pydantic.dataclasses import dataclass
|
||||
|
||||
from game.ato import FlightWaypoint
|
||||
from game.ato.flightwaypointtype import FlightWaypointType
|
||||
from game.theater import ConflictTheater, LatLon
|
||||
|
||||
|
||||
@dataclass
|
||||
class FlightWaypointJs:
|
||||
name: str
|
||||
position: LatLon
|
||||
altitude_ft: float
|
||||
altitude_reference: str
|
||||
is_movable: bool
|
||||
should_mark: bool
|
||||
include_in_path: bool
|
||||
|
||||
@staticmethod
|
||||
def for_waypoint(
|
||||
waypoint: FlightWaypoint, theater: ConflictTheater
|
||||
) -> FlightWaypointJs:
|
||||
# Target *points* are the exact location of a unit, whereas the target area is
|
||||
# only the center of the objective. Allow moving the latter since its exact
|
||||
# location isn't very important.
|
||||
#
|
||||
# Landing, and divert should be changed in the flight settings UI, takeoff
|
||||
# cannot be changed because that's where the plane is.
|
||||
#
|
||||
# Moving the bullseye reference only makes it wrong.
|
||||
is_movable = waypoint.waypoint_type not in {
|
||||
FlightWaypointType.BULLSEYE,
|
||||
FlightWaypointType.DIVERT,
|
||||
FlightWaypointType.LANDING_POINT,
|
||||
FlightWaypointType.TAKEOFF,
|
||||
FlightWaypointType.TARGET_POINT,
|
||||
}
|
||||
|
||||
# We don't need a marker for the departure waypoint (and it's likely
|
||||
# coincident with the landing waypoint, so hard to see). We do want to draw
|
||||
# the path from it though.
|
||||
#
|
||||
# We also don't need the landing waypoint since we'll be drawing that path
|
||||
# as well, and it's clear what it is, and only obscured the CP icon.
|
||||
#
|
||||
# The divert waypoint also obscures the CP. We don't draw the path to it,
|
||||
# but it can be seen in the flight settings page, so it's not really a
|
||||
# problem to exclude it.
|
||||
#
|
||||
# Bullseye ought to be (but currently isn't) drawn *once* rather than as a
|
||||
# flight waypoint.
|
||||
should_mark = waypoint.waypoint_type not in {
|
||||
FlightWaypointType.BULLSEYE,
|
||||
FlightWaypointType.DIVERT,
|
||||
FlightWaypointType.LANDING_POINT,
|
||||
FlightWaypointType.TAKEOFF,
|
||||
}
|
||||
|
||||
include_in_path = waypoint.waypoint_type not in {
|
||||
FlightWaypointType.BULLSEYE,
|
||||
FlightWaypointType.DIVERT,
|
||||
}
|
||||
|
||||
return FlightWaypointJs(
|
||||
name=waypoint.name,
|
||||
position=theater.point_to_ll(waypoint.position),
|
||||
altitude_ft=waypoint.alt.feet,
|
||||
altitude_reference=waypoint.alt_type,
|
||||
is_movable=is_movable,
|
||||
should_mark=should_mark,
|
||||
include_in_path=include_in_path,
|
||||
)
|
||||
@ -4,31 +4,36 @@ from uuid import UUID
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
|
||||
from game import Game
|
||||
from game.ato.flightwaypoint import BaseFlightWaypoint, FlightWaypoint
|
||||
from game.ato.flightwaypoint import FlightWaypoint
|
||||
from game.ato.flightwaypointtype import FlightWaypointType
|
||||
from game.server import GameContext
|
||||
from game.server.waypoints.models import FlightWaypointJs
|
||||
from game.theater import LatLon
|
||||
from game.utils import meters
|
||||
|
||||
router: APIRouter = APIRouter(prefix="/waypoints")
|
||||
|
||||
|
||||
@router.get("/{flight_id}", response_model=list[BaseFlightWaypoint])
|
||||
@router.get("/{flight_id}", response_model=list[FlightWaypointJs])
|
||||
def all_waypoints_for_flight(
|
||||
flight_id: UUID, game: Game = Depends(GameContext.get)
|
||||
) -> list[FlightWaypoint]:
|
||||
) -> list[FlightWaypointJs]:
|
||||
flight = game.db.flights.get(flight_id)
|
||||
departure = FlightWaypoint(
|
||||
FlightWaypointType.TAKEOFF,
|
||||
flight.departure.position.x,
|
||||
flight.departure.position.y,
|
||||
meters(0),
|
||||
departure = FlightWaypointJs.for_waypoint(
|
||||
FlightWaypoint(
|
||||
"TAKEOFF",
|
||||
FlightWaypointType.TAKEOFF,
|
||||
flight.departure.position.x,
|
||||
flight.departure.position.y,
|
||||
meters(0),
|
||||
"RADIO",
|
||||
),
|
||||
game.theater,
|
||||
)
|
||||
departure.alt_type = "RADIO"
|
||||
points = [departure] + flight.flight_plan.waypoints
|
||||
for point in points:
|
||||
point.update_latlng(game.theater)
|
||||
return points
|
||||
return [departure] + [
|
||||
FlightWaypointJs.for_waypoint(w, game.theater)
|
||||
for w in flight.flight_plan.waypoints
|
||||
]
|
||||
|
||||
|
||||
@router.post("/{flight_id}/{waypoint_idx}/position")
|
||||
@ -43,9 +48,6 @@ def set_position(
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
waypoint = flight.flight_plan.waypoints[waypoint_idx - 1]
|
||||
if not waypoint.is_movable:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
point = game.theater.ll_to_point(position)
|
||||
waypoint.x = point.x
|
||||
waypoint.y = point.y
|
||||
|
||||
@ -5,10 +5,10 @@ import math
|
||||
import random
|
||||
from abc import ABC, abstractmethod
|
||||
from collections.abc import Iterable
|
||||
from dataclasses import dataclass
|
||||
from typing import TypeVar, Union
|
||||
|
||||
from dcs import Point
|
||||
from pydantic.dataclasses import dataclass
|
||||
from shapely.geometry import Point as ShapelyPoint
|
||||
|
||||
METERS_TO_FEET = 3.28084
|
||||
|
||||
@ -24,7 +24,6 @@ from typing import (
|
||||
)
|
||||
|
||||
from dcs.mapping import Point
|
||||
from dcs.unit import Unit
|
||||
from shapely.geometry import Point as ShapelyPoint
|
||||
|
||||
from game.ato.flighttype import FlightType
|
||||
@ -700,10 +699,12 @@ class StrikeFlightPlan(FormationFlightPlan):
|
||||
@property
|
||||
def target_area_waypoint(self) -> FlightWaypoint:
|
||||
return FlightWaypoint(
|
||||
"TARGET AREA",
|
||||
FlightWaypointType.TARGET_GROUP_LOC,
|
||||
self.package.target.position.x,
|
||||
self.package.target.position.y,
|
||||
meters(0),
|
||||
"RADIO",
|
||||
)
|
||||
|
||||
@property
|
||||
@ -903,10 +904,12 @@ class RefuelingFlightPlan(PatrollingFlightPlan):
|
||||
class PackageRefuelingFlightPlan(RefuelingFlightPlan):
|
||||
def target_area_waypoint(self) -> FlightWaypoint:
|
||||
return FlightWaypoint(
|
||||
"TARGET AREA",
|
||||
FlightWaypointType.TARGET_GROUP_LOC,
|
||||
self.package.target.position.x,
|
||||
self.package.target.position.y,
|
||||
meters(0),
|
||||
"RADIO",
|
||||
)
|
||||
|
||||
@property
|
||||
@ -921,13 +924,13 @@ class PackageRefuelingFlightPlan(RefuelingFlightPlan):
|
||||
# Cheat in a FlightWaypoint for the split point.
|
||||
split: Point = self.package.waypoints.split
|
||||
split_waypoint: FlightWaypoint = FlightWaypoint(
|
||||
FlightWaypointType.SPLIT, split.x, split.y, altitude
|
||||
"SPLIT", FlightWaypointType.SPLIT, split.x, split.y, altitude
|
||||
)
|
||||
|
||||
# Cheat in a FlightWaypoint for the refuel point.
|
||||
refuel: Point = self.package.waypoints.refuel
|
||||
refuel_waypoint: FlightWaypoint = FlightWaypoint(
|
||||
FlightWaypointType.REFUEL, refuel.x, refuel.y, altitude
|
||||
"REFUEL", FlightWaypointType.REFUEL, refuel.x, refuel.y, altitude
|
||||
)
|
||||
|
||||
delay_target_to_split: timedelta = self.travel_time_between_waypoints(
|
||||
|
||||
@ -14,6 +14,8 @@ from typing import (
|
||||
|
||||
from dcs.mapping import Point
|
||||
|
||||
from game.ato.flightwaypoint import AltitudeReference, FlightWaypoint
|
||||
from game.ato.flightwaypointtype import FlightWaypointType
|
||||
from game.theater import (
|
||||
ControlPoint,
|
||||
MissionTarget,
|
||||
@ -22,8 +24,6 @@ from game.theater import (
|
||||
TheaterUnit,
|
||||
)
|
||||
from game.utils import Distance, meters, nautical_miles
|
||||
from game.ato.flightwaypointtype import FlightWaypointType
|
||||
from game.ato.flightwaypoint import FlightWaypoint
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game.ato.flight import Flight
|
||||
@ -68,25 +68,26 @@ class WaypointBuilder:
|
||||
"""
|
||||
position = departure.position
|
||||
if isinstance(departure, OffMapSpawn):
|
||||
waypoint = FlightWaypoint(
|
||||
return FlightWaypoint(
|
||||
"NAV",
|
||||
FlightWaypointType.NAV,
|
||||
position.x,
|
||||
position.y,
|
||||
meters(500) if self.is_helo else self.doctrine.rendezvous_altitude,
|
||||
description="Enter theater",
|
||||
pretty_name="Enter theater",
|
||||
)
|
||||
waypoint.name = "NAV"
|
||||
waypoint.alt_type = "BARO"
|
||||
waypoint.description = "Enter theater"
|
||||
waypoint.pretty_name = "Enter theater"
|
||||
else:
|
||||
waypoint = FlightWaypoint(
|
||||
FlightWaypointType.TAKEOFF, position.x, position.y, meters(0)
|
||||
)
|
||||
waypoint.name = "TAKEOFF"
|
||||
waypoint.alt_type = "RADIO"
|
||||
waypoint.description = "Takeoff"
|
||||
waypoint.pretty_name = "Takeoff"
|
||||
return waypoint
|
||||
|
||||
return FlightWaypoint(
|
||||
"TAKEOFF",
|
||||
FlightWaypointType.TAKEOFF,
|
||||
position.x,
|
||||
position.y,
|
||||
meters(0),
|
||||
alt_type="RADIO",
|
||||
description="Takeoff",
|
||||
pretty_name="Takeoff",
|
||||
)
|
||||
|
||||
def land(self, arrival: ControlPoint) -> FlightWaypoint:
|
||||
"""Create descent waypoint for the given arrival airfield or carrier.
|
||||
@ -96,29 +97,27 @@ class WaypointBuilder:
|
||||
"""
|
||||
position = arrival.position
|
||||
if isinstance(arrival, OffMapSpawn):
|
||||
waypoint = FlightWaypoint(
|
||||
return FlightWaypoint(
|
||||
"NAV",
|
||||
FlightWaypointType.NAV,
|
||||
position.x,
|
||||
position.y,
|
||||
meters(500) if self.is_helo else self.doctrine.rendezvous_altitude,
|
||||
description="Exit theater",
|
||||
pretty_name="Exit theater",
|
||||
)
|
||||
waypoint.name = "NAV"
|
||||
waypoint.alt_type = "BARO"
|
||||
waypoint.description = "Exit theater"
|
||||
waypoint.pretty_name = "Exit theater"
|
||||
else:
|
||||
waypoint = FlightWaypoint(
|
||||
FlightWaypointType.LANDING_POINT,
|
||||
position.x,
|
||||
position.y,
|
||||
meters(0),
|
||||
control_point=arrival,
|
||||
)
|
||||
waypoint.name = "LANDING"
|
||||
waypoint.alt_type = "RADIO"
|
||||
waypoint.description = "Land"
|
||||
waypoint.pretty_name = "Land"
|
||||
return waypoint
|
||||
|
||||
return FlightWaypoint(
|
||||
"LANDING",
|
||||
FlightWaypointType.LANDING_POINT,
|
||||
position.x,
|
||||
position.y,
|
||||
meters(0),
|
||||
alt_type="RADIO",
|
||||
description="Land",
|
||||
pretty_name="Land",
|
||||
control_point=arrival,
|
||||
)
|
||||
|
||||
def divert(self, divert: Optional[ControlPoint]) -> Optional[FlightWaypoint]:
|
||||
"""Create divert waypoint for the given arrival airfield or carrier.
|
||||
@ -130,6 +129,7 @@ class WaypointBuilder:
|
||||
return None
|
||||
|
||||
position = divert.position
|
||||
altitude_type: AltitudeReference
|
||||
if isinstance(divert, OffMapSpawn):
|
||||
if self.is_helo:
|
||||
altitude = meters(500)
|
||||
@ -140,88 +140,94 @@ class WaypointBuilder:
|
||||
altitude = meters(0)
|
||||
altitude_type = "RADIO"
|
||||
|
||||
waypoint = FlightWaypoint(
|
||||
return FlightWaypoint(
|
||||
"DIVERT",
|
||||
FlightWaypointType.DIVERT,
|
||||
position.x,
|
||||
position.y,
|
||||
altitude,
|
||||
alt_type=altitude_type,
|
||||
description="Divert",
|
||||
pretty_name="Divert",
|
||||
only_for_player=True,
|
||||
control_point=divert,
|
||||
)
|
||||
waypoint.alt_type = altitude_type
|
||||
waypoint.name = "DIVERT"
|
||||
waypoint.description = "Divert"
|
||||
waypoint.pretty_name = "Divert"
|
||||
waypoint.only_for_player = True
|
||||
return waypoint
|
||||
|
||||
def bullseye(self) -> FlightWaypoint:
|
||||
waypoint = FlightWaypoint(
|
||||
return FlightWaypoint(
|
||||
"BULLSEYE",
|
||||
FlightWaypointType.BULLSEYE,
|
||||
self._bullseye.position.x,
|
||||
self._bullseye.position.y,
|
||||
meters(0),
|
||||
description="Bullseye",
|
||||
pretty_name="Bullseye",
|
||||
only_for_player=True,
|
||||
)
|
||||
waypoint.pretty_name = "Bullseye"
|
||||
waypoint.description = "Bullseye"
|
||||
waypoint.name = "BULLSEYE"
|
||||
waypoint.only_for_player = True
|
||||
return waypoint
|
||||
|
||||
def hold(self, position: Point) -> FlightWaypoint:
|
||||
waypoint = FlightWaypoint(
|
||||
alt_type: AltitudeReference = "BARO"
|
||||
if self.is_helo:
|
||||
alt_type = "RADIO"
|
||||
|
||||
return FlightWaypoint(
|
||||
"HOLD",
|
||||
FlightWaypointType.LOITER,
|
||||
position.x,
|
||||
position.y,
|
||||
meters(500) if self.is_helo else self.doctrine.rendezvous_altitude,
|
||||
alt_type,
|
||||
description="Wait until push time",
|
||||
pretty_name="Hold",
|
||||
)
|
||||
if self.is_helo:
|
||||
waypoint.alt_type = "RADIO"
|
||||
waypoint.pretty_name = "Hold"
|
||||
waypoint.description = "Wait until push time"
|
||||
waypoint.name = "HOLD"
|
||||
return waypoint
|
||||
|
||||
def join(self, position: Point) -> FlightWaypoint:
|
||||
waypoint = FlightWaypoint(
|
||||
alt_type: AltitudeReference = "BARO"
|
||||
if self.is_helo:
|
||||
alt_type = "RADIO"
|
||||
|
||||
return FlightWaypoint(
|
||||
"JOIN",
|
||||
FlightWaypointType.JOIN,
|
||||
position.x,
|
||||
position.y,
|
||||
meters(80) if self.is_helo else self.doctrine.ingress_altitude,
|
||||
alt_type,
|
||||
description="Rendezvous with package",
|
||||
pretty_name="Join",
|
||||
)
|
||||
if self.is_helo:
|
||||
waypoint.alt_type = "RADIO"
|
||||
waypoint.pretty_name = "Join"
|
||||
waypoint.description = "Rendezvous with package"
|
||||
waypoint.name = "JOIN"
|
||||
return waypoint
|
||||
|
||||
def refuel(self, position: Point) -> FlightWaypoint:
|
||||
waypoint = FlightWaypoint(
|
||||
alt_type: AltitudeReference = "BARO"
|
||||
if self.is_helo:
|
||||
alt_type = "RADIO"
|
||||
|
||||
return FlightWaypoint(
|
||||
"REFUEL",
|
||||
FlightWaypointType.REFUEL,
|
||||
position.x,
|
||||
position.y,
|
||||
meters(80) if self.is_helo else self.doctrine.ingress_altitude,
|
||||
alt_type,
|
||||
description="Refuel from tanker",
|
||||
pretty_name="Refuel",
|
||||
)
|
||||
if self.is_helo:
|
||||
waypoint.alt_type = "RADIO"
|
||||
waypoint.pretty_name = "Refuel"
|
||||
waypoint.description = "Refuel from tanker"
|
||||
waypoint.name = "REFUEL"
|
||||
return waypoint
|
||||
|
||||
def split(self, position: Point) -> FlightWaypoint:
|
||||
waypoint = FlightWaypoint(
|
||||
alt_type: AltitudeReference = "BARO"
|
||||
if self.is_helo:
|
||||
alt_type = "RADIO"
|
||||
|
||||
return FlightWaypoint(
|
||||
"SPLIT",
|
||||
FlightWaypointType.SPLIT,
|
||||
position.x,
|
||||
position.y,
|
||||
meters(80) if self.is_helo else self.doctrine.ingress_altitude,
|
||||
alt_type,
|
||||
description="Depart from package",
|
||||
pretty_name="Split",
|
||||
)
|
||||
if self.is_helo:
|
||||
waypoint.alt_type = "RADIO"
|
||||
waypoint.pretty_name = "Split"
|
||||
waypoint.description = "Depart from package"
|
||||
waypoint.name = "SPLIT"
|
||||
return waypoint
|
||||
|
||||
def ingress(
|
||||
self,
|
||||
@ -229,33 +235,37 @@ class WaypointBuilder:
|
||||
position: Point,
|
||||
objective: MissionTarget,
|
||||
) -> FlightWaypoint:
|
||||
waypoint = FlightWaypoint(
|
||||
alt_type: AltitudeReference = "BARO"
|
||||
if self.is_helo:
|
||||
alt_type = "RADIO"
|
||||
|
||||
return FlightWaypoint(
|
||||
"INGRESS",
|
||||
ingress_type,
|
||||
position.x,
|
||||
position.y,
|
||||
meters(60) if self.is_helo else self.doctrine.ingress_altitude,
|
||||
alt_type,
|
||||
description=f"INGRESS on {objective.name}",
|
||||
pretty_name=f"INGRESS on {objective.name}",
|
||||
targets=objective.strike_targets,
|
||||
)
|
||||
if self.is_helo:
|
||||
waypoint.alt_type = "RADIO"
|
||||
waypoint.pretty_name = "INGRESS on " + objective.name
|
||||
waypoint.description = "INGRESS on " + objective.name
|
||||
waypoint.name = "INGRESS"
|
||||
waypoint.targets = objective.strike_targets
|
||||
return waypoint
|
||||
|
||||
def egress(self, position: Point, target: MissionTarget) -> FlightWaypoint:
|
||||
waypoint = FlightWaypoint(
|
||||
alt_type: AltitudeReference = "BARO"
|
||||
if self.is_helo:
|
||||
alt_type = "RADIO"
|
||||
|
||||
return FlightWaypoint(
|
||||
"EGRESS",
|
||||
FlightWaypointType.EGRESS,
|
||||
position.x,
|
||||
position.y,
|
||||
meters(60) if self.is_helo else self.doctrine.ingress_altitude,
|
||||
alt_type,
|
||||
description=f"EGRESS from {target.name}",
|
||||
pretty_name=f"EGRESS from {target.name}",
|
||||
)
|
||||
if self.is_helo:
|
||||
waypoint.alt_type = "RADIO"
|
||||
waypoint.pretty_name = "EGRESS from " + target.name
|
||||
waypoint.description = "EGRESS from " + target.name
|
||||
waypoint.name = "EGRESS"
|
||||
return waypoint
|
||||
|
||||
def bai_group(self, target: StrikeTarget) -> FlightWaypoint:
|
||||
return self._target_point(target, f"ATTACK {target.name}")
|
||||
@ -271,21 +281,20 @@ class WaypointBuilder:
|
||||
|
||||
@staticmethod
|
||||
def _target_point(target: StrikeTarget, description: str) -> FlightWaypoint:
|
||||
waypoint = FlightWaypoint(
|
||||
return FlightWaypoint(
|
||||
target.name,
|
||||
FlightWaypointType.TARGET_POINT,
|
||||
target.target.position.x,
|
||||
target.target.position.y,
|
||||
meters(0),
|
||||
"RADIO",
|
||||
description=description,
|
||||
pretty_name=description,
|
||||
# The target waypoints are only for the player's benefit. AI tasks for
|
||||
# the target are set on the ingress point so that they begin their attack
|
||||
# *before* reaching the target.
|
||||
only_for_player=True,
|
||||
)
|
||||
waypoint.description = description
|
||||
waypoint.pretty_name = description
|
||||
waypoint.name = target.name
|
||||
waypoint.alt_type = "RADIO"
|
||||
# The target waypoints are only for the player's benefit. AI tasks for
|
||||
# the target are set on the ingress point so they begin their attack
|
||||
# *before* reaching the target.
|
||||
waypoint.only_for_player = True
|
||||
return waypoint
|
||||
|
||||
def strike_area(self, target: MissionTarget) -> FlightWaypoint:
|
||||
return self._target_area(f"STRIKE {target.name}", target)
|
||||
@ -304,15 +313,15 @@ class WaypointBuilder:
|
||||
name: str, location: MissionTarget, flyover: bool = False
|
||||
) -> FlightWaypoint:
|
||||
waypoint = FlightWaypoint(
|
||||
name,
|
||||
FlightWaypointType.TARGET_GROUP_LOC,
|
||||
location.position.x,
|
||||
location.position.y,
|
||||
meters(0),
|
||||
"RADIO",
|
||||
description=name,
|
||||
pretty_name=name,
|
||||
)
|
||||
waypoint.description = name
|
||||
waypoint.pretty_name = name
|
||||
waypoint.name = name
|
||||
waypoint.alt_type = "RADIO"
|
||||
|
||||
# Most target waypoints are only for the player's benefit. AI tasks for
|
||||
# the target are set on the ingress point so they begin their attack
|
||||
@ -328,17 +337,16 @@ class WaypointBuilder:
|
||||
return waypoint
|
||||
|
||||
def cas(self, position: Point) -> FlightWaypoint:
|
||||
waypoint = FlightWaypoint(
|
||||
return FlightWaypoint(
|
||||
"CAS",
|
||||
FlightWaypointType.CAS,
|
||||
position.x,
|
||||
position.y,
|
||||
meters(60) if self.is_helo else meters(1000),
|
||||
"RADIO",
|
||||
description="Provide CAS",
|
||||
pretty_name="CAS",
|
||||
)
|
||||
waypoint.alt_type = "RADIO"
|
||||
waypoint.description = "Provide CAS"
|
||||
waypoint.name = "CAS"
|
||||
waypoint.pretty_name = "CAS"
|
||||
return waypoint
|
||||
|
||||
@staticmethod
|
||||
def race_track_start(position: Point, altitude: Distance) -> FlightWaypoint:
|
||||
@ -348,13 +356,15 @@ class WaypointBuilder:
|
||||
position: Position of the waypoint.
|
||||
altitude: Altitude of the racetrack.
|
||||
"""
|
||||
waypoint = FlightWaypoint(
|
||||
FlightWaypointType.PATROL_TRACK, position.x, position.y, altitude
|
||||
return FlightWaypoint(
|
||||
"RACETRACK START",
|
||||
FlightWaypointType.PATROL_TRACK,
|
||||
position.x,
|
||||
position.y,
|
||||
altitude,
|
||||
description="Orbit between this point and the next point",
|
||||
pretty_name="Race-track start",
|
||||
)
|
||||
waypoint.name = "RACETRACK START"
|
||||
waypoint.description = "Orbit between this point and the next point"
|
||||
waypoint.pretty_name = "Race-track start"
|
||||
return waypoint
|
||||
|
||||
@staticmethod
|
||||
def race_track_end(position: Point, altitude: Distance) -> FlightWaypoint:
|
||||
@ -364,13 +374,15 @@ class WaypointBuilder:
|
||||
position: Position of the waypoint.
|
||||
altitude: Altitude of the racetrack.
|
||||
"""
|
||||
waypoint = FlightWaypoint(
|
||||
FlightWaypointType.PATROL, position.x, position.y, altitude
|
||||
return FlightWaypoint(
|
||||
"RACETRACK END",
|
||||
FlightWaypointType.PATROL,
|
||||
position.x,
|
||||
position.y,
|
||||
altitude,
|
||||
description="Orbit between this point and the previous point",
|
||||
pretty_name="Race-track end",
|
||||
)
|
||||
waypoint.name = "RACETRACK END"
|
||||
waypoint.description = "Orbit between this point and the previous point"
|
||||
waypoint.pretty_name = "Race-track end"
|
||||
return waypoint
|
||||
|
||||
def race_track(
|
||||
self, start: Point, end: Point, altitude: Distance
|
||||
@ -396,11 +408,15 @@ class WaypointBuilder:
|
||||
altitude: Altitude of the racetrack.
|
||||
"""
|
||||
|
||||
waypoint = FlightWaypoint(FlightWaypointType.LOITER, start.x, start.y, altitude)
|
||||
waypoint.name = "ORBIT"
|
||||
waypoint.description = "Anchor and hold at this point"
|
||||
waypoint.pretty_name = "Orbit"
|
||||
return waypoint
|
||||
return FlightWaypoint(
|
||||
"ORBIT",
|
||||
FlightWaypointType.LOITER,
|
||||
start.x,
|
||||
start.y,
|
||||
altitude,
|
||||
description="Anchor and hold at this point",
|
||||
pretty_name="Orbit",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def sweep_start(position: Point, altitude: Distance) -> FlightWaypoint:
|
||||
@ -410,13 +426,15 @@ class WaypointBuilder:
|
||||
position: Position of the waypoint.
|
||||
altitude: Altitude of the sweep in meters.
|
||||
"""
|
||||
waypoint = FlightWaypoint(
|
||||
FlightWaypointType.INGRESS_SWEEP, position.x, position.y, altitude
|
||||
return FlightWaypoint(
|
||||
"SWEEP START",
|
||||
FlightWaypointType.INGRESS_SWEEP,
|
||||
position.x,
|
||||
position.y,
|
||||
altitude,
|
||||
description="Proceed to the target and engage enemy aircraft",
|
||||
pretty_name="Sweep start",
|
||||
)
|
||||
waypoint.name = "SWEEP START"
|
||||
waypoint.description = "Proceed to the target and engage enemy aircraft"
|
||||
waypoint.pretty_name = "Sweep start"
|
||||
return waypoint
|
||||
|
||||
@staticmethod
|
||||
def sweep_end(position: Point, altitude: Distance) -> FlightWaypoint:
|
||||
@ -426,13 +444,15 @@ class WaypointBuilder:
|
||||
position: Position of the waypoint.
|
||||
altitude: Altitude of the sweep in meters.
|
||||
"""
|
||||
waypoint = FlightWaypoint(
|
||||
FlightWaypointType.EGRESS, position.x, position.y, altitude
|
||||
return FlightWaypoint(
|
||||
"SWEEP END",
|
||||
FlightWaypointType.EGRESS,
|
||||
position.x,
|
||||
position.y,
|
||||
altitude,
|
||||
description="End of sweep",
|
||||
pretty_name="Sweep end",
|
||||
)
|
||||
waypoint.name = "SWEEP END"
|
||||
waypoint.description = "End of sweep"
|
||||
waypoint.pretty_name = "Sweep end"
|
||||
return waypoint
|
||||
|
||||
def sweep(
|
||||
self, start: Point, end: Point, altitude: Distance
|
||||
@ -457,6 +477,10 @@ class WaypointBuilder:
|
||||
ingress: The package ingress point.
|
||||
target: The mission target.
|
||||
"""
|
||||
alt_type: AltitudeReference = "BARO"
|
||||
if self.is_helo:
|
||||
alt_type = "RADIO"
|
||||
|
||||
# This would preferably be no points at all, and instead the Escort task
|
||||
# would begin on the join point and end on the split point, however the
|
||||
# escort task does not appear to work properly (see the longer
|
||||
@ -464,18 +488,16 @@ class WaypointBuilder:
|
||||
# the escort flights a flight plan including the ingress point and target area.
|
||||
ingress_wp = self.ingress(FlightWaypointType.INGRESS_ESCORT, ingress, target)
|
||||
|
||||
waypoint = FlightWaypoint(
|
||||
return ingress_wp, FlightWaypoint(
|
||||
"TARGET",
|
||||
FlightWaypointType.TARGET_GROUP_LOC,
|
||||
target.position.x,
|
||||
target.position.y,
|
||||
meters(60) if self.is_helo else self.doctrine.ingress_altitude,
|
||||
alt_type,
|
||||
description="Escort the package",
|
||||
pretty_name="Target area",
|
||||
)
|
||||
if self.is_helo:
|
||||
waypoint.alt_type = "RADIO"
|
||||
waypoint.name = "TARGET"
|
||||
waypoint.description = "Escort the package"
|
||||
waypoint.pretty_name = "Target area"
|
||||
return ingress_wp, waypoint
|
||||
|
||||
@staticmethod
|
||||
def pickup(control_point: ControlPoint) -> FlightWaypoint:
|
||||
@ -484,17 +506,16 @@ class WaypointBuilder:
|
||||
Args:
|
||||
control_point: Pick up location.
|
||||
"""
|
||||
waypoint = FlightWaypoint(
|
||||
return FlightWaypoint(
|
||||
"PICKUP",
|
||||
FlightWaypointType.PICKUP,
|
||||
control_point.position.x,
|
||||
control_point.position.y,
|
||||
meters(0),
|
||||
"RADIO",
|
||||
description=f"Pick up cargo from {control_point}",
|
||||
pretty_name="Pick up location",
|
||||
)
|
||||
waypoint.alt_type = "RADIO"
|
||||
waypoint.name = "PICKUP"
|
||||
waypoint.description = f"Pick up cargo from {control_point}"
|
||||
waypoint.pretty_name = "Pick up location"
|
||||
return waypoint
|
||||
|
||||
@staticmethod
|
||||
def drop_off(control_point: ControlPoint) -> FlightWaypoint:
|
||||
@ -503,18 +524,17 @@ class WaypointBuilder:
|
||||
Args:
|
||||
control_point: Drop-off location.
|
||||
"""
|
||||
waypoint = FlightWaypoint(
|
||||
return FlightWaypoint(
|
||||
"DROP OFF",
|
||||
FlightWaypointType.PICKUP,
|
||||
control_point.position.x,
|
||||
control_point.position.y,
|
||||
meters(0),
|
||||
"RADIO",
|
||||
description=f"Drop off cargo at {control_point}",
|
||||
pretty_name="Drop off location",
|
||||
control_point=control_point,
|
||||
)
|
||||
waypoint.alt_type = "RADIO"
|
||||
waypoint.name = "DROP OFF"
|
||||
waypoint.description = f"Drop off cargo at {control_point}"
|
||||
waypoint.pretty_name = "Drop off location"
|
||||
return waypoint
|
||||
|
||||
@staticmethod
|
||||
def nav(
|
||||
@ -527,15 +547,20 @@ class WaypointBuilder:
|
||||
altitude: Altitude of the waypoint.
|
||||
altitude_is_agl: True for altitude is AGL. False if altitude is MSL.
|
||||
"""
|
||||
waypoint = FlightWaypoint(
|
||||
FlightWaypointType.NAV, position.x, position.y, altitude
|
||||
)
|
||||
alt_type: AltitudeReference = "BARO"
|
||||
if altitude_is_agl:
|
||||
waypoint.alt_type = "RADIO"
|
||||
waypoint.name = "NAV"
|
||||
waypoint.description = "NAV"
|
||||
waypoint.pretty_name = "Nav"
|
||||
return waypoint
|
||||
alt_type = "RADIO"
|
||||
|
||||
return FlightWaypoint(
|
||||
"NAV",
|
||||
FlightWaypointType.NAV,
|
||||
position.x,
|
||||
position.y,
|
||||
altitude,
|
||||
alt_type,
|
||||
description="NAV",
|
||||
pretty_name="Nav",
|
||||
)
|
||||
|
||||
def nav_path(
|
||||
self, a: Point, b: Point, altitude: Distance, altitude_is_agl: bool = False
|
||||
|
||||
@ -768,7 +768,7 @@ class Waypoint {
|
||||
}
|
||||
|
||||
position() {
|
||||
return this.waypoint.latlng;
|
||||
return this.waypoint.position;
|
||||
}
|
||||
|
||||
shouldMark() {
|
||||
@ -783,10 +783,8 @@ class Waypoint {
|
||||
}
|
||||
|
||||
async description(dragging) {
|
||||
const alt = Math.floor(
|
||||
this.waypoint.alt.distance_in_meters * METERS_TO_FEET
|
||||
);
|
||||
const altRef = this.waypoint.alt_type == "BARO" ? "MSL" : "AGL";
|
||||
const alt = this.waypoint.altitude_ft;
|
||||
const altRef = this.waypoint.altitude_reference;
|
||||
return (
|
||||
`${this.number} ${this.waypoint.name}<br />` +
|
||||
`${alt} ft ${altRef}<br />` +
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user