Addresses #478, adding a heading class to represent headings and angles (#1387)

* Addresses #478, adding a heading class to represent headings and angles
Removed some unused code

* Fixing bad merge

* Formatting

* Fixing type issues and other merge resolution misses
This commit is contained in:
bgreman
2021-07-21 10:29:37 -04:00
committed by GitHub
parent fab550157a
commit 91d430085e
25 changed files with 296 additions and 189 deletions

View File

@@ -1,15 +1,16 @@
from __future__ import annotations
from dcs import Point
from game.utils import Heading
class PointWithHeading(Point):
def __init__(self) -> None:
super(PointWithHeading, self).__init__(0, 0)
self.heading = 0
self.heading: Heading = Heading.from_degrees(0)
@staticmethod
def from_point(point: Point, heading: int) -> PointWithHeading:
def from_point(point: Point, heading: Heading) -> PointWithHeading:
p = PointWithHeading()
p.x = point.x
p.y = point.y

View File

@@ -59,7 +59,7 @@ from ..point_with_heading import PointWithHeading
from ..positioned import Positioned
from ..profiling import logged_duration
from ..scenery_group import SceneryGroup
from ..utils import Distance, meters
from ..utils import Distance, Heading, meters
if TYPE_CHECKING:
from . import TheaterGroundObject
@@ -400,85 +400,113 @@ class MizCampaignLoader:
for static in self.offshore_strike_targets:
closest, distance = self.objective_info(static)
closest.preset_locations.offshore_strike_locations.append(
PointWithHeading.from_point(static.position, static.units[0].heading)
PointWithHeading.from_point(
static.position, Heading.from_degrees(static.units[0].heading)
)
)
for ship in self.ships:
closest, distance = self.objective_info(ship, allow_naval=True)
closest.preset_locations.ships.append(
PointWithHeading.from_point(ship.position, ship.units[0].heading)
PointWithHeading.from_point(
ship.position, Heading.from_degrees(ship.units[0].heading)
)
)
for group in self.missile_sites:
closest, distance = self.objective_info(group)
closest.preset_locations.missile_sites.append(
PointWithHeading.from_point(group.position, group.units[0].heading)
PointWithHeading.from_point(
group.position, Heading.from_degrees(group.units[0].heading)
)
)
for group in self.coastal_defenses:
closest, distance = self.objective_info(group)
closest.preset_locations.coastal_defenses.append(
PointWithHeading.from_point(group.position, group.units[0].heading)
PointWithHeading.from_point(
group.position, Heading.from_degrees(group.units[0].heading)
)
)
for group in self.long_range_sams:
closest, distance = self.objective_info(group)
closest.preset_locations.long_range_sams.append(
PointWithHeading.from_point(group.position, group.units[0].heading)
PointWithHeading.from_point(
group.position, Heading.from_degrees(group.units[0].heading)
)
)
for group in self.medium_range_sams:
closest, distance = self.objective_info(group)
closest.preset_locations.medium_range_sams.append(
PointWithHeading.from_point(group.position, group.units[0].heading)
PointWithHeading.from_point(
group.position, Heading.from_degrees(group.units[0].heading)
)
)
for group in self.short_range_sams:
closest, distance = self.objective_info(group)
closest.preset_locations.short_range_sams.append(
PointWithHeading.from_point(group.position, group.units[0].heading)
PointWithHeading.from_point(
group.position, Heading.from_degrees(group.units[0].heading)
)
)
for group in self.aaa:
closest, distance = self.objective_info(group)
closest.preset_locations.aaa.append(
PointWithHeading.from_point(group.position, group.units[0].heading)
PointWithHeading.from_point(
group.position, Heading.from_degrees(group.units[0].heading)
)
)
for group in self.ewrs:
closest, distance = self.objective_info(group)
closest.preset_locations.ewrs.append(
PointWithHeading.from_point(group.position, group.units[0].heading)
PointWithHeading.from_point(
group.position, Heading.from_degrees(group.units[0].heading)
)
)
for group in self.armor_groups:
closest, distance = self.objective_info(group)
closest.preset_locations.armor_groups.append(
PointWithHeading.from_point(group.position, group.units[0].heading)
PointWithHeading.from_point(
group.position, Heading.from_degrees(group.units[0].heading)
)
)
for static in self.helipads:
closest, distance = self.objective_info(static)
closest.helipads.append(
PointWithHeading.from_point(static.position, static.units[0].heading)
PointWithHeading.from_point(
static.position, Heading.from_degrees(static.units[0].heading)
)
)
for static in self.factories:
closest, distance = self.objective_info(static)
closest.preset_locations.factories.append(
PointWithHeading.from_point(static.position, static.units[0].heading)
PointWithHeading.from_point(
static.position, Heading.from_degrees(static.units[0].heading)
)
)
for static in self.ammunition_depots:
closest, distance = self.objective_info(static)
closest.preset_locations.ammunition_depots.append(
PointWithHeading.from_point(static.position, static.units[0].heading)
PointWithHeading.from_point(
static.position, Heading.from_degrees(static.units[0].heading)
)
)
for static in self.strike_targets:
closest, distance = self.objective_info(static)
closest.preset_locations.strike_locations.append(
PointWithHeading.from_point(static.position, static.units[0].heading)
PointWithHeading.from_point(
static.position, Heading.from_degrees(static.units[0].heading)
)
)
for scenery_group in self.scenery:

View File

@@ -35,6 +35,7 @@ from dcs.unit import Unit
from game import db
from game.point_with_heading import PointWithHeading
from game.scenery_group import SceneryGroup
from game.utils import Heading
from gen.flights.closestairfields import ObjectiveDistanceCache
from gen.ground_forces.combat_stance import CombatStance
from gen.runways import RunwayAssigner, RunwayData
@@ -335,7 +336,7 @@ class ControlPoint(MissionTarget, ABC):
@property
@abstractmethod
def heading(self) -> int:
def heading(self) -> Heading:
...
def __str__(self) -> str:
@@ -838,8 +839,8 @@ class Airfield(ControlPoint):
return len(self.airport.parking_slots)
@property
def heading(self) -> int:
return self.airport.runways[0].heading
def heading(self) -> Heading:
return Heading.from_degrees(self.airport.runways[0].heading)
def runway_is_operational(self) -> bool:
return not self.runway_status.damaged
@@ -903,8 +904,8 @@ class NavalControlPoint(ControlPoint, ABC):
yield from super().mission_types(for_player)
@property
def heading(self) -> int:
return 0 # TODO compute heading
def heading(self) -> Heading:
return Heading.from_degrees(0) # TODO compute heading
def find_main_tgo(self) -> GenericCarrierGroundObject:
for g in self.ground_objects:
@@ -933,7 +934,9 @@ class NavalControlPoint(ControlPoint, ABC):
self, conditions: Conditions, dynamic_runways: Dict[str, RunwayData]
) -> RunwayData:
# TODO: Assign TACAN and ICLS earlier so we don't need this.
fallback = RunwayData(self.full_name, runway_heading=0, runway_name="")
fallback = RunwayData(
self.full_name, runway_heading=Heading.from_degrees(0), runway_name=""
)
return dynamic_runways.get(self.name, fallback)
@property
@@ -1071,14 +1074,16 @@ class OffMapSpawn(ControlPoint):
return True
@property
def heading(self) -> int:
return 0
def heading(self) -> Heading:
return Heading.from_degrees(0)
def active_runway(
self, conditions: Conditions, dynamic_runways: Dict[str, RunwayData]
) -> RunwayData:
logging.warning("TODO: Off map spawns have no runways.")
return RunwayData(self.full_name, runway_heading=0, runway_name="")
return RunwayData(
self.full_name, runway_heading=Heading.from_degrees(0), runway_name=""
)
@property
def runway_status(self) -> RunwayStatus:
@@ -1120,7 +1125,9 @@ class Fob(ControlPoint):
self, conditions: Conditions, dynamic_runways: Dict[str, RunwayData]
) -> RunwayData:
logging.warning("TODO: FOBs have no runways.")
return RunwayData(self.full_name, runway_heading=0, runway_name="")
return RunwayData(
self.full_name, runway_heading=Heading.from_degrees(0), runway_name=""
)
@property
def runway_status(self) -> RunwayStatus:
@@ -1142,8 +1149,8 @@ class Fob(ControlPoint):
return False
@property
def heading(self) -> int:
return 0
def heading(self) -> Heading:
return Heading.from_degrees(0)
@property
def can_deploy_ground_units(self) -> bool:

View File

@@ -11,7 +11,7 @@ from .controlpoint import (
ControlPoint,
MissionTarget,
)
from ..utils import pairwise
from ..utils import Heading, pairwise
FRONTLINE_MIN_CP_DISTANCE = 5000
@@ -27,9 +27,9 @@ class FrontLineSegment:
point_b: Point
@property
def attack_heading(self) -> float:
def attack_heading(self) -> Heading:
"""The heading of the frontline segment from player to enemy control point"""
return self.point_a.heading_between_point(self.point_b)
return Heading.from_degrees(self.point_a.heading_between_point(self.point_b))
@property
def attack_distance(self) -> float:
@@ -123,7 +123,7 @@ class FrontLine(MissionTarget):
return sum(i.attack_distance for i in self.segments)
@property
def attack_heading(self) -> float:
def attack_heading(self) -> Heading:
"""The heading of the active attack segment from player to enemy control point"""
return self.active_segment.attack_heading
@@ -150,13 +150,13 @@ class FrontLine(MissionTarget):
"""
if distance < self.segments[0].attack_distance:
return self.blue_cp.position.point_from_heading(
self.segments[0].attack_heading, distance
self.segments[0].attack_heading.degrees, distance
)
remaining_dist = distance
for segment in self.segments:
if remaining_dist < segment.attack_distance:
return segment.point_a.point_from_heading(
segment.attack_heading, remaining_dist
segment.attack_heading.degrees, remaining_dist
)
else:
remaining_dist -= segment.attack_distance

View File

@@ -28,6 +28,7 @@ from game.theater.theatergroundobject import (
VehicleGroupGroundObject,
CoastalSiteGroundObject,
)
from game.utils import Heading
from game.version import VERSION
from gen import namegen
from gen.coastal.coastal_group_generator import generate_coastal_group
@@ -385,7 +386,7 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
group_id,
object_id,
position + template_point,
unit["heading"],
Heading.from_degrees(unit["heading"]),
self.control_point,
unit["type"],
)
@@ -585,7 +586,7 @@ class FobGroundObjectGenerator(AirbaseGroundObjectGenerator):
group_id,
object_id,
point + template_point,
unit["heading"],
Heading.from_degrees(unit["heading"]),
self.control_point,
unit["type"],
is_fob_structure=True,

View File

@@ -17,7 +17,7 @@ from ..data.radar_db import (
TELARS,
LAUNCHER_TRACKER_PAIRS,
)
from ..utils import Distance, meters
from ..utils import Distance, Heading, meters
if TYPE_CHECKING:
from .controlpoint import ControlPoint
@@ -58,7 +58,7 @@ class TheaterGroundObject(MissionTarget, Generic[GroupT]):
category: str,
group_id: int,
position: Point,
heading: int,
heading: Heading,
control_point: ControlPoint,
dcs_identifier: str,
sea_object: bool,
@@ -222,7 +222,7 @@ class BuildingGroundObject(TheaterGroundObject[VehicleGroup]):
group_id: int,
object_id: int,
position: Point,
heading: int,
heading: Heading,
control_point: ControlPoint,
dcs_identifier: str,
is_fob_structure: bool = False,
@@ -310,7 +310,7 @@ class SceneryGroundObject(BuildingGroundObject):
group_id=group_id,
object_id=object_id,
position=position,
heading=0,
heading=Heading.from_degrees(0),
control_point=control_point,
dcs_identifier=dcs_identifier,
is_fob_structure=False,
@@ -334,7 +334,7 @@ class FactoryGroundObject(BuildingGroundObject):
name: str,
group_id: int,
position: Point,
heading: int,
heading: Heading,
control_point: ControlPoint,
) -> None:
super().__init__(
@@ -385,7 +385,7 @@ class CarrierGroundObject(GenericCarrierGroundObject):
category="CARRIER",
group_id=group_id,
position=control_point.position,
heading=0,
heading=Heading.from_degrees(0),
control_point=control_point,
dcs_identifier="CARRIER",
sea_object=True,
@@ -406,7 +406,7 @@ class LhaGroundObject(GenericCarrierGroundObject):
category="LHA",
group_id=group_id,
position=control_point.position,
heading=0,
heading=Heading.from_degrees(0),
control_point=control_point,
dcs_identifier="LHA",
sea_object=True,
@@ -428,7 +428,7 @@ class MissileSiteGroundObject(TheaterGroundObject[VehicleGroup]):
category="missile",
group_id=group_id,
position=position,
heading=0,
heading=Heading.from_degrees(0),
control_point=control_point,
dcs_identifier="AA",
sea_object=False,
@@ -450,7 +450,7 @@ class CoastalSiteGroundObject(TheaterGroundObject[VehicleGroup]):
group_id: int,
position: Point,
control_point: ControlPoint,
heading: int,
heading: Heading,
) -> None:
super().__init__(
name=name,
@@ -497,7 +497,7 @@ class SamGroundObject(IadsGroundObject):
category="aa",
group_id=group_id,
position=position,
heading=0,
heading=Heading.from_degrees(0),
control_point=control_point,
dcs_identifier="AA",
sea_object=False,
@@ -565,7 +565,7 @@ class VehicleGroupGroundObject(TheaterGroundObject[VehicleGroup]):
category="armor",
group_id=group_id,
position=position,
heading=0,
heading=Heading.from_degrees(0),
control_point=control_point,
dcs_identifier="AA",
sea_object=False,
@@ -593,7 +593,7 @@ class EwrGroundObject(IadsGroundObject):
category="ewr",
group_id=group_id,
position=position,
heading=0,
heading=Heading.from_degrees(0),
control_point=control_point,
dcs_identifier="EWR",
sea_object=False,
@@ -627,7 +627,7 @@ class ShipGroundObject(NavalGroundObject):
category="ship",
group_id=group_id,
position=position,
heading=0,
heading=Heading.from_degrees(0),
control_point=control_point,
dcs_identifier="AA",
sea_object=True,

View File

@@ -2,6 +2,7 @@ from __future__ import annotations
import itertools
import math
import random
from collections import Iterable
from dataclasses import dataclass
from typing import Union, Any, TypeVar
@@ -20,15 +21,6 @@ INHG_TO_HPA = 33.86389
INHG_TO_MMHG = 25.400002776728
def heading_sum(h: int, a: int) -> int:
h += a
return h % 360
def opposite_heading(h: int) -> int:
return heading_sum(h, 180)
@dataclass(frozen=True, order=True)
class Distance:
distance_in_meters: float
@@ -184,6 +176,60 @@ def mach(value: float, altitude: Distance) -> Speed:
SPEED_OF_SOUND_AT_SEA_LEVEL = knots(661.5)
@dataclass(frozen=True, order=True)
class Heading:
heading_in_degrees: int
@property
def degrees(self) -> int:
return Heading.reduce_angle(self.heading_in_degrees)
@property
def radians(self) -> float:
return math.radians(Heading.reduce_angle(self.heading_in_degrees))
@property
def opposite(self) -> Heading:
return self + Heading.from_degrees(180)
@property
def right(self) -> Heading:
return self + Heading.from_degrees(90)
@property
def left(self) -> Heading:
return self - Heading.from_degrees(90)
def angle_between(self, other: Heading) -> Heading:
angle_between = abs(self.degrees - other.degrees)
if angle_between > 180:
angle_between = 360 - angle_between
return Heading.from_degrees(angle_between)
@staticmethod
def reduce_angle(angle: int) -> int:
return angle % 360
@classmethod
def from_degrees(cls, angle: Union[int, float]) -> Heading:
return cls(Heading.reduce_angle(round(angle)))
@classmethod
def from_radians(cls, angle: Union[int, float]) -> Heading:
deg = round(math.degrees(angle))
return cls(Heading.reduce_angle(deg))
@classmethod
def random(cls, min_angle: int = 0, max_angle: int = 0) -> Heading:
return Heading.from_degrees(random.randint(min_angle, max_angle))
def __add__(self, other: Heading) -> Heading:
return Heading.from_degrees(self.degrees + other.degrees)
def __sub__(self, other: Heading) -> Heading:
return Heading.from_degrees(self.degrees - other.degrees)
@dataclass(frozen=True, order=True)
class Pressure:
pressure_in_inches_hg: float