diff --git a/game/ato/flightwaypoint.py b/game/ato/flightwaypoint.py
index 1d9b01b6..1eb84114 100644
--- a/game/ato/flightwaypoint.py
+++ b/game/ato/flightwaypoint.py
@@ -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))
diff --git a/game/server/waypoints/models.py b/game/server/waypoints/models.py
new file mode 100644
index 00000000..a959bf40
--- /dev/null
+++ b/game/server/waypoints/models.py
@@ -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,
+ )
diff --git a/game/server/waypoints/routes.py b/game/server/waypoints/routes.py
index 5400c5a2..367148d6 100644
--- a/game/server/waypoints/routes.py
+++ b/game/server/waypoints/routes.py
@@ -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
diff --git a/game/utils.py b/game/utils.py
index aceeff24..ad8f8fed 100644
--- a/game/utils.py
+++ b/game/utils.py
@@ -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
diff --git a/gen/flights/flightplan.py b/gen/flights/flightplan.py
index a1b21ab6..a3577ee7 100644
--- a/gen/flights/flightplan.py
+++ b/gen/flights/flightplan.py
@@ -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(
diff --git a/gen/flights/waypointbuilder.py b/gen/flights/waypointbuilder.py
index b62fd669..8e205220 100644
--- a/gen/flights/waypointbuilder.py
+++ b/gen/flights/waypointbuilder.py
@@ -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
diff --git a/resources/ui/map/map.js b/resources/ui/map/map.js
index b23540a0..83136fbc 100644
--- a/resources/ui/map/map.js
+++ b/resources/ui/map/map.js
@@ -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}
` +
`${alt} ft ${altRef}
` +