From 9e2e4ffa74fcc72bf275c7b44bed0b5e5d4826a6 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Mon, 21 Feb 2022 18:14:49 -0800 Subject: [PATCH] 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. --- game/ato/flightplan.py | 10 +-- game/ato/flightstate/navigating.py | 9 +-- game/ato/flightwaypoint.py | 11 ++- game/ato/waypointbuilder.py | 76 +++++++------------ game/coalition.py | 23 +++--- game/commander/theaterstate.py | 1 + game/flightplan/holdzonegeometry.py | 5 +- game/flightplan/ipzonegeometry.py | 7 +- game/flightplan/joinzonegeometry.py | 7 +- game/game.py | 15 ++-- .../aircraft/flightgroupspawner.py | 7 +- .../waypoints/pydcswaypointbuilder.py | 5 +- .../aircraft/waypoints/strikeingress.py | 13 ++-- game/missiongenerator/drawingsgenerator.py | 6 +- game/missiongenerator/flotgenerator.py | 6 +- game/missiongenerator/kneeboard.py | 2 +- game/missiongenerator/missiongenerator.py | 4 +- game/missiongenerator/tgogenerator.py | 3 +- game/navmesh.py | 16 ++-- game/point_with_heading.py | 14 ++-- game/server/leaflet.py | 5 +- game/server/waypoints/routes.py | 11 +-- game/theater/bullseye.py | 4 +- game/theater/conflicttheater.py | 16 ++-- game/theater/transitnetwork.py | 4 +- game/threatzones.py | 26 +++++-- .../QPredefinedWaypointSelectionComboBox.py | 26 +++---- qt_ui/widgets/map/model/controlpointjs.py | 7 +- requirements.txt | 2 +- 29 files changed, 155 insertions(+), 186 deletions(-) diff --git a/game/ato/flightplan.py b/game/ato/flightplan.py index f9b69204..8d53ca6f 100644 --- a/game/ato/flightplan.py +++ b/game/ato/flightplan.py @@ -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( diff --git a/game/ato/flightstate/navigating.py b/game/ato/flightstate/navigating.py index ff0c15ec..01644f07 100644 --- a/game/ato/flightstate/navigating.py +++ b/game/ato/flightstate/navigating.py @@ -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 ( diff --git a/game/ato/flightwaypoint.py b/game/ato/flightwaypoint.py index 1eb84114..c863b102 100644 --- a/game/ato/flightwaypoint.py +++ b/game/ato/flightwaypoint.py @@ -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)) diff --git a/game/ato/waypointbuilder.py b/game/ato/waypointbuilder.py index 5a952c21..aaa39110 100644 --- a/game/ato/waypointbuilder.py +++ b/game/ato/waypointbuilder.py @@ -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) diff --git a/game/coalition.py b/game/coalition.py index ac6c3db1..5d49e037 100644 --- a/game/coalition.py +++ b/game/coalition.py @@ -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) diff --git a/game/commander/theaterstate.py b/game/commander/theaterstate.py index 1b8112ad..07abc39a 100644 --- a/game/commander/theaterstate.py +++ b/game/commander/theaterstate.py @@ -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), diff --git a/game/flightplan/holdzonegeometry.py b/game/flightplan/holdzonegeometry.py index 6e6fc874..a5c37149 100644 --- a/game/flightplan/holdzonegeometry.py +++ b/game/flightplan/holdzonegeometry.py @@ -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) diff --git a/game/flightplan/ipzonegeometry.py b/game/flightplan/ipzonegeometry.py index a909cf03..70b5a072 100644 --- a/game/flightplan/ipzonegeometry.py +++ b/game/flightplan/ipzonegeometry.py @@ -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) diff --git a/game/flightplan/joinzonegeometry.py b/game/flightplan/joinzonegeometry.py index 02e00fa4..fd20c963 100644 --- a/game/flightplan/joinzonegeometry.py +++ b/game/flightplan/joinzonegeometry.py @@ -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) diff --git a/game/game.py b/game/game.py index e0fa966b..84242a67 100644 --- a/game/game.py +++ b/game/game.py @@ -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) diff --git a/game/missiongenerator/aircraft/flightgroupspawner.py b/game/missiongenerator/aircraft/flightgroupspawner.py index 9db33f2a..ff58cc3d 100644 --- a/game/missiongenerator/aircraft/flightgroupspawner.py +++ b/game/missiongenerator/aircraft/flightgroupspawner.py @@ -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, diff --git a/game/missiongenerator/aircraft/waypoints/pydcswaypointbuilder.py b/game/missiongenerator/aircraft/waypoints/pydcswaypointbuilder.py index 08466aff..4c48e5ce 100644 --- a/game/missiongenerator/aircraft/waypoints/pydcswaypointbuilder.py +++ b/game/missiongenerator/aircraft/waypoints/pydcswaypointbuilder.py @@ -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, ) diff --git a/game/missiongenerator/aircraft/waypoints/strikeingress.py b/game/missiongenerator/aircraft/waypoints/strikeingress.py index ff5dd95b..f10a67ab 100644 --- a/game/missiongenerator/aircraft/waypoints/strikeingress.py +++ b/game/missiongenerator/aircraft/waypoints/strikeingress.py @@ -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 diff --git a/game/missiongenerator/drawingsgenerator.py b/game/missiongenerator/drawingsgenerator.py index 5f4de765..d5f73876 100644 --- a/game/missiongenerator/drawingsgenerator.py +++ b/game/missiongenerator/drawingsgenerator.py @@ -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, diff --git a/game/missiongenerator/flotgenerator.py b/game/missiongenerator/flotgenerator.py index d4146cdc..ceaa25a3 100644 --- a/game/missiongenerator/flotgenerator.py +++ b/game/missiongenerator/flotgenerator.py @@ -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), ) diff --git a/game/missiongenerator/kneeboard.py b/game/missiongenerator/kneeboard.py index f3099936..93e81c57 100644 --- a/game/missiongenerator/kneeboard.py +++ b/game/missiongenerator/kneeboard.py @@ -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, diff --git a/game/missiongenerator/missiongenerator.py b/game/missiongenerator/missiongenerator.py index d84a74d8..c7dd828c 100644 --- a/game/missiongenerator/missiongenerator.py +++ b/game/missiongenerator/missiongenerator.py @@ -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), diff --git a/game/missiongenerator/tgogenerator.py b/game/missiongenerator/tgogenerator.py index f65fde6c..5ee6bc04 100644 --- a/game/missiongenerator/tgogenerator.py +++ b/game/missiongenerator/tgogenerator.py @@ -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): diff --git a/game/navmesh.py b/game/navmesh.py index e18052d0..3c109411 100644 --- a/game/navmesh.py +++ b/game/navmesh.py @@ -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) diff --git a/game/point_with_heading.py b/game/point_with_heading.py index 9769ba02..c55569bf 100644 --- a/game/point_with_heading.py +++ b/game/point_with_heading.py @@ -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""" diff --git a/game/server/leaflet.py b/game/server/leaflet.py index 1915790b..ed6005cf 100644 --- a/game/server/leaflet.py +++ b/game/server/leaflet.py @@ -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( diff --git a/game/server/waypoints/routes.py b/game/server/waypoints/routes.py index 367148d6..a24e5b75 100644 --- a/game/server/waypoints/routes.py +++ b/game/server/waypoints/routes.py @@ -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) diff --git a/game/theater/bullseye.py b/game/theater/bullseye.py index 138412c0..1f3691c6 100644 --- a/game/theater/bullseye.py +++ b/game/theater/bullseye.py @@ -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 diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index cfde4f24..6e2e01e8 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -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)) diff --git a/game/theater/transitnetwork.py b/game/theater/transitnetwork.py index 571d447b..500bb554 100644 --- a/game/theater/transitnetwork.py +++ b/game/theater/transitnetwork.py @@ -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): diff --git a/game/threatzones.py b/game/threatzones.py index 8a945e17..ec52a8c3 100644 --- a/game/threatzones.py +++ b/game/threatzones.py @@ -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), diff --git a/qt_ui/widgets/combos/QPredefinedWaypointSelectionComboBox.py b/qt_ui/widgets/combos/QPredefinedWaypointSelectionComboBox.py index b7b856ae..2726e836 100644 --- a/qt_ui/widgets/combos/QPredefinedWaypointSelectionComboBox.py +++ b/qt_ui/widgets/combos/QPredefinedWaypointSelectionComboBox.py @@ -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 diff --git a/qt_ui/widgets/map/model/controlpointjs.py b/qt_ui/widgets/map/model/controlpointjs.py index 7f511727..4c05d541 100644 --- a/qt_ui/widgets/map/model/controlpointjs.py +++ b/qt_ui/widgets/map/model/controlpointjs.py @@ -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 " diff --git a/requirements.txt b/requirements.txt index 4b3f86ba..47da8882 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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