diff --git a/game/ato/flightplans/airassault.py b/game/ato/flightplans/airassault.py index 8df43e57..015d287d 100644 --- a/game/ato/flightplans/airassault.py +++ b/game/ato/flightplans/airassault.py @@ -10,6 +10,7 @@ from game.theater.missiontarget import MissionTarget from game.utils import Distance, feet, meters from .ibuilder import IBuilder from .planningerror import PlanningError +from .uizonedisplay import UiZone, UiZoneDisplay from .waypointbuilder import WaypointBuilder from ..flightwaypoint import FlightWaypointType @@ -45,7 +46,7 @@ class AirAssaultLayout(StandardLayout): yield self.bullseye -class AirAssaultFlightPlan(StandardFlightPlan[AirAssaultLayout]): +class AirAssaultFlightPlan(StandardFlightPlan[AirAssaultLayout], UiZoneDisplay): @staticmethod def builder_type() -> Type[Builder]: return Builder @@ -70,6 +71,12 @@ class AirAssaultFlightPlan(StandardFlightPlan[AirAssaultLayout]): def mission_departure_time(self) -> timedelta: return self.package.time_over_target + def ui_zone(self) -> UiZone: + return UiZone( + [self.layout.target.position], + self.ctld_target_zone_radius, + ) + class Builder(IBuilder[AirAssaultFlightPlan, AirAssaultLayout]): def layout(self) -> AirAssaultLayout: diff --git a/game/ato/flightplans/cas.py b/game/ato/flightplans/cas.py index b09cec42..1f9b9ac9 100644 --- a/game/ato/flightplans/cas.py +++ b/game/ato/flightplans/cas.py @@ -10,6 +10,7 @@ from game.utils import Distance, Speed, kph, meters from .ibuilder import IBuilder from .invalidobjectivelocation import InvalidObjectiveLocation from .patrolling import PatrollingFlightPlan, PatrollingLayout +from .uizonedisplay import UiZone, UiZoneDisplay from .waypointbuilder import WaypointBuilder from ..flightwaypointtype import FlightWaypointType @@ -34,7 +35,7 @@ class CasLayout(PatrollingLayout): yield self.bullseye -class CasFlightPlan(PatrollingFlightPlan[CasLayout]): +class CasFlightPlan(PatrollingFlightPlan[CasLayout], UiZoneDisplay): @staticmethod def builder_type() -> Type[Builder]: return Builder @@ -66,6 +67,12 @@ class CasFlightPlan(PatrollingFlightPlan[CasLayout]): def dismiss_escort_at(self) -> FlightWaypoint | None: return self.layout.patrol_end + def ui_zone(self) -> UiZone: + return UiZone( + [self.layout.target.position], + self.engagement_distance, + ) + class Builder(IBuilder[CasFlightPlan, CasLayout]): def layout(self) -> CasLayout: diff --git a/game/ato/flightplans/patrolling.py b/game/ato/flightplans/patrolling.py index c531fdfb..eecc1828 100644 --- a/game/ato/flightplans/patrolling.py +++ b/game/ato/flightplans/patrolling.py @@ -9,6 +9,7 @@ from typing import Any, TYPE_CHECKING, TypeGuard, TypeVar from game.ato.flightplans.standard import StandardFlightPlan, StandardLayout from game.typeguard import self_type_guard from game.utils import Distance, Speed +from .uizonedisplay import UiZone, UiZoneDisplay if TYPE_CHECKING: from ..flightwaypoint import FlightWaypoint @@ -37,7 +38,7 @@ class PatrollingLayout(StandardLayout): LayoutT = TypeVar("LayoutT", bound=PatrollingLayout) -class PatrollingFlightPlan(StandardFlightPlan[LayoutT], ABC): +class PatrollingFlightPlan(StandardFlightPlan[LayoutT], UiZoneDisplay, ABC): @property @abstractmethod def patrol_duration(self) -> timedelta: @@ -97,3 +98,9 @@ class PatrollingFlightPlan(StandardFlightPlan[LayoutT], ABC): self, flight_plan: FlightPlan[Any] ) -> TypeGuard[PatrollingFlightPlan[Any]]: return True + + def ui_zone(self) -> UiZone: + return UiZone( + [self.layout.patrol_start.position, self.layout.patrol_end.position], + self.engagement_distance, + ) diff --git a/game/ato/flightplans/uizonedisplay.py b/game/ato/flightplans/uizonedisplay.py new file mode 100644 index 00000000..8d880c87 --- /dev/null +++ b/game/ato/flightplans/uizonedisplay.py @@ -0,0 +1,18 @@ +import abc +from dataclasses import dataclass + +from dcs import Point + +from game.utils import Distance + + +@dataclass(frozen=True) +class UiZone: + points: list[Point] + radius: Distance + + +class UiZoneDisplay(abc.ABC): + @abc.abstractmethod + def ui_zone(self) -> UiZone: + ... diff --git a/game/server/flights/routes.py b/game/server/flights/routes.py index 6ce69315..de1f99bd 100644 --- a/game/server/flights/routes.py +++ b/game/server/flights/routes.py @@ -4,9 +4,7 @@ from fastapi import APIRouter, Depends from shapely.geometry import LineString, Point as ShapelyPoint from game import Game -from game.ato.flightplans.airassault import AirAssaultFlightPlan -from game.ato.flightplans.cas import CasFlightPlan -from game.ato.flightplans.patrolling import PatrollingFlightPlan +from game.ato.flightplans.uizonedisplay import UiZoneDisplay from game.server import GameContext from game.server.flights.models import FlightJs from game.server.leaflet import LeafletPoly, ShapelyUtil @@ -40,23 +38,12 @@ def commit_boundary( flight_id: UUID, game: Game = Depends(GameContext.require) ) -> LeafletPoly: flight = game.db.flights.get(flight_id) - if isinstance(flight.flight_plan, CasFlightPlan) or isinstance( - flight.flight_plan, AirAssaultFlightPlan - ): - # Special Commit boundary for CAS and AirAssault - center = flight.flight_plan.layout.target.position - commit_center = ShapelyPoint(center.x, center.y) - elif isinstance(flight.flight_plan, PatrollingFlightPlan): - # Commit boundary for standard patrolling flight plan - start = flight.flight_plan.layout.patrol_start - end = flight.flight_plan.layout.patrol_end - commit_center = LineString( - [ - ShapelyPoint(start.x, start.y), - ShapelyPoint(end.x, end.y), - ] - ) - else: + if not isinstance(flight.flight_plan, UiZoneDisplay): return [] - bubble = commit_center.buffer(flight.flight_plan.engagement_distance.meters) + zone = flight.flight_plan.ui_zone() + if len(zone.points) == 1: + center = ShapelyPoint(zone.points[0].x, zone.points[0].y) + else: + center = LineString([ShapelyPoint(p.x, p.y) for p in zone.points]) + bubble = center.buffer(zone.radius.meters) return ShapelyUtil.poly_to_leaflet(bubble, game.theater)