mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Migrate IP placement to WaypointSolver.
This commit is contained in:
parent
5cb4c363e3
commit
6b6c4f4112
@ -50,14 +50,6 @@ const injectedRtkApi = api.injectEndpoints({
|
||||
url: `/debug/waypoint-geometries/hold/${queryArg.flightId}`,
|
||||
}),
|
||||
}),
|
||||
getDebugIpZones: build.query<
|
||||
GetDebugIpZonesApiResponse,
|
||||
GetDebugIpZonesApiArg
|
||||
>({
|
||||
query: (queryArg) => ({
|
||||
url: `/debug/waypoint-geometries/ip/${queryArg.flightId}`,
|
||||
}),
|
||||
}),
|
||||
getDebugJoinZones: build.query<
|
||||
GetDebugJoinZonesApiResponse,
|
||||
GetDebugJoinZonesApiArg
|
||||
@ -245,11 +237,6 @@ export type GetDebugHoldZonesApiResponse =
|
||||
export type GetDebugHoldZonesApiArg = {
|
||||
flightId: string;
|
||||
};
|
||||
export type GetDebugIpZonesApiResponse =
|
||||
/** status 200 Successful Response */ IpZones;
|
||||
export type GetDebugIpZonesApiArg = {
|
||||
flightId: string;
|
||||
};
|
||||
export type GetDebugJoinZonesApiResponse =
|
||||
/** status 200 Successful Response */ JoinZones;
|
||||
export type GetDebugJoinZonesApiArg = {
|
||||
@ -379,14 +366,6 @@ export type HoldZones = {
|
||||
permissibleZones: LatLng[][][];
|
||||
preferredLines: LatLng[][];
|
||||
};
|
||||
export type IpZones = {
|
||||
homeBubble: LatLng[][];
|
||||
ipBubble: LatLng[][];
|
||||
permissibleZone: LatLng[][];
|
||||
safeZones: LatLng[][][];
|
||||
preferredThreatenedZones: LatLng[][][];
|
||||
tolerableThreatenedLines: LatLng[][];
|
||||
};
|
||||
export type JoinZones = {
|
||||
homeBubble: LatLng[][];
|
||||
targetBubble: LatLng[][];
|
||||
@ -499,7 +478,6 @@ export const {
|
||||
useSetControlPointDestinationMutation,
|
||||
useClearControlPointDestinationMutation,
|
||||
useGetDebugHoldZonesQuery,
|
||||
useGetDebugIpZonesQuery,
|
||||
useGetDebugJoinZonesQuery,
|
||||
useListFlightsQuery,
|
||||
useGetFlightByIdQuery,
|
||||
|
||||
@ -30,11 +30,6 @@ export const liberationApi = _liberationApi.enhanceEndpoints({
|
||||
{ type: Tags.FLIGHT_PLAN, id: arg.flightId },
|
||||
],
|
||||
},
|
||||
getDebugIpZones: {
|
||||
providesTags: (result, error, arg) => [
|
||||
{ type: Tags.FLIGHT_PLAN, id: arg.flightId },
|
||||
],
|
||||
},
|
||||
getDebugJoinZones: {
|
||||
providesTags: (result, error, arg) => [
|
||||
{ type: Tags.FLIGHT_PLAN, id: arg.flightId },
|
||||
|
||||
@ -1,96 +0,0 @@
|
||||
import { useGetDebugIpZonesQuery } from "../../api/liberationApi";
|
||||
import { LayerGroup, Polygon, Polyline } from "react-leaflet";
|
||||
|
||||
interface IpZonesProps {
|
||||
flightId: string;
|
||||
}
|
||||
|
||||
function IpZones(props: IpZonesProps) {
|
||||
const { data, error, isLoading } = useGetDebugIpZonesQuery({
|
||||
flightId: props.flightId,
|
||||
});
|
||||
|
||||
if (isLoading) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
console.error("Error while loading waypoint IP zone info", error);
|
||||
return <></>;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
console.log("Waypoint IP zone returned empty response");
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Polygon
|
||||
positions={data.homeBubble}
|
||||
color="#ffff00"
|
||||
fillOpacity={0.1}
|
||||
interactive={false}
|
||||
/>
|
||||
<Polygon
|
||||
positions={data.ipBubble}
|
||||
color="#bb89ff"
|
||||
fillOpacity={0.1}
|
||||
interactive={false}
|
||||
/>
|
||||
<Polygon
|
||||
positions={data.permissibleZone}
|
||||
color="#ffffff"
|
||||
fillOpacity={0.1}
|
||||
interactive={false}
|
||||
/>
|
||||
|
||||
{data.safeZones.map((zone, idx) => {
|
||||
return (
|
||||
<Polygon
|
||||
key={idx}
|
||||
positions={zone}
|
||||
color="#80BA80"
|
||||
fillOpacity={0.1}
|
||||
interactive={false}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
{data.preferredThreatenedZones.map((zone, idx) => {
|
||||
return (
|
||||
<Polygon
|
||||
key={idx}
|
||||
positions={zone}
|
||||
color="#fe7d0a"
|
||||
fillOpacity={0.1}
|
||||
interactive={false}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
{data.tolerableThreatenedLines.map((line, idx) => {
|
||||
return (
|
||||
<Polyline
|
||||
key={idx}
|
||||
positions={line}
|
||||
color="#80BA80"
|
||||
interactive={false}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
interface IpZonesLayerProps {
|
||||
flightId: string | null;
|
||||
}
|
||||
|
||||
export function IpZonesLayer(props: IpZonesLayerProps) {
|
||||
return (
|
||||
<LayerGroup>
|
||||
{props.flightId ? <IpZones flightId={props.flightId} /> : <></>}
|
||||
</LayerGroup>
|
||||
);
|
||||
}
|
||||
@ -1,7 +1,6 @@
|
||||
import { selectSelectedFlightId } from "../../api/flightsSlice";
|
||||
import { useAppSelector } from "../../app/hooks";
|
||||
import { HoldZonesLayer } from "./HoldZones";
|
||||
import { IpZonesLayer } from "./IpZones";
|
||||
import { JoinZonesLayer } from "./JoinZones";
|
||||
import { LayersControl } from "react-leaflet";
|
||||
|
||||
@ -16,9 +15,6 @@ export function WaypointDebugZonesControls() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<LayersControl.Overlay name="IP zones">
|
||||
<IpZonesLayer flightId={selectedFlightId} />
|
||||
</LayersControl.Overlay>
|
||||
<LayersControl.Overlay name="Join zones">
|
||||
<JoinZonesLayer flightId={selectedFlightId} />
|
||||
</LayersControl.Overlay>
|
||||
|
||||
@ -44,7 +44,11 @@ class IBuilder(ABC, Generic[FlightPlanT, LayoutT]):
|
||||
) from ex
|
||||
|
||||
def _generate_package_waypoints_if_needed(self) -> None:
|
||||
if self.package.waypoints is None:
|
||||
# Package waypoints are only valid for offensive missions. Skip this if the
|
||||
# target is friendly.
|
||||
if self.package.waypoints is None and not self.package.target.is_friendly(
|
||||
self.is_player
|
||||
):
|
||||
self.package.waypoints = PackageWaypoints.create(
|
||||
self.package, self.coalition
|
||||
)
|
||||
|
||||
@ -6,8 +6,10 @@ from typing import TYPE_CHECKING
|
||||
from dcs import Point
|
||||
|
||||
from game.ato.flightplans.waypointbuilder import WaypointBuilder
|
||||
from game.flightplan import IpZoneGeometry, JoinZoneGeometry
|
||||
from game.flightplan import JoinZoneGeometry
|
||||
from game.flightplan.ipsolver import IpSolver
|
||||
from game.flightplan.refuelzonegeometry import RefuelZoneGeometry
|
||||
from game.utils import dcs_to_shapely_point
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game.ato import Package
|
||||
@ -26,11 +28,15 @@ class PackageWaypoints:
|
||||
origin = package.departure_closest_to_target()
|
||||
|
||||
# Start by picking the best IP for the attack.
|
||||
ingress_point = IpZoneGeometry(
|
||||
package.target.position,
|
||||
origin.position,
|
||||
coalition,
|
||||
).find_best_ip()
|
||||
ingress_point_shapely = IpSolver(
|
||||
dcs_to_shapely_point(origin.position),
|
||||
dcs_to_shapely_point(package.target.position),
|
||||
coalition.doctrine,
|
||||
coalition.opponent.threat_zone.all,
|
||||
).solve()
|
||||
ingress_point = origin.position.new_in_same_map(
|
||||
ingress_point_shapely.x, ingress_point_shapely.y
|
||||
)
|
||||
|
||||
join_point = JoinZoneGeometry(
|
||||
package.target.position,
|
||||
|
||||
@ -1,3 +1,2 @@
|
||||
from .holdzonegeometry import HoldZoneGeometry
|
||||
from .ipzonegeometry import IpZoneGeometry
|
||||
from .joinzonegeometry import JoinZoneGeometry
|
||||
|
||||
173
game/flightplan/ipsolver.py
Normal file
173
game/flightplan/ipsolver.py
Normal file
@ -0,0 +1,173 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterator
|
||||
from typing import Any
|
||||
|
||||
from shapely.geometry import MultiPolygon, Point
|
||||
from shapely.geometry.base import BaseGeometry
|
||||
|
||||
from game.data.doctrine import Doctrine
|
||||
from game.flightplan.waypointsolver import WaypointSolver
|
||||
from game.flightplan.waypointstrategy import WaypointStrategy
|
||||
from game.utils import meters, nautical_miles
|
||||
|
||||
MIN_DISTANCE_FROM_DEPARTURE = nautical_miles(5)
|
||||
|
||||
|
||||
class ThreatTolerantIpStrategy(WaypointStrategy):
|
||||
def __init__(
|
||||
self,
|
||||
departure: Point,
|
||||
target: Point,
|
||||
doctrine: Doctrine,
|
||||
threat_zones: MultiPolygon,
|
||||
) -> None:
|
||||
super().__init__(threat_zones)
|
||||
self.prerequisite(target).min_distance_from(
|
||||
departure, doctrine.min_ingress_distance
|
||||
)
|
||||
self.require().at_least(MIN_DISTANCE_FROM_DEPARTURE).away_from(departure)
|
||||
self.require().at_most(meters(departure.distance(target))).away_from(departure)
|
||||
self.require().at_least(doctrine.min_ingress_distance).away_from(target)
|
||||
max_ip_range = min(
|
||||
doctrine.max_ingress_distance, meters(departure.distance(target))
|
||||
)
|
||||
self.require().at_most(max_ip_range).away_from(target)
|
||||
self.threat_tolerance(target, max_ip_range, nautical_miles(5))
|
||||
self.nearest(departure)
|
||||
|
||||
|
||||
class UnsafeIpStrategy(WaypointStrategy):
|
||||
def __init__(
|
||||
self,
|
||||
departure: Point,
|
||||
target: Point,
|
||||
doctrine: Doctrine,
|
||||
threat_zones: MultiPolygon,
|
||||
) -> None:
|
||||
super().__init__(threat_zones)
|
||||
self.prerequisite(target).min_distance_from(
|
||||
departure, doctrine.min_ingress_distance
|
||||
)
|
||||
self.require().at_least(MIN_DISTANCE_FROM_DEPARTURE).away_from(
|
||||
departure, "departure"
|
||||
)
|
||||
self.require().at_most(meters(departure.distance(target))).away_from(
|
||||
departure, "departure"
|
||||
)
|
||||
self.require().at_least(doctrine.min_ingress_distance).away_from(
|
||||
target, "target"
|
||||
)
|
||||
max_ip_range = min(
|
||||
doctrine.max_ingress_distance, meters(departure.distance(target))
|
||||
)
|
||||
self.require().at_most(max_ip_range).away_from(target, "target")
|
||||
self.nearest(departure)
|
||||
|
||||
|
||||
class SafeIpStrategy(WaypointStrategy):
|
||||
def __init__(
|
||||
self,
|
||||
departure: Point,
|
||||
target: Point,
|
||||
doctrine: Doctrine,
|
||||
threat_zones: MultiPolygon,
|
||||
) -> None:
|
||||
super().__init__(threat_zones)
|
||||
self.prerequisite(departure).is_safe()
|
||||
self.prerequisite(target).min_distance_from(
|
||||
departure, doctrine.min_ingress_distance
|
||||
)
|
||||
self.require().at_least(MIN_DISTANCE_FROM_DEPARTURE).away_from(
|
||||
departure, "departure"
|
||||
)
|
||||
self.require().at_most(meters(departure.distance(target))).away_from(
|
||||
departure, "departure"
|
||||
)
|
||||
self.require().at_least(doctrine.min_ingress_distance).away_from(
|
||||
target, "target"
|
||||
)
|
||||
self.require().at_most(
|
||||
min(doctrine.max_ingress_distance, meters(departure.distance(target)))
|
||||
).away_from(target, "target")
|
||||
self.require().safe()
|
||||
self.nearest(departure)
|
||||
|
||||
|
||||
class SafeBackTrackingIpStrategy(WaypointStrategy):
|
||||
def __init__(
|
||||
self,
|
||||
departure: Point,
|
||||
target: Point,
|
||||
doctrine: Doctrine,
|
||||
threat_zones: MultiPolygon,
|
||||
) -> None:
|
||||
super().__init__(threat_zones)
|
||||
self.require().at_least(MIN_DISTANCE_FROM_DEPARTURE).away_from(
|
||||
departure, "departure"
|
||||
)
|
||||
self.require().at_least(doctrine.min_ingress_distance).away_from(
|
||||
target, "target"
|
||||
)
|
||||
self.require().at_most(doctrine.max_ingress_distance).away_from(
|
||||
target, "target"
|
||||
)
|
||||
self.require().safe()
|
||||
self.nearest(departure)
|
||||
|
||||
|
||||
class UnsafeBackTrackingIpStrategy(WaypointStrategy):
|
||||
def __init__(
|
||||
self,
|
||||
departure: Point,
|
||||
target: Point,
|
||||
doctrine: Doctrine,
|
||||
threat_zones: MultiPolygon,
|
||||
) -> None:
|
||||
super().__init__(threat_zones)
|
||||
self.require().at_least(MIN_DISTANCE_FROM_DEPARTURE).away_from(
|
||||
departure, "departure"
|
||||
)
|
||||
self.require().at_least(doctrine.min_ingress_distance).away_from(
|
||||
target, "target"
|
||||
)
|
||||
self.require().at_most(doctrine.max_ingress_distance).away_from(
|
||||
target, "target"
|
||||
)
|
||||
self.nearest(departure)
|
||||
|
||||
|
||||
class IpSolver(WaypointSolver):
|
||||
def __init__(
|
||||
self,
|
||||
departure: Point,
|
||||
target: Point,
|
||||
doctrine: Doctrine,
|
||||
threat_zones: MultiPolygon,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.departure = departure
|
||||
self.target = target
|
||||
self.doctrine = doctrine
|
||||
self.threat_zones = threat_zones
|
||||
|
||||
self.add_strategy(SafeIpStrategy(departure, target, doctrine, threat_zones))
|
||||
self.add_strategy(
|
||||
ThreatTolerantIpStrategy(departure, target, doctrine, threat_zones)
|
||||
)
|
||||
self.add_strategy(UnsafeIpStrategy(departure, target, doctrine, threat_zones))
|
||||
self.add_strategy(
|
||||
SafeBackTrackingIpStrategy(departure, target, doctrine, threat_zones)
|
||||
)
|
||||
# TODO: The cases that require this are not covered by any tests.
|
||||
self.add_strategy(
|
||||
UnsafeBackTrackingIpStrategy(departure, target, doctrine, threat_zones)
|
||||
)
|
||||
|
||||
def describe_metadata(self) -> dict[str, Any]:
|
||||
return {"doctrine": self.doctrine.name}
|
||||
|
||||
def describe_inputs(self) -> Iterator[tuple[str, BaseGeometry]]:
|
||||
yield "departure", self.departure
|
||||
yield "target", self.target
|
||||
yield "threat_zones", self.threat_zones
|
||||
@ -1,165 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import shapely.ops
|
||||
from dcs import Point
|
||||
from shapely.geometry import MultiPolygon, Point as ShapelyPoint, MultiLineString
|
||||
|
||||
from game.utils import meters, nautical_miles
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game.coalition import Coalition
|
||||
|
||||
|
||||
class IpZoneGeometry:
|
||||
"""Defines the zones used for finding optimal IP placement.
|
||||
|
||||
The zones themselves are stored in the class rather than just the resulting IP so
|
||||
that the zones can be drawn in the map for debugging purposes.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
target: Point,
|
||||
home: Point,
|
||||
coalition: Coalition,
|
||||
) -> None:
|
||||
self._target = target
|
||||
self.threat_zone = coalition.opponent.threat_zone.all
|
||||
self.home = ShapelyPoint(home.x, home.y)
|
||||
|
||||
max_ip_distance = coalition.doctrine.max_ingress_distance
|
||||
min_ip_distance = coalition.doctrine.min_ingress_distance
|
||||
|
||||
# The minimum distance between the home location and the IP.
|
||||
min_distance_from_home = nautical_miles(5)
|
||||
|
||||
# The distance that is expected to be needed between the beginning of the attack
|
||||
# and weapon release. This buffers the threat zone to give a 5nm window between
|
||||
# the edge of the "safe" zone and the actual threat so that "safe" IPs are less
|
||||
# likely to end up with the attacker entering a threatened area.
|
||||
attack_distance_buffer = nautical_miles(5)
|
||||
|
||||
home_threatened = coalition.opponent.threat_zone.threatened(home)
|
||||
|
||||
shapely_target = ShapelyPoint(target.x, target.y)
|
||||
home_to_target_distance = meters(home.distance_to_point(target))
|
||||
|
||||
self.home_bubble = self.home.buffer(home_to_target_distance.meters).difference(
|
||||
self.home.buffer(min_distance_from_home.meters)
|
||||
)
|
||||
|
||||
# If the home zone is not threatened and home is within LAR, constrain the max
|
||||
# range to the home-to-target distance to prevent excessive backtracking.
|
||||
#
|
||||
# If the home zone *is* threatened, we need to back out of the zone to
|
||||
# rendezvous anyway.
|
||||
if not home_threatened and (
|
||||
min_ip_distance < home_to_target_distance < max_ip_distance
|
||||
):
|
||||
max_ip_distance = home_to_target_distance
|
||||
max_ip_bubble = shapely_target.buffer(max_ip_distance.meters)
|
||||
min_ip_bubble = shapely_target.buffer(min_ip_distance.meters)
|
||||
self.ip_bubble = max_ip_bubble.difference(min_ip_bubble)
|
||||
|
||||
# The intersection of the home bubble and IP bubble will be all the points that
|
||||
# are within the valid IP range that are not farther from home than the target
|
||||
# is. However, if the origin airfield is threatened but there are safe
|
||||
# placements for the IP, we should not constrain to the home zone. In this case
|
||||
# we'll either end up with a safe zone outside the home zone and pick the
|
||||
# closest point in to to home (minimizing backtracking), or we'll have no safe
|
||||
# IP anywhere within range of the target, and we'll later pick the IP nearest
|
||||
# the edge of the threat zone.
|
||||
if home_threatened:
|
||||
self.permissible_zone = self.ip_bubble
|
||||
else:
|
||||
self.permissible_zone = self.ip_bubble.intersection(self.home_bubble)
|
||||
|
||||
if self.permissible_zone.is_empty:
|
||||
# If home is closer to the target than the min range, there will not be an
|
||||
# IP solution that's close enough to home, in which case we need to ignore
|
||||
# the home bubble.
|
||||
self.permissible_zone = self.ip_bubble
|
||||
|
||||
safe_zones = self.permissible_zone.difference(
|
||||
self.threat_zone.buffer(attack_distance_buffer.meters)
|
||||
)
|
||||
|
||||
if not isinstance(safe_zones, MultiPolygon):
|
||||
safe_zones = MultiPolygon([safe_zones])
|
||||
self.safe_zones = safe_zones
|
||||
|
||||
# See explanation where this is used in _unsafe_ip.
|
||||
# https://github.com/dcs-liberation/dcs_liberation/issues/2754
|
||||
preferred_threatened_zone_wiggle_room = nautical_miles(5)
|
||||
threat_buffer_distance = self.permissible_zone.distance(
|
||||
self.threat_zone.boundary
|
||||
)
|
||||
preferred_threatened_zone_mask = self.threat_zone.buffer(
|
||||
-threat_buffer_distance - preferred_threatened_zone_wiggle_room.meters
|
||||
)
|
||||
preferred_threatened_zones = self.threat_zone.difference(
|
||||
preferred_threatened_zone_mask
|
||||
)
|
||||
|
||||
if not isinstance(preferred_threatened_zones, MultiPolygon):
|
||||
preferred_threatened_zones = MultiPolygon([preferred_threatened_zones])
|
||||
self.preferred_threatened_zones = preferred_threatened_zones
|
||||
|
||||
tolerable_threatened_lines = self.preferred_threatened_zones.intersection(
|
||||
self.permissible_zone.boundary
|
||||
)
|
||||
if tolerable_threatened_lines.is_empty:
|
||||
tolerable_threatened_lines = MultiLineString([])
|
||||
elif not isinstance(tolerable_threatened_lines, MultiLineString):
|
||||
tolerable_threatened_lines = MultiLineString([tolerable_threatened_lines])
|
||||
self.tolerable_threatened_lines = tolerable_threatened_lines
|
||||
|
||||
def _unsafe_ip(self) -> ShapelyPoint:
|
||||
unthreatened_home_zone = self.home_bubble.difference(self.threat_zone)
|
||||
if unthreatened_home_zone.is_empty:
|
||||
# Nowhere in our home zone is safe. The package will need to exit the
|
||||
# threatened area to hold and rendezvous. Pick the IP closest to the
|
||||
# edge of the threat zone.
|
||||
return shapely.ops.nearest_points(
|
||||
self.permissible_zone, self.threat_zone.boundary
|
||||
)[0]
|
||||
|
||||
# No safe point in the IP zone, but the home zone is safe. Pick an IP within
|
||||
# both the permissible zone and preferred threatened zone that's as close to the
|
||||
# unthreatened home zone as possible. This should get us a max-range IP that
|
||||
# is roughly as safe as possible without unjustifiably long routes.
|
||||
#
|
||||
# If we do the obvious thing and pick the IP that minimizes threatened travel
|
||||
# time (the IP closest to the threat boundary) and the objective is near the
|
||||
# center of the threat zone (common when there is an airbase covered only by air
|
||||
# defenses with shorter range than the BARCAP zone, and the target is a TGO near
|
||||
# the CP), the IP could be placed such that the flight would fly all the way
|
||||
# around the threat zone just to avoid a few more threatened miles of travel. To
|
||||
# avoid that, we generate a set of preferred threatened areas that offer a
|
||||
# trade-off between travel time and safety.
|
||||
#
|
||||
# https://github.com/dcs-liberation/dcs_liberation/issues/2754
|
||||
if not self.tolerable_threatened_lines.is_empty:
|
||||
return shapely.ops.nearest_points(
|
||||
self.tolerable_threatened_lines, self.home
|
||||
)[0]
|
||||
|
||||
# But if no part of the permissible zone is tolerably threatened, fall back to
|
||||
# the old safety maximizing approach.
|
||||
return shapely.ops.nearest_points(
|
||||
self.permissible_zone, unthreatened_home_zone
|
||||
)[0]
|
||||
|
||||
def _safe_ip(self) -> ShapelyPoint:
|
||||
# We have a zone of possible IPs that are safe, close enough, and in range. Pick
|
||||
# the IP in the zone that's closest to the target.
|
||||
return shapely.ops.nearest_points(self.safe_zones, self.home)[0]
|
||||
|
||||
def find_best_ip(self) -> Point:
|
||||
if self.safe_zones.is_empty:
|
||||
ip = self._unsafe_ip()
|
||||
else:
|
||||
ip = self._safe_ip()
|
||||
return self._target.new_in_same_map(ip.x, ip.y)
|
||||
@ -4,7 +4,7 @@ from pydantic import BaseModel, Field
|
||||
|
||||
from game import Game
|
||||
from game.ato import Flight
|
||||
from game.flightplan import HoldZoneGeometry, IpZoneGeometry, JoinZoneGeometry
|
||||
from game.flightplan import HoldZoneGeometry, JoinZoneGeometry
|
||||
from ..leaflet import LeafletLine, LeafletPoly, ShapelyUtil
|
||||
|
||||
|
||||
@ -59,42 +59,6 @@ class HoldZonesJs(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class IpZonesJs(BaseModel):
|
||||
home_bubble: LeafletPoly = Field(alias="homeBubble")
|
||||
ipBubble: LeafletPoly = Field(alias="ipBubble")
|
||||
permissibleZone: LeafletPoly = Field(alias="permissibleZone")
|
||||
safeZones: list[LeafletPoly] = Field(alias="safeZones")
|
||||
preferred_threatened_zones: list[LeafletPoly] = Field(
|
||||
alias="preferredThreatenedZones"
|
||||
)
|
||||
tolerable_threatened_lines: list[LeafletLine] = Field(
|
||||
alias="tolerableThreatenedLines"
|
||||
)
|
||||
|
||||
class Config:
|
||||
title = "IpZones"
|
||||
|
||||
@classmethod
|
||||
def for_flight(cls, flight: Flight, game: Game) -> IpZonesJs:
|
||||
target = flight.package.target
|
||||
home = flight.departure
|
||||
geometry = IpZoneGeometry(target.position, home.position, game.blue)
|
||||
return IpZonesJs(
|
||||
homeBubble=ShapelyUtil.poly_to_leaflet(geometry.home_bubble, game.theater),
|
||||
ipBubble=ShapelyUtil.poly_to_leaflet(geometry.ip_bubble, game.theater),
|
||||
permissibleZone=ShapelyUtil.poly_to_leaflet(
|
||||
geometry.permissible_zone, game.theater
|
||||
),
|
||||
safeZones=ShapelyUtil.polys_to_leaflet(geometry.safe_zones, game.theater),
|
||||
preferredThreatenedZones=ShapelyUtil.polys_to_leaflet(
|
||||
geometry.preferred_threatened_zones, game.theater
|
||||
),
|
||||
tolerableThreatenedLines=ShapelyUtil.lines_to_leaflet(
|
||||
geometry.tolerable_threatened_lines, game.theater
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class JoinZonesJs(BaseModel):
|
||||
home_bubble: LeafletPoly = Field(alias="homeBubble")
|
||||
target_bubble: LeafletPoly = Field(alias="targetBubble")
|
||||
|
||||
@ -4,7 +4,7 @@ from fastapi import APIRouter, Depends
|
||||
|
||||
from game import Game
|
||||
from game.server import GameContext
|
||||
from .models import HoldZonesJs, IpZonesJs, JoinZonesJs
|
||||
from .models import HoldZonesJs, JoinZonesJs
|
||||
|
||||
router: APIRouter = APIRouter(prefix="/debug/waypoint-geometries")
|
||||
|
||||
@ -18,13 +18,6 @@ def hold_zones(
|
||||
return HoldZonesJs.for_flight(game.db.flights.get(flight_id), game)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/ip/{flight_id}", operation_id="get_debug_ip_zones", response_model=IpZonesJs
|
||||
)
|
||||
def ip_zones(flight_id: UUID, game: Game = Depends(GameContext.require)) -> IpZonesJs:
|
||||
return IpZonesJs.for_flight(game.db.flights.get(flight_id), game)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/join/{flight_id}", operation_id="get_debug_join_zones", response_model=JoinZonesJs
|
||||
)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user