Update pydcs, adapt to new Point APIs.

This is briefly moving us over to my fork of pydcs while we wait for
https://github.com/pydcs/dcs/pull/206 to be merged. The adaptation is
invasive enough that I don't want it lingering for long.
This commit is contained in:
Dan Albert 2022-02-21 18:14:49 -08:00
parent ff12b37431
commit 9e2e4ffa74
29 changed files with 155 additions and 186 deletions

View File

@ -701,8 +701,7 @@ class StrikeFlightPlan(FormationFlightPlan):
return FlightWaypoint(
"TARGET AREA",
FlightWaypointType.TARGET_GROUP_LOC,
self.package.target.position.x,
self.package.target.position.y,
self.package.target.position,
meters(0),
"RADIO",
)
@ -906,8 +905,7 @@ class PackageRefuelingFlightPlan(RefuelingFlightPlan):
return FlightWaypoint(
"TARGET AREA",
FlightWaypointType.TARGET_GROUP_LOC,
self.package.target.position.x,
self.package.target.position.y,
self.package.target.position,
meters(0),
"RADIO",
)
@ -924,13 +922,13 @@ class PackageRefuelingFlightPlan(RefuelingFlightPlan):
# Cheat in a FlightWaypoint for the split point.
split: Point = self.package.waypoints.split
split_waypoint: FlightWaypoint = FlightWaypoint(
"SPLIT", FlightWaypointType.SPLIT, split.x, split.y, altitude
"SPLIT", FlightWaypointType.SPLIT, split, altitude
)
# Cheat in a FlightWaypoint for the refuel point.
refuel: Point = self.package.waypoints.refuel
refuel_waypoint: FlightWaypoint = FlightWaypoint(
"REFUEL", FlightWaypointType.REFUEL, refuel.x, refuel.y, altitude
"REFUEL", FlightWaypointType.REFUEL, refuel, altitude
)
delay_target_to_split: timedelta = self.travel_time_between_waypoints(

View File

@ -31,12 +31,9 @@ class Navigating(InFlight):
)
def estimate_position(self) -> Point:
x0 = self.current_waypoint.position.x
y0 = self.current_waypoint.position.y
x1 = self.next_waypoint.position.x
y1 = self.next_waypoint.position.y
progress = self.progress()
return Point(lerp(x0, x1, progress), lerp(y0, y1, progress))
return self.current_waypoint.position.lerp(
self.next_waypoint.position, self.progress()
)
def estimate_altitude(self) -> tuple[Distance, str]:
return (

View File

@ -22,8 +22,7 @@ AltitudeReference = Literal["BARO", "RADIO"]
class FlightWaypoint:
name: str
waypoint_type: FlightWaypointType
x: float
y: float
position: Point
alt: Distance = meters(0)
alt_type: AltitudeReference = "BARO"
control_point: ControlPoint | None = None
@ -50,8 +49,12 @@ class FlightWaypoint:
departure_time: timedelta | None = None
@property
def position(self) -> Point:
return Point(self.x, self.y)
def x(self) -> float:
return self.position.x
@property
def y(self) -> float:
return self.position.y
def __hash__(self) -> int:
return hash(id(self))

View File

@ -12,7 +12,7 @@ from typing import (
Union,
)
from dcs.mapping import Point
from dcs.mapping import Point, Vector2
from game.theater import (
ControlPoint,
@ -71,8 +71,7 @@ class WaypointBuilder:
return FlightWaypoint(
"NAV",
FlightWaypointType.NAV,
position.x,
position.y,
position,
meters(500) if self.is_helo else self.doctrine.rendezvous_altitude,
description="Enter theater",
pretty_name="Enter theater",
@ -81,8 +80,7 @@ class WaypointBuilder:
return FlightWaypoint(
"TAKEOFF",
FlightWaypointType.TAKEOFF,
position.x,
position.y,
position,
meters(0),
alt_type="RADIO",
description="Takeoff",
@ -100,8 +98,7 @@ class WaypointBuilder:
return FlightWaypoint(
"NAV",
FlightWaypointType.NAV,
position.x,
position.y,
position,
meters(500) if self.is_helo else self.doctrine.rendezvous_altitude,
description="Exit theater",
pretty_name="Exit theater",
@ -110,8 +107,7 @@ class WaypointBuilder:
return FlightWaypoint(
"LANDING",
FlightWaypointType.LANDING_POINT,
position.x,
position.y,
position,
meters(0),
alt_type="RADIO",
description="Land",
@ -143,8 +139,7 @@ class WaypointBuilder:
return FlightWaypoint(
"DIVERT",
FlightWaypointType.DIVERT,
position.x,
position.y,
position,
altitude,
alt_type=altitude_type,
description="Divert",
@ -157,8 +152,7 @@ class WaypointBuilder:
return FlightWaypoint(
"BULLSEYE",
FlightWaypointType.BULLSEYE,
self._bullseye.position.x,
self._bullseye.position.y,
self._bullseye.position,
meters(0),
description="Bullseye",
pretty_name="Bullseye",
@ -173,8 +167,7 @@ class WaypointBuilder:
return FlightWaypoint(
"HOLD",
FlightWaypointType.LOITER,
position.x,
position.y,
position,
meters(500) if self.is_helo else self.doctrine.rendezvous_altitude,
alt_type,
description="Wait until push time",
@ -189,8 +182,7 @@ class WaypointBuilder:
return FlightWaypoint(
"JOIN",
FlightWaypointType.JOIN,
position.x,
position.y,
position,
meters(80) if self.is_helo else self.doctrine.ingress_altitude,
alt_type,
description="Rendezvous with package",
@ -205,8 +197,7 @@ class WaypointBuilder:
return FlightWaypoint(
"REFUEL",
FlightWaypointType.REFUEL,
position.x,
position.y,
position,
meters(80) if self.is_helo else self.doctrine.ingress_altitude,
alt_type,
description="Refuel from tanker",
@ -221,8 +212,7 @@ class WaypointBuilder:
return FlightWaypoint(
"SPLIT",
FlightWaypointType.SPLIT,
position.x,
position.y,
position,
meters(80) if self.is_helo else self.doctrine.ingress_altitude,
alt_type,
description="Depart from package",
@ -242,8 +232,7 @@ class WaypointBuilder:
return FlightWaypoint(
"INGRESS",
ingress_type,
position.x,
position.y,
position,
meters(60) if self.is_helo else self.doctrine.ingress_altitude,
alt_type,
description=f"INGRESS on {objective.name}",
@ -259,8 +248,7 @@ class WaypointBuilder:
return FlightWaypoint(
"EGRESS",
FlightWaypointType.EGRESS,
position.x,
position.y,
position,
meters(60) if self.is_helo else self.doctrine.ingress_altitude,
alt_type,
description=f"EGRESS from {target.name}",
@ -284,8 +272,7 @@ class WaypointBuilder:
return FlightWaypoint(
target.name,
FlightWaypointType.TARGET_POINT,
target.target.position.x,
target.target.position.y,
target.target.position,
meters(0),
"RADIO",
description=description,
@ -315,8 +302,7 @@ class WaypointBuilder:
waypoint = FlightWaypoint(
name,
FlightWaypointType.TARGET_GROUP_LOC,
location.position.x,
location.position.y,
location.position,
meters(0),
"RADIO",
description=name,
@ -340,8 +326,7 @@ class WaypointBuilder:
return FlightWaypoint(
"CAS",
FlightWaypointType.CAS,
position.x,
position.y,
position,
meters(60) if self.is_helo else meters(1000),
"RADIO",
description="Provide CAS",
@ -359,8 +344,7 @@ class WaypointBuilder:
return FlightWaypoint(
"RACETRACK START",
FlightWaypointType.PATROL_TRACK,
position.x,
position.y,
position,
altitude,
description="Orbit between this point and the next point",
pretty_name="Race-track start",
@ -377,8 +361,7 @@ class WaypointBuilder:
return FlightWaypoint(
"RACETRACK END",
FlightWaypointType.PATROL,
position.x,
position.y,
position,
altitude,
description="Orbit between this point and the previous point",
pretty_name="Race-track end",
@ -411,8 +394,7 @@ class WaypointBuilder:
return FlightWaypoint(
"ORBIT",
FlightWaypointType.LOITER,
start.x,
start.y,
start,
altitude,
description="Anchor and hold at this point",
pretty_name="Orbit",
@ -429,8 +411,7 @@ class WaypointBuilder:
return FlightWaypoint(
"SWEEP START",
FlightWaypointType.INGRESS_SWEEP,
position.x,
position.y,
position,
altitude,
description="Proceed to the target and engage enemy aircraft",
pretty_name="Sweep start",
@ -447,8 +428,7 @@ class WaypointBuilder:
return FlightWaypoint(
"SWEEP END",
FlightWaypointType.EGRESS,
position.x,
position.y,
position,
altitude,
description="End of sweep",
pretty_name="Sweep end",
@ -491,8 +471,7 @@ class WaypointBuilder:
return ingress_wp, FlightWaypoint(
"TARGET",
FlightWaypointType.TARGET_GROUP_LOC,
target.position.x,
target.position.y,
target.position,
meters(60) if self.is_helo else self.doctrine.ingress_altitude,
alt_type,
description="Escort the package",
@ -509,8 +488,7 @@ class WaypointBuilder:
return FlightWaypoint(
"PICKUP",
FlightWaypointType.PICKUP,
control_point.position.x,
control_point.position.y,
control_point.position,
meters(0),
"RADIO",
description=f"Pick up cargo from {control_point}",
@ -527,8 +505,7 @@ class WaypointBuilder:
return FlightWaypoint(
"DROP OFF",
FlightWaypointType.PICKUP,
control_point.position.x,
control_point.position.y,
control_point.position,
meters(0),
"RADIO",
description=f"Drop off cargo at {control_point}",
@ -554,8 +531,7 @@ class WaypointBuilder:
return FlightWaypoint(
"NAV",
FlightWaypointType.NAV,
position.x,
position.y,
position,
altitude,
alt_type,
description="NAV",
@ -617,4 +593,4 @@ class WaypointBuilder:
deviation = nautical_miles(1)
x_adj = random.randint(int(-deviation.meters), int(deviation.meters))
y_adj = random.randint(int(-deviation.meters), int(deviation.meters))
return Point(point.x + x_adj, point.y + y_adj)
return point + Vector2(x_adj, y_adj)

View File

@ -1,31 +1,30 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Optional
from typing import Any, Optional, TYPE_CHECKING
from dcs import Point
from faker import Faker
from game.campaignloader import CampaignAirWingConfig
from game.armedforces.armedforces import ArmedForces
from game.ato.airtaaskingorder import AirTaskingOrder
from game.campaignloader.defaultsquadronassigner import DefaultSquadronAssigner
from game.commander import TheaterCommander
from game.commander.missionscheduler import MissionScheduler
from game.armedforces.armedforces import ArmedForces
from game.income import Income
from game.navmesh import NavMesh
from game.orderedset import OrderedSet
from game.profiling import logged_duration, MultiEventTracer
from game.procurement import AircraftProcurementRequest, ProcurementAi
from game.profiling import MultiEventTracer, logged_duration
from game.squadrons import AirWing
from game.theater.bullseye import Bullseye
from game.theater.transitnetwork import TransitNetwork, TransitNetworkBuilder
from game.threatzones import ThreatZones
from game.transfers import PendingTransfers
if TYPE_CHECKING:
from game import Game
from game.data.doctrine import Doctrine
from game.factions.faction import Faction
from game.procurement import AircraftProcurementRequest, ProcurementAi
from game.theater.bullseye import Bullseye
from game.theater.transitnetwork import TransitNetwork, TransitNetworkBuilder
from game.ato.airtaaskingorder import AirTaskingOrder
from game.campaignloader import CampaignAirWingConfig
from game.data.doctrine import Doctrine
from game.factions.faction import Faction
class Coalition:
@ -39,7 +38,7 @@ class Coalition:
self.ato = AirTaskingOrder()
self.transit_network = TransitNetwork()
self.procurement_requests: OrderedSet[AircraftProcurementRequest] = OrderedSet()
self.bullseye = Bullseye(Point(0, 0))
self.bullseye = Bullseye(self.game.point_in_world(0, 0))
self.faker = Faker(self.faction.locales)
self.air_wing = AirWing(player, game, self.faction)
self.armed_forces = ArmedForces(self.faction)

View File

@ -64,6 +64,7 @@ class TheaterState(WorldState["TheaterState"]):
def _rebuild_threat_zones(self) -> None:
"""Recreates the theater's threat zones based on the current planned state."""
self.threat_zones = ThreatZones.for_threats(
self.context.theater,
self.context.coalition.opponent.doctrine,
barcap_locations=self.enemy_barcaps,
air_defenses=itertools.chain(self.enemy_air_defenses, self.enemy_ships),

View File

@ -4,7 +4,7 @@ from typing import TYPE_CHECKING
import shapely.ops
from dcs import Point
from shapely.geometry import Point as ShapelyPoint, Polygon, MultiPolygon
from shapely.geometry import MultiPolygon, Point as ShapelyPoint, Polygon
from game.utils import nautical_miles
@ -29,6 +29,7 @@ class HoldZoneGeometry:
coalition: Coalition,
theater: ConflictTheater,
) -> None:
self._target = target
# Hold points are placed one of two ways. Either approach guarantees:
#
# * Safe hold point.
@ -105,4 +106,4 @@ class HoldZoneGeometry:
hold, _ = shapely.ops.nearest_points(self.permissible_zones, self.home)
else:
hold, _ = shapely.ops.nearest_points(self.preferred_lines, self.join)
return Point(hold.x, hold.y)
return self._target.new_in_same_map(hold.x, hold.y)

View File

@ -4,9 +4,9 @@ from typing import TYPE_CHECKING
import shapely.ops
from dcs import Point
from shapely.geometry import Point as ShapelyPoint, MultiPolygon
from shapely.geometry import MultiPolygon, Point as ShapelyPoint
from game.utils import nautical_miles, meters
from game.utils import meters, nautical_miles
if TYPE_CHECKING:
from game.coalition import Coalition
@ -25,6 +25,7 @@ class IpZoneGeometry:
home: Point,
coalition: Coalition,
) -> None:
self._target = target
self.threat_zone = coalition.opponent.threat_zone.all
self.home = ShapelyPoint(home.x, home.y)
@ -115,4 +116,4 @@ class IpZoneGeometry:
ip = self._unsafe_ip()
else:
ip = self._safe_ip()
return Point(ip.x, ip.y)
return self._target.new_in_same_map(ip.x, ip.y)

View File

@ -5,10 +5,10 @@ from typing import TYPE_CHECKING
import shapely.ops
from dcs import Point
from shapely.geometry import (
MultiLineString,
MultiPolygon,
Point as ShapelyPoint,
Polygon,
MultiPolygon,
MultiLineString,
)
from game.utils import nautical_miles
@ -27,6 +27,7 @@ class JoinZoneGeometry:
def __init__(
self, target: Point, home: Point, ip: Point, coalition: Coalition
) -> None:
self._target = target
# Normal join placement is based on the path from home to the IP. If no path is
# found it means that the target is on a direct path. In that case we instead
# want to enforce that the join point is:
@ -100,4 +101,4 @@ class JoinZoneGeometry:
join, _ = shapely.ops.nearest_points(self.permissible_zones, self.ip)
else:
join, _ = shapely.ops.nearest_points(self.preferred_lines, self.home)
return Point(join.x, join.y)
return self._target.new_in_same_map(join.x, join.y)

View File

@ -143,6 +143,9 @@ class Game:
yield self.blue
yield self.red
def point_in_world(self, x: float, y: float) -> Point:
return Point(x, y, self.theater.terrain)
def ato_for(self, player: bool) -> AirTaskingOrder:
return self.coalition_for(player).ato
@ -180,9 +183,6 @@ class Game:
def country_for(self, player: bool) -> str:
return self.coalition_for(player).country_name
def bullseye_for(self, player: bool) -> Bullseye:
return self.coalition_for(player).bullseye
@property
def neutral_country(self) -> Type[Country]:
"""Return the best fitting country that can be used as neutral faction in the generated mission"""
@ -447,10 +447,7 @@ class Game:
d = cp.position.distance_to_point(cp2.position)
if d < min_distance:
min_distance = d
cpoint = Point(
(cp.position.x + cp2.position.x) / 2,
(cp.position.y + cp2.position.y) / 2,
)
cpoint = cp.position.midpoint(cp2.position)
zones.append(cp.position)
zones.append(cp2.position)
break
@ -473,7 +470,9 @@ class Game:
self.__culling_zones = zones
def add_destroyed_units(self, data: dict[str, Union[float, str]]) -> None:
pos = Point(cast(float, data["x"]), cast(float, data["z"]))
pos = Point(
cast(float, data["x"]), cast(float, data["z"]), self.theater.terrain
)
if self.theater.is_on_land(pos):
self.__destroyed_units.append(data)

View File

@ -3,8 +3,9 @@ import logging
import random
from typing import Any, Union
from dcs import Mission, Point
from dcs import Mission
from dcs.country import Country
from dcs.mapping import Vector2
from dcs.mission import StartType as DcsStartType
from dcs.planes import F_14A, Su_33
from dcs.point import PointAction
@ -139,7 +140,7 @@ class FlightGroupSpawner:
)
speed = self.flight.state.estimate_speed()
pos = self.flight.state.estimate_position()
pos += Point(random.randint(100, 1000), random.randint(100, 1000))
pos += Vector2(random.randint(100, 1000), random.randint(100, 1000))
alt, alt_type = self.flight.state.estimate_altitude()
# We don't know where the ground is, so just make sure that any aircraft
@ -197,7 +198,7 @@ class FlightGroupSpawner:
alt = WARM_START_ALTITUDE
speed = GroundSpeed.for_flight(self.flight, alt)
pos = Point(at.x + random.randint(100, 1000), at.y + random.randint(100, 1000))
pos = at + Vector2(random.randint(100, 1000), random.randint(100, 1000))
group = self.mission.flight_group(
country=self.country,

View File

@ -3,10 +3,9 @@ from __future__ import annotations
from datetime import timedelta
from typing import Any, Iterable, Union
from dcs import Mission, Point
from dcs import Mission
from dcs.planes import AJS37, F_14B, JF_17
from dcs.point import MovingPoint, PointAction
from dcs.unit import Unit
from dcs.unitgroup import FlyingGroup
from game.ato import Flight, FlightWaypoint
@ -41,7 +40,7 @@ class PydcsWaypointBuilder:
def build(self) -> MovingPoint:
waypoint = self.group.add_waypoint(
Point(self.waypoint.x, self.waypoint.y),
self.waypoint.position,
self.waypoint.alt.meters,
name=self.waypoint.name,
)

View File

@ -1,4 +1,5 @@
from dcs import Point
import copy
from dcs.planes import B_17G, B_52H, Tu_22M3
from dcs.point import MovingPoint
from dcs.task import Bombing, OptFormation, WeaponType
@ -20,12 +21,10 @@ class StrikeIngressBuilder(PydcsWaypointBuilder):
if not targets:
return
center = Point(0, 0)
for target in targets:
center.x += target.position.x
center.y += target.position.y
center.x /= len(targets)
center.y /= len(targets)
center = copy.copy(targets[0].position)
for target in targets[1:]:
center += target.position
center /= len(targets)
bombing = Bombing(center, weapon_type=WeaponType.Bombs)
bombing.params["expend"] = "All"
bombing.params["attackQtyLimit"] = False

View File

@ -1,11 +1,9 @@
from datetime import datetime
from dcs import Point
from dcs.drawing import LineStyle, Rgba
from dcs.drawing.drawings import StandardLayer
from dcs.mission import Mission
from game import Game, VERSION
from game import Game
from game.missiongenerator.frontlineconflictdescription import (
FrontLineConflictDescription,
)
@ -73,7 +71,7 @@ class DrawingsGenerator:
# Add shape to layer
shape = self.player_layer.add_line_segments(
cp.position,
[Point(0, 0)]
[Point(0, 0, self.game.theater.terrain)]
+ [p - cp.position for p in convoy_route]
+ [destination.position - cp.position],
line_thickness=6,

View File

@ -9,7 +9,7 @@ from dcs import Mission
from dcs.action import AITaskPush
from dcs.condition import GroupLifeLess, Or, TimeAfter, UnitDamaged
from dcs.country import Country
from dcs.mapping import Point
from dcs.mapping import Point, Vector2
from dcs.point import PointAction
from dcs.task import (
AFAC,
@ -372,7 +372,7 @@ class FlotGenerator:
# Then move forward OR Attack enemy base if it is not too far away
target = self.find_nearest_enemy_group(dcs_group, enemy_groups)
if target is not None:
rand_offset = Point(
rand_offset = Vector2(
random.randint(-RANDOM_OFFSET_ATTACK, RANDOM_OFFSET_ATTACK),
random.randint(-RANDOM_OFFSET_ATTACK, RANDOM_OFFSET_ATTACK),
)
@ -419,7 +419,7 @@ class FlotGenerator:
# In elimination mode, the units focus on destroying as much enemy groups as possible
targets = self.find_n_nearest_enemy_groups(dcs_group, enemy_groups, 3)
for i, target in enumerate(targets, start=1):
rand_offset = Point(
rand_offset = Vector2(
random.randint(-RANDOM_OFFSET_ATTACK, RANDOM_OFFSET_ATTACK),
random.randint(-RANDOM_OFFSET_ATTACK, RANDOM_OFFSET_ATTACK),
)

View File

@ -766,7 +766,7 @@ class KneeboardGenerator(MissionInfoGenerator):
pages: List[KneeboardPage] = [
BriefingPage(
flight,
self.game.bullseye_for(flight.friendly),
self.game.coalition_for(flight.friendly).bullseye,
self.game.theater,
self.game.conditions.weather,
zoned_time,

View File

@ -129,7 +129,7 @@ class MissionGenerator:
"red", bullseye=self.game.red.bullseye.to_pydcs()
)
self.mission.coalition["neutrals"] = Coalition(
"neutrals", bullseye=Bullseye(Point(0, 0)).to_pydcs()
"neutrals", bullseye=Bullseye(Point(0, 0, self.mission.terrain)).to_pydcs()
)
p_country = self.game.blue.country_name
@ -295,7 +295,7 @@ class MissionGenerator:
logging.warning(f"Destroyed unit has no type: {d}")
continue
pos = Point(cast(float, d["x"]), cast(float, d["z"]))
pos = Point(cast(float, d["x"]), cast(float, d["z"]), self.mission.terrain)
if utype is not None and not self.game.position_culled(pos):
self.mission.static_group(
country=self.mission.country(self.game.blue.country_name),

View File

@ -537,8 +537,7 @@ class HelipadGenerator:
country = self.m.country(self.game.coalition_for(self.cp.captured).country_name)
name = self.cp.name + "_helipad"
sg = unitgroup.StaticGroup(self.m.next_group_id(), name)
sp = StaticPoint()
sp.position = self.cp.position
sp = StaticPoint(self.cp.position)
sg.add_point(sp)
for i, helipad in enumerate(self.cp.helipads):

View File

@ -46,9 +46,8 @@ class NavPoint:
point: ShapelyPoint
poly: NavMeshPoly
@property
def world_point(self) -> Point:
return Point(self.point.x, self.point.y)
def world_point(self, theater: ConflictTheater) -> Point:
return Point(self.point.x, self.point.y, theater.terrain)
def __hash__(self) -> int:
return hash(self.poly.ident)
@ -90,8 +89,9 @@ class NavFrontier:
class NavMesh:
def __init__(self, polys: List[NavMeshPoly]) -> None:
def __init__(self, polys: List[NavMeshPoly], theater: ConflictTheater) -> None:
self.polys = polys
self.theater = theater
def localize(self, point: Point) -> Optional[NavMeshPoly]:
# This is a naive implementation but it's O(n). Runs at about 10k
@ -117,8 +117,8 @@ class NavMesh:
def travel_heuristic(self, a: NavPoint, b: NavPoint) -> float:
return self.travel_cost(a, b)
@staticmethod
def reconstruct_path(
self,
came_from: Dict[NavPoint, Optional[NavPoint]],
origin: NavPoint,
destination: NavPoint,
@ -126,14 +126,14 @@ class NavMesh:
current = destination
path: List[Point] = []
while current != origin:
path.append(current.world_point)
path.append(current.world_point(self.theater))
previous = came_from[current]
if previous is None:
raise NavMeshError(
f"Could not reconstruct path to {destination} from {origin}"
)
current = previous
path.append(origin.world_point)
path.append(origin.world_point(self.theater))
path.reverse()
return path
@ -270,4 +270,4 @@ class NavMesh:
# Triangulate the safe-region to build the navmesh.
navpolys = cls.create_navpolys(triangulate(bounds), threat_zones)
cls.associate_neighbors(navpolys)
return cls(navpolys)
return NavMesh(navpolys, theater)

View File

@ -3,21 +3,19 @@ from __future__ import annotations
import math
from dcs import Point
from dcs.terrain import Terrain
from game.utils import Heading
class PointWithHeading(Point):
def __init__(self) -> None:
super(PointWithHeading, self).__init__(0, 0)
self.heading: Heading = Heading.from_degrees(0)
def __init__(self, x: float, y: float, heading: Heading, terrain: Terrain) -> None:
super().__init__(x, y, terrain)
self.heading: Heading = heading
@staticmethod
def from_point(point: Point, heading: Heading) -> PointWithHeading:
p = PointWithHeading()
p.x = point.x
p.y = point.y
p.heading = heading
return p
return PointWithHeading(point.x, point.y, heading, point._terrain)
def rotate(self, origin: Point, heading: Heading) -> None:
"""Rotates the Point by a given angle clockwise around the origin"""

View File

@ -17,7 +17,8 @@ class ShapelyUtil:
if poly.is_empty:
return []
return [
theater.point_to_ll(Point(x, y)).as_list() for x, y in poly.exterior.coords
Point(x, y, theater.terrain).latlng().as_list()
for x, y in poly.exterior.coords
]
@classmethod
@ -34,7 +35,7 @@ class ShapelyUtil:
def line_to_leaflet(
line: LineString, theater: ConflictTheater
) -> list[LeafletLatLon]:
return [theater.point_to_ll(Point(x, y)).as_list() for x, y in line.coords]
return [Point(x, y, theater.terrain).latlng().as_list() for x, y in line.coords]
@classmethod
def lines_to_leaflet(

View File

@ -1,6 +1,7 @@
from datetime import timedelta
from uuid import UUID
from dcs.mapping import LatLng
from fastapi import APIRouter, Depends, HTTPException, status
from game import Game
@ -8,7 +9,6 @@ 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")
@ -23,8 +23,7 @@ def all_waypoints_for_flight(
FlightWaypoint(
"TAKEOFF",
FlightWaypointType.TAKEOFF,
flight.departure.position.x,
flight.departure.position.y,
flight.departure.position,
meters(0),
"RADIO",
),
@ -40,7 +39,7 @@ def all_waypoints_for_flight(
def set_position(
flight_id: UUID,
waypoint_idx: int,
position: LatLon,
position: LatLng,
game: Game = Depends(GameContext.get),
) -> None:
flight = game.db.flights.get(flight_id)
@ -48,9 +47,7 @@ def set_position(
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
waypoint = flight.flight_plan.waypoints[waypoint_idx - 1]
point = game.theater.ll_to_point(position)
waypoint.x = point.x
waypoint.y = point.y
waypoint.position = game.theater.ll_to_point(position)
package_model = (
GameContext.get_model()
.ato_model_for(flight.blue)

View File

@ -5,10 +5,10 @@ from typing import Dict, TYPE_CHECKING
from dcs import Point
from game.theater import LatLon
from .latlon import LatLon
if TYPE_CHECKING:
from game.theater import ConflictTheater
from .conflicttheater import ConflictTheater
@dataclass

View File

@ -6,7 +6,7 @@ from dataclasses import dataclass
from pathlib import Path
from typing import Any, Dict, Iterator, List, Optional, TYPE_CHECKING, Tuple
from dcs.mapping import Point
from dcs.mapping import LatLng, Point
from dcs.terrain import (
caucasus,
marianaislands,
@ -147,8 +147,8 @@ class ConflictTheater:
min_distance = distance
nearest_point = pt
assert isinstance(nearest_point, geometry.Point)
point = Point(point.x, point.y)
nearest_point = Point(nearest_point.x, nearest_point.y)
point = Point(point.x, point.y, self.terrain)
nearest_point = Point(nearest_point.x, nearest_point.y, self.terrain)
new_point = point.point_from_heading(
point.heading_between_point(nearest_point),
point.distance_to_point(nearest_point) + extend_dist,
@ -261,9 +261,8 @@ class ConflictTheater:
lat, lon = self.point_to_ll_transformer.transform(point.x, point.y)
return LatLon(lat, lon)
def ll_to_point(self, ll: LatLon) -> Point:
x, y = self.ll_to_point_transformer.transform(ll.lat, ll.lng)
return Point(x, y)
def ll_to_point(self, ll: LatLng) -> Point:
return Point.from_latlng(ll, self.terrain)
def heading_to_conflict_from(self, position: Point) -> Optional[Heading]:
# Heading for a Group to the enemy.
@ -281,9 +280,8 @@ class ConflictTheater:
]
last = len(sorted_conflicts) - 1
conflict_center = Point(
(sorted_conflicts[0].position.x + sorted_conflicts[last].position.x) / 2,
(sorted_conflicts[0].position.y + sorted_conflicts[last].position.y) / 2,
conflict_center = sorted_conflicts[0].position.midpoint(
sorted_conflicts[last].position
)
return Heading.from_degrees(position.heading_between_point(conflict_center))

View File

@ -7,8 +7,8 @@ from dataclasses import dataclass, field
from enum import Enum, auto
from typing import Dict, Iterator, List, Optional, Set, Tuple
from game.theater import ConflictTheater
from game.theater.controlpoint import ControlPoint
from .conflicttheater import ConflictTheater
from .controlpoint import ControlPoint
class NoPathError(RuntimeError):

View File

@ -1,7 +1,7 @@
from __future__ import annotations
from functools import singledispatchmethod
from typing import Optional, TYPE_CHECKING, Union, Iterable
from typing import Iterable, Optional, TYPE_CHECKING, Union
from dcs.mapping import Point as DcsPoint
from shapely.geometry import (
@ -13,11 +13,16 @@ from shapely.geometry import (
from shapely.geometry.base import BaseGeometry
from shapely.ops import nearest_points, unary_union
from game.data.doctrine import Doctrine
from game.theater import ControlPoint, MissionTarget, TheaterGroundObject
from game.utils import Distance, meters, nautical_miles
from game.ato.closestairfields import ObjectiveDistanceCache
from game.ato import Flight, FlightWaypoint
from game.ato.closestairfields import ObjectiveDistanceCache
from game.data.doctrine import Doctrine
from game.theater import (
ConflictTheater,
ControlPoint,
MissionTarget,
TheaterGroundObject,
)
from game.utils import Distance, meters, nautical_miles
if TYPE_CHECKING:
from game import Game
@ -29,10 +34,12 @@ ThreatPoly = Union[MultiPolygon, Polygon]
class ThreatZones:
def __init__(
self,
theater: ConflictTheater,
airbases: ThreatPoly,
air_defenses: ThreatPoly,
radar_sam_threats: ThreatPoly,
) -> None:
self.theater = theater
self.airbases = airbases
self.air_defenses = air_defenses
self.radar_sam_threats = radar_sam_threats
@ -42,7 +49,7 @@ class ThreatZones:
boundary, _ = nearest_points(
self.all.boundary, self.dcs_to_shapely_point(point)
)
return DcsPoint(boundary.x, boundary.y)
return DcsPoint(boundary.x, boundary.y, self.theater.terrain)
def distance_to_threat(self, point: DcsPoint) -> Distance:
boundary = self.closest_boundary(point)
@ -200,12 +207,13 @@ class ThreatZones:
air_defenses.extend(control_point.ground_objects)
return cls.for_threats(
game.faction_for(player).doctrine, air_threats, air_defenses
game.theater, game.faction_for(player).doctrine, air_threats, air_defenses
)
@classmethod
def for_threats(
cls,
theater: ConflictTheater,
doctrine: Doctrine,
barcap_locations: Iterable[ControlPoint],
air_defenses: Iterable[TheaterGroundObject],
@ -213,6 +221,7 @@ class ThreatZones:
"""Generates the threat zones projected by the given locations.
Args:
theater: The theater the threat zones are in.
doctrine: The doctrine of the owning coalition.
barcap_locations: The locations that will be considered for BARCAP planning.
air_defenses: TGOs that may have air defenses.
@ -245,7 +254,8 @@ class ThreatZones:
threat_zone = point.buffer(threat_range.meters)
radar_sam_threats.append(threat_zone)
return cls(
return ThreatZones(
theater,
airbases=unary_union(air_threats),
air_defenses=unary_union(air_defense_threats),
radar_sam_threats=unary_union(radar_sam_threats),

View File

@ -1,14 +1,14 @@
from PySide2.QtGui import QStandardItem, QStandardItemModel
from game import Game
from game.theater.controlpoint import ControlPointType
from game.theater.theatergroundobject import IadsGroundObject, BuildingGroundObject
from game.utils import Distance
from game.ato.flightwaypoint import FlightWaypoint
from game.ato.flightwaypointtype import FlightWaypointType
from game.missiongenerator.frontlineconflictdescription import (
FrontLineConflictDescription,
)
from game.ato.flightwaypointtype import FlightWaypointType
from game.ato.flightwaypoint import FlightWaypoint
from game.theater.controlpoint import ControlPointType
from game.theater.theatergroundobject import BuildingGroundObject, IadsGroundObject
from game.utils import Distance
from qt_ui.widgets.combos.QFilteredComboBox import QFilteredComboBox
@ -72,10 +72,7 @@ class QPredefinedWaypointSelectionComboBox(QFilteredComboBox):
front_line, self.game.theater
)[0]
wpt = FlightWaypoint(
FlightWaypointType.CUSTOM,
pos.x,
pos.y,
Distance.from_meters(800),
FlightWaypointType.CUSTOM, pos, Distance.from_meters(800)
)
wpt.name = f"Frontline {front_line.name} [CAS]"
wpt.alt_type = "RADIO"
@ -94,8 +91,7 @@ class QPredefinedWaypointSelectionComboBox(QFilteredComboBox):
):
wpt = FlightWaypoint(
FlightWaypointType.CUSTOM,
ground_object.position.x,
ground_object.position.y,
ground_object.position,
Distance.from_meters(0),
)
wpt.alt_type = "RADIO"
@ -122,8 +118,7 @@ class QPredefinedWaypointSelectionComboBox(QFilteredComboBox):
for j, u in enumerate(g.units):
wpt = FlightWaypoint(
FlightWaypointType.CUSTOM,
u.position.x,
u.position.y,
u.position,
Distance.from_meters(0),
)
wpt.alt_type = "RADIO"
@ -151,10 +146,7 @@ class QPredefinedWaypointSelectionComboBox(QFilteredComboBox):
self.include_friendly and cp.captured
):
wpt = FlightWaypoint(
FlightWaypointType.CUSTOM,
cp.position.x,
cp.position.y,
Distance.from_meters(0),
FlightWaypointType.CUSTOM, cp.position, Distance.from_meters(0)
)
wpt.alt_type = "RADIO"
wpt.name = cp.name

View File

@ -4,9 +4,10 @@ from typing import Optional
from PySide2.QtCore import Property, QObject, Signal, Slot
from dcs import Point
from dcs.mapping import LatLng
from game.server.leaflet import LeafletLatLon
from game.theater import ConflictTheater, ControlPoint, ControlPointStatus, LatLon
from game.theater import ConflictTheater, ControlPoint, ControlPointStatus
from game.utils import meters, nautical_miles
from qt_ui.dialogs import Dialog
from qt_ui.models import GameModel
@ -83,7 +84,7 @@ class ControlPointJs(QObject):
@Slot(list, result=bool)
def destinationInRange(self, destination: LeafletLatLon) -> bool:
return self.destination_in_range(self.theater.ll_to_point(LatLon(*destination)))
return self.destination_in_range(self.theater.ll_to_point(LatLng(*destination)))
@Slot(list, result=str)
def setDestination(self, destination: LeafletLatLon) -> str:
@ -92,7 +93,7 @@ class ControlPointJs(QObject):
if not self.control_point.captured:
return f"{self.control_point} is not owned by player"
point = self.theater.ll_to_point(LatLon(*destination))
point = self.theater.ll_to_point(LatLng(*destination))
if not self.destination_in_range(point):
return (
f"Cannot move {self.control_point} more than "

View File

@ -32,7 +32,7 @@ pluggy==1.0.0
pre-commit==2.17.0
py==1.11.0
pydantic==1.9.0
-e git+https://github.com/pydcs/dcs@63863a88e0a43cb0a310dbab3ce2c7800a099dbb#egg=pydcs
-e git+https://github.com/DanAlbert/dcs@e652979adefca9f4358244cf9e42985ef58e9343#egg=pydcs
pyinstaller==4.9
pyinstaller-hooks-contrib==2022.1
pyparsing==3.0.7