mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
* 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:
parent
fab550157a
commit
91d430085e
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -81,7 +81,7 @@ from game.theater.missiontarget import MissionTarget
|
||||
from game.theater.theatergroundobject import TheaterGroundObject
|
||||
from game.transfers import MultiGroupTransport
|
||||
from game.unitmap import UnitMap
|
||||
from game.utils import Distance, meters, nautical_miles, pairwise
|
||||
from game.utils import Distance, Heading, meters, nautical_miles, pairwise
|
||||
from gen.ato import AirTaskingOrder, Package
|
||||
from gen.callsigns import create_group_callsign_from_unit
|
||||
from gen.flights.flight import (
|
||||
|
||||
@ -17,6 +17,9 @@ from dcs.task import (
|
||||
)
|
||||
from dcs.unittype import UnitType
|
||||
|
||||
from game.utils import Heading
|
||||
from .flights.ai_flight_planner_db import AEWC_CAPABLE
|
||||
from .naming import namegen
|
||||
from .callsigns import callsign_for_support_unit
|
||||
from .conflictgen import Conflict
|
||||
from .flights.ai_flight_planner_db import AEWC_CAPABLE
|
||||
@ -122,14 +125,14 @@ class AirSupportConflictGenerator:
|
||||
alt, airspeed = self._get_tanker_params(tanker_unit_type.dcs_unit_type)
|
||||
freq = self.radio_registry.alloc_uhf()
|
||||
tacan = self.tacan_registry.alloc_for_band(TacanBand.Y)
|
||||
tanker_heading = (
|
||||
tanker_heading = Heading.from_degrees(
|
||||
self.conflict.red_cp.position.heading_between_point(
|
||||
self.conflict.blue_cp.position
|
||||
)
|
||||
+ TANKER_HEADING_OFFSET * i
|
||||
)
|
||||
tanker_position = player_cp.position.point_from_heading(
|
||||
tanker_heading, TANKER_DISTANCE
|
||||
tanker_heading.degrees, TANKER_DISTANCE
|
||||
)
|
||||
tanker_group = self.mission.refuel_flight(
|
||||
country=country,
|
||||
|
||||
84
gen/armor.py
84
gen/armor.py
@ -32,7 +32,7 @@ from game.dcs.aircrafttype import AircraftType
|
||||
from game.dcs.groundunittype import GroundUnitType
|
||||
from game.theater.controlpoint import ControlPoint
|
||||
from game.unitmap import UnitMap
|
||||
from game.utils import heading_sum, opposite_heading
|
||||
from game.utils import Heading
|
||||
from gen.ground_forces.ai_ground_planner import (
|
||||
DISTANCE_FROM_FRONTLINE,
|
||||
CombatGroup,
|
||||
@ -130,7 +130,7 @@ class GroundConflictGenerator:
|
||||
self.player_stance,
|
||||
player_groups,
|
||||
enemy_groups,
|
||||
self.conflict.heading + 90,
|
||||
self.conflict.heading.right,
|
||||
self.conflict.blue_cp,
|
||||
self.conflict.red_cp,
|
||||
)
|
||||
@ -138,7 +138,7 @@ class GroundConflictGenerator:
|
||||
self.enemy_stance,
|
||||
enemy_groups,
|
||||
player_groups,
|
||||
self.conflict.heading - 90,
|
||||
self.conflict.heading.left,
|
||||
self.conflict.red_cp,
|
||||
self.conflict.blue_cp,
|
||||
)
|
||||
@ -182,7 +182,11 @@ class GroundConflictGenerator:
|
||||
)
|
||||
|
||||
def gen_infantry_group_for_group(
|
||||
self, group: VehicleGroup, is_player: bool, side: Country, forward_heading: int
|
||||
self,
|
||||
group: VehicleGroup,
|
||||
is_player: bool,
|
||||
side: Country,
|
||||
forward_heading: Heading,
|
||||
) -> None:
|
||||
|
||||
infantry_position = self.conflict.find_ground_position(
|
||||
@ -217,7 +221,7 @@ class GroundConflictGenerator:
|
||||
u.dcs_unit_type,
|
||||
position=infantry_position,
|
||||
group_size=1,
|
||||
heading=forward_heading,
|
||||
heading=forward_heading.degrees,
|
||||
move_formation=PointAction.OffRoad,
|
||||
)
|
||||
return
|
||||
@ -244,7 +248,7 @@ class GroundConflictGenerator:
|
||||
units[0].dcs_unit_type,
|
||||
position=infantry_position,
|
||||
group_size=1,
|
||||
heading=forward_heading,
|
||||
heading=forward_heading.degrees,
|
||||
move_formation=PointAction.OffRoad,
|
||||
)
|
||||
|
||||
@ -256,17 +260,19 @@ class GroundConflictGenerator:
|
||||
unit.dcs_unit_type,
|
||||
position=position,
|
||||
group_size=1,
|
||||
heading=forward_heading,
|
||||
heading=forward_heading.degrees,
|
||||
move_formation=PointAction.OffRoad,
|
||||
)
|
||||
|
||||
def _set_reform_waypoint(
|
||||
self, dcs_group: VehicleGroup, forward_heading: int
|
||||
self, dcs_group: VehicleGroup, forward_heading: Heading
|
||||
) -> None:
|
||||
"""Setting a waypoint close to the spawn position allows the group to reform gracefully
|
||||
rather than spin
|
||||
"""
|
||||
reform_point = dcs_group.position.point_from_heading(forward_heading, 50)
|
||||
reform_point = dcs_group.position.point_from_heading(
|
||||
forward_heading.degrees, 50
|
||||
)
|
||||
dcs_group.add_waypoint(reform_point)
|
||||
|
||||
def _plan_artillery_action(
|
||||
@ -274,7 +280,7 @@ class GroundConflictGenerator:
|
||||
stance: CombatStance,
|
||||
gen_group: CombatGroup,
|
||||
dcs_group: VehicleGroup,
|
||||
forward_heading: int,
|
||||
forward_heading: Heading,
|
||||
target: Point,
|
||||
) -> bool:
|
||||
"""
|
||||
@ -308,7 +314,7 @@ class GroundConflictGenerator:
|
||||
dcs_group, forward_heading, (int)(RETREAT_DISTANCE / 3)
|
||||
)
|
||||
dcs_group.add_waypoint(
|
||||
dcs_group.position.point_from_heading(forward_heading, 1),
|
||||
dcs_group.position.point_from_heading(forward_heading.degrees, 1),
|
||||
PointAction.OffRoad,
|
||||
)
|
||||
dcs_group.points[2].tasks.append(Hold())
|
||||
@ -336,7 +342,7 @@ class GroundConflictGenerator:
|
||||
self.mission.triggerrules.triggers.append(artillery_fallback)
|
||||
|
||||
for u in dcs_group.units:
|
||||
u.heading = forward_heading + random.randint(-5, 5)
|
||||
u.heading = (forward_heading + Heading.random(-5, 5)).degrees
|
||||
return True
|
||||
return False
|
||||
|
||||
@ -345,7 +351,7 @@ class GroundConflictGenerator:
|
||||
stance: CombatStance,
|
||||
enemy_groups: List[Tuple[VehicleGroup, CombatGroup]],
|
||||
dcs_group: VehicleGroup,
|
||||
forward_heading: int,
|
||||
forward_heading: Heading,
|
||||
to_cp: ControlPoint,
|
||||
) -> bool:
|
||||
"""
|
||||
@ -378,9 +384,7 @@ class GroundConflictGenerator:
|
||||
else:
|
||||
# We use an offset heading here because DCS doesn't always
|
||||
# force vehicles to move if there's no heading change.
|
||||
offset_heading = forward_heading - 2
|
||||
if offset_heading < 0:
|
||||
offset_heading = 358
|
||||
offset_heading = forward_heading - Heading.from_degrees(2)
|
||||
attack_point = self.find_offensive_point(
|
||||
dcs_group, offset_heading, AGGRESIVE_MOVE_DISTANCE
|
||||
)
|
||||
@ -398,9 +402,7 @@ class GroundConflictGenerator:
|
||||
else:
|
||||
# We use an offset heading here because DCS doesn't always
|
||||
# force vehicles to move if there's no heading change.
|
||||
offset_heading = forward_heading - 1
|
||||
if offset_heading < 0:
|
||||
offset_heading = 359
|
||||
offset_heading = forward_heading - Heading.from_degrees(1)
|
||||
attack_point = self.find_offensive_point(
|
||||
dcs_group, offset_heading, BREAKTHROUGH_OFFENSIVE_DISTANCE
|
||||
)
|
||||
@ -436,7 +438,7 @@ class GroundConflictGenerator:
|
||||
self,
|
||||
stance: CombatStance,
|
||||
dcs_group: VehicleGroup,
|
||||
forward_heading: int,
|
||||
forward_heading: Heading,
|
||||
to_cp: ControlPoint,
|
||||
) -> bool:
|
||||
"""
|
||||
@ -473,7 +475,7 @@ class GroundConflictGenerator:
|
||||
stance: CombatStance,
|
||||
ally_groups: List[Tuple[VehicleGroup, CombatGroup]],
|
||||
enemy_groups: List[Tuple[VehicleGroup, CombatGroup]],
|
||||
forward_heading: int,
|
||||
forward_heading: Heading,
|
||||
from_cp: ControlPoint,
|
||||
to_cp: ControlPoint,
|
||||
) -> None:
|
||||
@ -514,12 +516,14 @@ class GroundConflictGenerator:
|
||||
else:
|
||||
retreat_point = self.find_retreat_point(dcs_group, forward_heading)
|
||||
reposition_point = retreat_point.point_from_heading(
|
||||
forward_heading, 10
|
||||
forward_heading.degrees, 10
|
||||
) # Another point to make the unit face the enemy
|
||||
dcs_group.add_waypoint(retreat_point, PointAction.OffRoad)
|
||||
dcs_group.add_waypoint(reposition_point, PointAction.OffRoad)
|
||||
|
||||
def add_morale_trigger(self, dcs_group: VehicleGroup, forward_heading: int) -> None:
|
||||
def add_morale_trigger(
|
||||
self, dcs_group: VehicleGroup, forward_heading: Heading
|
||||
) -> None:
|
||||
"""
|
||||
This add a trigger to manage units fleeing whenever their group is hit hard, or being engaged by CAS
|
||||
"""
|
||||
@ -532,7 +536,7 @@ class GroundConflictGenerator:
|
||||
|
||||
# Force unit heading
|
||||
for unit in dcs_group.units:
|
||||
unit.heading = forward_heading
|
||||
unit.heading = forward_heading.degrees
|
||||
dcs_group.manualHeading = True
|
||||
|
||||
# We add a new retreat waypoint
|
||||
@ -563,7 +567,7 @@ class GroundConflictGenerator:
|
||||
def find_retreat_point(
|
||||
self,
|
||||
dcs_group: VehicleGroup,
|
||||
frontline_heading: int,
|
||||
frontline_heading: Heading,
|
||||
distance: int = RETREAT_DISTANCE,
|
||||
) -> Point:
|
||||
"""
|
||||
@ -573,14 +577,14 @@ class GroundConflictGenerator:
|
||||
:return: dcs.mapping.Point object with the desired position
|
||||
"""
|
||||
desired_point = dcs_group.points[0].position.point_from_heading(
|
||||
heading_sum(frontline_heading, +180), distance
|
||||
frontline_heading.opposite.degrees, distance
|
||||
)
|
||||
if self.conflict.theater.is_on_land(desired_point):
|
||||
return desired_point
|
||||
return self.conflict.theater.nearest_land_pos(desired_point)
|
||||
|
||||
def find_offensive_point(
|
||||
self, dcs_group: VehicleGroup, frontline_heading: int, distance: int
|
||||
self, dcs_group: VehicleGroup, frontline_heading: Heading, distance: int
|
||||
) -> Point:
|
||||
"""
|
||||
Find a point to attack
|
||||
@ -590,7 +594,7 @@ class GroundConflictGenerator:
|
||||
:return: dcs.mapping.Point object with the desired position
|
||||
"""
|
||||
desired_point = dcs_group.points[0].position.point_from_heading(
|
||||
frontline_heading, distance
|
||||
frontline_heading.degrees, distance
|
||||
)
|
||||
if self.conflict.theater.is_on_land(desired_point):
|
||||
return desired_point
|
||||
@ -688,14 +692,14 @@ class GroundConflictGenerator:
|
||||
conflict_position: Point,
|
||||
combat_width: int,
|
||||
distance_from_frontline: int,
|
||||
heading: int,
|
||||
spawn_heading: int,
|
||||
heading: Heading,
|
||||
spawn_heading: Heading,
|
||||
) -> Optional[Point]:
|
||||
shifted = conflict_position.point_from_heading(
|
||||
heading, random.randint(0, combat_width)
|
||||
heading.degrees, random.randint(0, combat_width)
|
||||
)
|
||||
desired_point = shifted.point_from_heading(
|
||||
spawn_heading, distance_from_frontline
|
||||
spawn_heading.degrees, distance_from_frontline
|
||||
)
|
||||
return Conflict.find_ground_position(
|
||||
desired_point, combat_width, heading, self.conflict.theater
|
||||
@ -704,17 +708,13 @@ class GroundConflictGenerator:
|
||||
def _generate_groups(
|
||||
self,
|
||||
groups: list[CombatGroup],
|
||||
frontline_vector: Tuple[Point, int, int],
|
||||
frontline_vector: Tuple[Point, Heading, int],
|
||||
is_player: bool,
|
||||
) -> List[Tuple[VehicleGroup, CombatGroup]]:
|
||||
"""Finds valid positions for planned groups and generates a pydcs group for them"""
|
||||
positioned_groups = []
|
||||
position, heading, combat_width = frontline_vector
|
||||
spawn_heading = (
|
||||
int(heading_sum(heading, -90))
|
||||
if is_player
|
||||
else int(heading_sum(heading, 90))
|
||||
)
|
||||
spawn_heading = heading.left if is_player else heading.right
|
||||
country = self.game.coalition_for(is_player).country_name
|
||||
for group in groups:
|
||||
if group.role == CombatGroupRole.ARTILLERY:
|
||||
@ -737,7 +737,7 @@ class GroundConflictGenerator:
|
||||
group.unit_type,
|
||||
group.size,
|
||||
final_position,
|
||||
heading=opposite_heading(spawn_heading),
|
||||
heading=spawn_heading.opposite,
|
||||
)
|
||||
if is_player:
|
||||
g.set_skill(Skill(self.game.settings.player_skill))
|
||||
@ -750,7 +750,7 @@ class GroundConflictGenerator:
|
||||
g,
|
||||
is_player,
|
||||
self.mission.country(country),
|
||||
opposite_heading(spawn_heading),
|
||||
spawn_heading.opposite,
|
||||
)
|
||||
else:
|
||||
logging.warning(f"Unable to get valid position for {group}")
|
||||
@ -764,7 +764,7 @@ class GroundConflictGenerator:
|
||||
count: int,
|
||||
at: Point,
|
||||
move_formation: PointAction = PointAction.OffRoad,
|
||||
heading: int = 0,
|
||||
heading: Heading = Heading.from_degrees(0),
|
||||
) -> VehicleGroup:
|
||||
|
||||
if side == self.conflict.attackers_country:
|
||||
@ -778,7 +778,7 @@ class GroundConflictGenerator:
|
||||
unit_type.dcs_unit_type,
|
||||
position=at,
|
||||
group_size=count,
|
||||
heading=heading,
|
||||
heading=heading.degrees,
|
||||
move_formation=move_formation,
|
||||
)
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ from dcs.vehicles import MissilesSS, Unarmed, AirDefence
|
||||
from game import Game
|
||||
from game.factions.faction import Faction
|
||||
from game.theater.theatergroundobject import CoastalSiteGroundObject
|
||||
from game.utils import Heading
|
||||
from gen.sam.group_generator import VehicleGroupGenerator
|
||||
|
||||
|
||||
@ -59,5 +60,5 @@ class SilkwormGenerator(VehicleGroupGenerator[CoastalSiteGroundObject]):
|
||||
"STRELA#0",
|
||||
self.position.x + 200,
|
||||
self.position.y + 15,
|
||||
90,
|
||||
Heading.from_degrees(90),
|
||||
)
|
||||
|
||||
@ -9,7 +9,7 @@ from shapely.geometry import LineString, Point as ShapelyPoint
|
||||
|
||||
from game.theater.conflicttheater import ConflictTheater, FrontLine
|
||||
from game.theater.controlpoint import ControlPoint
|
||||
from game.utils import heading_sum, opposite_heading
|
||||
from game.utils import Heading
|
||||
|
||||
|
||||
FRONTLINE_LENGTH = 80000
|
||||
@ -25,7 +25,7 @@ class Conflict:
|
||||
attackers_country: Country,
|
||||
defenders_country: Country,
|
||||
position: Point,
|
||||
heading: Optional[int] = None,
|
||||
heading: Optional[Heading] = None,
|
||||
size: Optional[int] = None,
|
||||
):
|
||||
|
||||
@ -55,28 +55,28 @@ class Conflict:
|
||||
@classmethod
|
||||
def frontline_position(
|
||||
cls, frontline: FrontLine, theater: ConflictTheater
|
||||
) -> Tuple[Point, int]:
|
||||
attack_heading = int(frontline.attack_heading)
|
||||
) -> Tuple[Point, Heading]:
|
||||
attack_heading = frontline.attack_heading
|
||||
position = cls.find_ground_position(
|
||||
frontline.position,
|
||||
FRONTLINE_LENGTH,
|
||||
heading_sum(attack_heading, 90),
|
||||
attack_heading.right,
|
||||
theater,
|
||||
)
|
||||
if position is None:
|
||||
raise RuntimeError("Could not find front line position")
|
||||
return position, opposite_heading(attack_heading)
|
||||
return position, attack_heading.opposite
|
||||
|
||||
@classmethod
|
||||
def frontline_vector(
|
||||
cls, front_line: FrontLine, theater: ConflictTheater
|
||||
) -> Tuple[Point, int, int]:
|
||||
) -> Tuple[Point, Heading, int]:
|
||||
"""
|
||||
Returns a vector for a valid frontline location avoiding exclusion zones.
|
||||
"""
|
||||
center_position, heading = cls.frontline_position(front_line, theater)
|
||||
left_heading = heading_sum(heading, -90)
|
||||
right_heading = heading_sum(heading, 90)
|
||||
left_heading = heading.left
|
||||
right_heading = heading.right
|
||||
left_position = cls.extend_ground_position(
|
||||
center_position, int(FRONTLINE_LENGTH / 2), left_heading, theater
|
||||
)
|
||||
@ -113,10 +113,14 @@ class Conflict:
|
||||
|
||||
@classmethod
|
||||
def extend_ground_position(
|
||||
cls, initial: Point, max_distance: int, heading: int, theater: ConflictTheater
|
||||
cls,
|
||||
initial: Point,
|
||||
max_distance: int,
|
||||
heading: Heading,
|
||||
theater: ConflictTheater,
|
||||
) -> Point:
|
||||
"""Finds the first intersection with an exclusion zone in one heading from an initial point up to max_distance"""
|
||||
extended = initial.point_from_heading(heading, max_distance)
|
||||
extended = initial.point_from_heading(heading.degrees, max_distance)
|
||||
if theater.landmap is None:
|
||||
# TODO: Why is this possible?
|
||||
return extended
|
||||
@ -133,14 +137,14 @@ class Conflict:
|
||||
return extended
|
||||
|
||||
# Otherwise extend the front line only up to the intersection.
|
||||
return initial.point_from_heading(heading, p0.distance(intersection))
|
||||
return initial.point_from_heading(heading.degrees, p0.distance(intersection))
|
||||
|
||||
@classmethod
|
||||
def find_ground_position(
|
||||
cls,
|
||||
initial: Point,
|
||||
max_distance: int,
|
||||
heading: int,
|
||||
heading: Heading,
|
||||
theater: ConflictTheater,
|
||||
coerce: bool = True,
|
||||
) -> Optional[Point]:
|
||||
@ -153,10 +157,10 @@ class Conflict:
|
||||
if theater.is_on_land(pos):
|
||||
return pos
|
||||
for distance in range(0, int(max_distance), 100):
|
||||
pos = initial.point_from_heading(heading, distance)
|
||||
pos = initial.point_from_heading(heading.degrees, distance)
|
||||
if theater.is_on_land(pos):
|
||||
return pos
|
||||
pos = initial.point_from_heading(opposite_heading(heading), distance)
|
||||
pos = initial.point_from_heading(heading.opposite.degrees, distance)
|
||||
if theater.is_on_land(pos):
|
||||
return pos
|
||||
if coerce:
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import random
|
||||
|
||||
from gen.sam.group_generator import ShipGroupGenerator
|
||||
from game.utils import Heading
|
||||
|
||||
from dcs.ships import USS_Arleigh_Burke_IIa, TICONDEROG
|
||||
|
||||
@ -54,7 +55,7 @@ class CarrierGroupGenerator(ShipGroupGenerator):
|
||||
)
|
||||
|
||||
# Add Ticonderoga escort
|
||||
if self.heading >= 180:
|
||||
if self.heading >= Heading.from_degrees(180):
|
||||
self.add_unit(
|
||||
TICONDEROG,
|
||||
"USS Hué City",
|
||||
|
||||
@ -37,8 +37,10 @@ from game.theater.theatergroundobject import (
|
||||
NavalGroundObject,
|
||||
BuildingGroundObject,
|
||||
)
|
||||
|
||||
from game.threatzones import ThreatZones
|
||||
from game.utils import Distance, Speed, feet, meters, nautical_miles, knots
|
||||
from game.utils import Distance, Heading, Speed, feet, meters, nautical_miles, knots
|
||||
|
||||
from .closestairfields import ObjectiveDistanceCache
|
||||
from .flight import Flight, FlightType, FlightWaypoint, FlightWaypointType
|
||||
from .traveltime import GroundSpeed, TravelTime
|
||||
@ -1151,10 +1153,11 @@ class FlightPlanBuilder:
|
||||
"""
|
||||
assert self.package.waypoints is not None
|
||||
target = self.package.target.position
|
||||
|
||||
heading = self.package.waypoints.join.heading_between_point(target)
|
||||
heading = Heading.from_degrees(
|
||||
self.package.waypoints.join.heading_between_point(target)
|
||||
)
|
||||
start_pos = target.point_from_heading(
|
||||
heading, -self.doctrine.sweep_distance.meters
|
||||
heading.degrees, -self.doctrine.sweep_distance.meters
|
||||
)
|
||||
|
||||
builder = WaypointBuilder(flight, self.coalition)
|
||||
@ -1249,7 +1252,9 @@ class FlightPlanBuilder:
|
||||
else:
|
||||
raise PlanningError("Could not find any enemy airfields")
|
||||
|
||||
heading = location.position.heading_between_point(closest_airfield.position)
|
||||
heading = Heading.from_degrees(
|
||||
location.position.heading_between_point(closest_airfield.position)
|
||||
)
|
||||
|
||||
position = ShapelyPoint(
|
||||
self.package.target.position.x, self.package.target.position.y
|
||||
@ -1285,20 +1290,20 @@ class FlightPlanBuilder:
|
||||
)
|
||||
|
||||
end = location.position.point_from_heading(
|
||||
heading,
|
||||
heading.degrees,
|
||||
random.randint(int(min_cap_distance.meters), int(max_cap_distance.meters)),
|
||||
)
|
||||
diameter = random.randint(
|
||||
int(self.doctrine.cap_min_track_length.meters),
|
||||
int(self.doctrine.cap_max_track_length.meters),
|
||||
)
|
||||
start = end.point_from_heading(heading - 180, diameter)
|
||||
start = end.point_from_heading(heading.opposite.degrees, diameter)
|
||||
return start, end
|
||||
|
||||
def aewc_orbit(self, location: MissionTarget) -> Point:
|
||||
closest_boundary = self.threat_zones.closest_boundary(location.position)
|
||||
heading_to_threat_boundary = location.position.heading_between_point(
|
||||
closest_boundary
|
||||
heading_to_threat_boundary = Heading.from_degrees(
|
||||
location.position.heading_between_point(closest_boundary)
|
||||
)
|
||||
distance_to_threat = meters(
|
||||
location.position.distance_to_point(closest_boundary)
|
||||
@ -1312,7 +1317,7 @@ class FlightPlanBuilder:
|
||||
orbit_distance = distance_to_threat - threat_buffer
|
||||
|
||||
return location.position.point_from_heading(
|
||||
orbit_heading, orbit_distance.meters
|
||||
orbit_heading.degrees, orbit_distance.meters
|
||||
)
|
||||
|
||||
def racetrack_for_frontline(
|
||||
@ -1320,9 +1325,9 @@ class FlightPlanBuilder:
|
||||
) -> Tuple[Point, Point]:
|
||||
# Find targets waypoints
|
||||
ingress, heading, distance = Conflict.frontline_vector(front_line, self.theater)
|
||||
center = ingress.point_from_heading(heading, distance / 2)
|
||||
center = ingress.point_from_heading(heading.degrees, distance / 2)
|
||||
orbit_center = center.point_from_heading(
|
||||
heading - 90,
|
||||
heading.left.degrees,
|
||||
random.randint(
|
||||
int(nautical_miles(6).meters), int(nautical_miles(15).meters)
|
||||
),
|
||||
@ -1335,8 +1340,8 @@ class FlightPlanBuilder:
|
||||
combat_width = 35000
|
||||
|
||||
radius = combat_width * 1.25
|
||||
start = orbit_center.point_from_heading(heading, radius)
|
||||
end = orbit_center.point_from_heading(heading + 180, radius)
|
||||
start = orbit_center.point_from_heading(heading.degrees, radius)
|
||||
end = orbit_center.point_from_heading(heading.opposite.degrees, radius)
|
||||
|
||||
if end.distance_to_point(origin) < start.distance_to_point(origin):
|
||||
start, end = end, start
|
||||
@ -1530,8 +1535,8 @@ class FlightPlanBuilder:
|
||||
raise InvalidObjectiveLocation(flight.flight_type, location)
|
||||
|
||||
ingress, heading, distance = Conflict.frontline_vector(location, self.theater)
|
||||
center = ingress.point_from_heading(heading, distance / 2)
|
||||
egress = ingress.point_from_heading(heading, distance)
|
||||
center = ingress.point_from_heading(heading.degrees, distance / 2)
|
||||
egress = ingress.point_from_heading(heading.degrees, distance)
|
||||
|
||||
ingress_distance = ingress.distance_to_point(flight.departure.position)
|
||||
egress_distance = egress.distance_to_point(flight.departure.position)
|
||||
@ -1566,8 +1571,8 @@ class FlightPlanBuilder:
|
||||
location = self.package.target
|
||||
|
||||
closest_boundary = self.threat_zones.closest_boundary(location.position)
|
||||
heading_to_threat_boundary = location.position.heading_between_point(
|
||||
closest_boundary
|
||||
heading_to_threat_boundary = Heading.from_degrees(
|
||||
location.position.heading_between_point(closest_boundary)
|
||||
)
|
||||
distance_to_threat = meters(
|
||||
location.position.distance_to_point(closest_boundary)
|
||||
@ -1582,16 +1587,16 @@ class FlightPlanBuilder:
|
||||
orbit_distance = distance_to_threat - threat_buffer
|
||||
|
||||
racetrack_center = location.position.point_from_heading(
|
||||
orbit_heading, orbit_distance.meters
|
||||
orbit_heading.degrees, orbit_distance.meters
|
||||
)
|
||||
|
||||
racetrack_half_distance = Distance.from_nautical_miles(20).meters
|
||||
|
||||
racetrack_start = racetrack_center.point_from_heading(
|
||||
orbit_heading + 90, racetrack_half_distance
|
||||
orbit_heading.right.degrees, racetrack_half_distance
|
||||
)
|
||||
racetrack_end = racetrack_center.point_from_heading(
|
||||
orbit_heading - 90, racetrack_half_distance
|
||||
orbit_heading.left.degrees, racetrack_half_distance
|
||||
)
|
||||
|
||||
builder = WaypointBuilder(flight, self.coalition)
|
||||
|
||||
@ -55,7 +55,7 @@ from game.theater.theatergroundobject import (
|
||||
SceneryGroundObject,
|
||||
)
|
||||
from game.unitmap import UnitMap
|
||||
from game.utils import feet, knots, mps
|
||||
from game.utils import Heading, feet, knots, mps
|
||||
from .radios import RadioFrequency, RadioRegistry
|
||||
from .runways import RunwayData
|
||||
from .tacan import TacanBand, TacanChannel, TacanRegistry
|
||||
@ -166,7 +166,7 @@ class MissileSiteGenerator(GenericGroundObjectGenerator[MissileSiteGroundObject]
|
||||
if targets:
|
||||
target = random.choice(targets)
|
||||
real_target = target.point_from_heading(
|
||||
random.randint(0, 360), random.randint(0, 2500)
|
||||
Heading.random().degrees, random.randint(0, 2500)
|
||||
)
|
||||
vg.points[0].add_task(FireAtPoint(real_target))
|
||||
logging.info("Set up fire task for missile group.")
|
||||
@ -246,7 +246,7 @@ class BuildingSiteGenerator(GenericGroundObjectGenerator[BuildingGroundObject]):
|
||||
name=self.ground_object.group_name,
|
||||
_type=unit_type,
|
||||
position=self.ground_object.position,
|
||||
heading=self.ground_object.heading,
|
||||
heading=self.ground_object.heading.degrees,
|
||||
)
|
||||
self._register_fortification(group)
|
||||
|
||||
@ -256,7 +256,7 @@ class BuildingSiteGenerator(GenericGroundObjectGenerator[BuildingGroundObject]):
|
||||
name=self.ground_object.group_name,
|
||||
_type=static_type,
|
||||
position=self.ground_object.position,
|
||||
heading=self.ground_object.heading,
|
||||
heading=self.ground_object.heading.degrees,
|
||||
dead=self.ground_object.is_dead,
|
||||
)
|
||||
self._register_building(group)
|
||||
@ -387,7 +387,9 @@ class GenericCarrierGenerator(GenericGroundObjectGenerator[GenericCarrierGroundO
|
||||
# time as the recovery window.
|
||||
brc = self.steam_into_wind(ship_group)
|
||||
self.activate_beacons(ship_group, tacan, tacan_callsign, icls)
|
||||
self.add_runway_data(brc or 0, atc, tacan, tacan_callsign, icls)
|
||||
self.add_runway_data(
|
||||
brc or Heading.from_degrees(0), atc, tacan, tacan_callsign, icls
|
||||
)
|
||||
self._register_unit_group(group, ship_group)
|
||||
|
||||
def get_carrier_type(self, group: ShipGroup) -> Type[ShipType]:
|
||||
@ -422,14 +424,14 @@ class GenericCarrierGenerator(GenericGroundObjectGenerator[GenericCarrierGroundO
|
||||
ship.set_frequency(atc_channel.hertz)
|
||||
return ship
|
||||
|
||||
def steam_into_wind(self, group: ShipGroup) -> Optional[int]:
|
||||
wind = self.game.conditions.weather.wind.at_0m
|
||||
brc = wind.direction + 180
|
||||
def steam_into_wind(self, group: ShipGroup) -> Optional[Heading]:
|
||||
wind = self.game.conditions.weather.wind.at_0m.direction
|
||||
brc = Heading.from_degrees(wind.direction).opposite
|
||||
# Aim for 25kts over the deck.
|
||||
carrier_speed = knots(25) - mps(wind.speed)
|
||||
for attempt in range(5):
|
||||
point = group.points[0].position.point_from_heading(
|
||||
brc, 100000 - attempt * 20000
|
||||
brc.degrees, 100000 - attempt * 20000
|
||||
)
|
||||
if self.game.theater.is_in_sea(point):
|
||||
group.points[0].speed = carrier_speed.meters_per_second
|
||||
@ -459,7 +461,7 @@ class GenericCarrierGenerator(GenericGroundObjectGenerator[GenericCarrierGroundO
|
||||
|
||||
def add_runway_data(
|
||||
self,
|
||||
brc: int,
|
||||
brc: Heading,
|
||||
atc: RadioFrequency,
|
||||
tacan: TacanChannel,
|
||||
callsign: str,
|
||||
@ -593,7 +595,7 @@ class HelipadGenerator:
|
||||
logging.info("Generating helipad : " + name)
|
||||
pad = SingleHeliPad(name=(name + "_unit"))
|
||||
pad.position = Point(helipad.x, helipad.y)
|
||||
pad.heading = helipad.heading
|
||||
pad.heading = helipad.heading.degrees
|
||||
# pad.heliport_frequency = self.radio_registry.alloc_uhf() TODO : alloc radio & callsign
|
||||
sg = unitgroup.StaticGroup(self.m.next_group_id(), name)
|
||||
sg.add_unit(pad)
|
||||
|
||||
@ -5,6 +5,7 @@ from dcs.vehicles import Unarmed, MissilesSS, AirDefence
|
||||
from game import Game
|
||||
from game.factions.faction import Faction
|
||||
from game.theater.theatergroundobject import MissileSiteGroundObject
|
||||
from game.utils import Heading
|
||||
from gen.sam.group_generator import VehicleGroupGenerator
|
||||
|
||||
|
||||
@ -63,5 +64,5 @@ class ScudGenerator(VehicleGroupGenerator[MissileSiteGroundObject]):
|
||||
"STRELA#0",
|
||||
self.position.x + 200,
|
||||
self.position.y + 15,
|
||||
90,
|
||||
Heading.from_degrees(90),
|
||||
)
|
||||
|
||||
@ -5,6 +5,7 @@ from dcs.vehicles import Unarmed, MissilesSS, AirDefence
|
||||
from game import Game
|
||||
from game.factions.faction import Faction
|
||||
from game.theater.theatergroundobject import MissileSiteGroundObject
|
||||
from game.utils import Heading
|
||||
from gen.sam.group_generator import VehicleGroupGenerator
|
||||
|
||||
|
||||
@ -65,5 +66,5 @@ class V1GroupGenerator(VehicleGroupGenerator[MissileSiteGroundObject]):
|
||||
"Blitz#0",
|
||||
self.position.x + 200,
|
||||
self.position.y + 15,
|
||||
90,
|
||||
Heading.from_degrees(90),
|
||||
)
|
||||
|
||||
@ -8,6 +8,7 @@ from typing import Iterator, Optional
|
||||
from dcs.terrain.terrain import Airport
|
||||
|
||||
from game.weather import Conditions
|
||||
from game.utils import Heading
|
||||
from .airfields import AIRFIELD_DATA
|
||||
from .radios import RadioFrequency
|
||||
from .tacan import TacanChannel
|
||||
@ -16,7 +17,7 @@ from .tacan import TacanChannel
|
||||
@dataclass(frozen=True)
|
||||
class RunwayData:
|
||||
airfield_name: str
|
||||
runway_heading: int
|
||||
runway_heading: Heading
|
||||
runway_name: str
|
||||
atc: Optional[RadioFrequency] = None
|
||||
tacan: Optional[TacanChannel] = None
|
||||
@ -26,7 +27,7 @@ class RunwayData:
|
||||
|
||||
@classmethod
|
||||
def for_airfield(
|
||||
cls, airport: Airport, runway_heading: int, runway_name: str
|
||||
cls, airport: Airport, runway_heading: Heading, runway_name: str
|
||||
) -> RunwayData:
|
||||
"""Creates RunwayData for the given runway of an airfield.
|
||||
|
||||
@ -66,12 +67,14 @@ class RunwayData:
|
||||
runway_number = runway.heading // 10
|
||||
runway_side = ["", "L", "R"][runway.leftright]
|
||||
runway_name = f"{runway_number:02}{runway_side}"
|
||||
yield cls.for_airfield(airport, runway.heading, runway_name)
|
||||
yield cls.for_airfield(
|
||||
airport, Heading.from_degrees(runway.heading), runway_name
|
||||
)
|
||||
|
||||
# pydcs only exposes one runway per physical runway, so to expose
|
||||
# both sides of the runway we need to generate the other.
|
||||
heading = (runway.heading + 180) % 360
|
||||
runway_number = heading // 10
|
||||
heading = Heading.from_degrees(runway.heading).opposite
|
||||
runway_number = heading.degrees // 10
|
||||
runway_side = ["", "R", "L"][runway.leftright]
|
||||
runway_name = f"{runway_number:02}{runway_side}"
|
||||
yield cls.for_airfield(airport, heading, runway_name)
|
||||
@ -81,10 +84,10 @@ class RunwayAssigner:
|
||||
def __init__(self, conditions: Conditions):
|
||||
self.conditions = conditions
|
||||
|
||||
def angle_off_headwind(self, runway: RunwayData) -> int:
|
||||
wind = self.conditions.weather.wind.at_0m.direction
|
||||
ideal_heading = (wind + 180) % 360
|
||||
return abs(runway.runway_heading - ideal_heading)
|
||||
def angle_off_headwind(self, runway: RunwayData) -> Heading:
|
||||
wind = Heading.from_degrees(self.conditions.weather.wind.at_0m.direction)
|
||||
ideal_heading = wind.opposite
|
||||
return runway.runway_heading.angle_between(ideal_heading)
|
||||
|
||||
def get_preferred_runway(self, airport: Airport) -> RunwayData:
|
||||
"""Returns the preferred runway for the given airport.
|
||||
|
||||
@ -6,6 +6,7 @@ from gen.sam.airdefensegroupgenerator import (
|
||||
AirDefenseRange,
|
||||
AirDefenseGroupGenerator,
|
||||
)
|
||||
from game.utils import Heading
|
||||
|
||||
GFLAK = [
|
||||
AirDefence.Flak38,
|
||||
@ -88,7 +89,7 @@ class FlakGenerator(AirDefenseGroupGenerator):
|
||||
"BLITZ#" + str(index),
|
||||
self.position.x + 125 + 15 * i + random.randint(1, 5),
|
||||
self.position.y + 15 * j + random.randint(1, 5),
|
||||
75,
|
||||
Heading.from_degrees(75),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
||||
@ -6,6 +6,7 @@ from gen.sam.airdefensegroupgenerator import (
|
||||
AirDefenseRange,
|
||||
AirDefenseGroupGenerator,
|
||||
)
|
||||
from game.utils import Heading
|
||||
|
||||
|
||||
class AllyWW2FlakGenerator(AirDefenseGroupGenerator):
|
||||
@ -53,28 +54,28 @@ class AllyWW2FlakGenerator(AirDefenseGroupGenerator):
|
||||
"CMD#1",
|
||||
self.position.x,
|
||||
self.position.y - 20,
|
||||
random.randint(0, 360),
|
||||
Heading.random(),
|
||||
)
|
||||
self.add_unit(
|
||||
Unarmed.M30_CC,
|
||||
"LOG#1",
|
||||
self.position.x,
|
||||
self.position.y + 20,
|
||||
random.randint(0, 360),
|
||||
Heading.random(),
|
||||
)
|
||||
self.add_unit(
|
||||
Unarmed.M4_Tractor,
|
||||
"LOG#2",
|
||||
self.position.x + 20,
|
||||
self.position.y,
|
||||
random.randint(0, 360),
|
||||
Heading.random(),
|
||||
)
|
||||
self.add_unit(
|
||||
Unarmed.Bedford_MWD,
|
||||
"LOG#3",
|
||||
self.position.x - 20,
|
||||
self.position.y,
|
||||
random.randint(0, 360),
|
||||
Heading.random(),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
||||
@ -41,7 +41,7 @@ class EarlyColdWarFlakGenerator(AirDefenseGroupGenerator):
|
||||
"SHO#1",
|
||||
self.position.x - 40,
|
||||
self.position.y - 40,
|
||||
self.heading + 180,
|
||||
self.heading.opposite,
|
||||
),
|
||||
self.add_unit(
|
||||
AirDefence.S_60_Type59_Artillery,
|
||||
@ -57,7 +57,7 @@ class EarlyColdWarFlakGenerator(AirDefenseGroupGenerator):
|
||||
"SHO#3",
|
||||
self.position.x - 80,
|
||||
self.position.y - 40,
|
||||
self.heading + 180,
|
||||
self.heading.opposite,
|
||||
),
|
||||
self.add_unit(
|
||||
AirDefence.ZU_23_Emplacement_Closed,
|
||||
@ -113,7 +113,7 @@ class ColdWarFlakGenerator(AirDefenseGroupGenerator):
|
||||
"SHO#1",
|
||||
self.position.x - 40,
|
||||
self.position.y - 40,
|
||||
self.heading + 180,
|
||||
self.heading.opposite,
|
||||
),
|
||||
self.add_unit(
|
||||
AirDefence.S_60_Type59_Artillery,
|
||||
@ -129,7 +129,7 @@ class ColdWarFlakGenerator(AirDefenseGroupGenerator):
|
||||
"SHO#3",
|
||||
self.position.x - 80,
|
||||
self.position.y - 40,
|
||||
self.heading + 180,
|
||||
self.heading.opposite,
|
||||
),
|
||||
self.add_unit(
|
||||
AirDefence.ZU_23_Emplacement_Closed,
|
||||
|
||||
@ -4,6 +4,7 @@ from gen.sam.airdefensegroupgenerator import (
|
||||
AirDefenseRange,
|
||||
AirDefenseGroupGenerator,
|
||||
)
|
||||
from game.utils import Heading
|
||||
|
||||
|
||||
class FreyaGenerator(AirDefenseGroupGenerator):
|
||||
@ -101,7 +102,7 @@ class FreyaGenerator(AirDefenseGroupGenerator):
|
||||
"Inf#3",
|
||||
self.position.x + 20,
|
||||
self.position.y - 24,
|
||||
self.heading + 45,
|
||||
self.heading + Heading.from_degrees(45),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
||||
@ -16,6 +16,7 @@ from dcs.unittype import VehicleType, UnitType, ShipType
|
||||
from game.dcs.groundunittype import GroundUnitType
|
||||
from game.factions.faction import Faction
|
||||
from game.theater.theatergroundobject import TheaterGroundObject, NavalGroundObject
|
||||
from game.utils import Heading
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game.game import Game
|
||||
@ -37,7 +38,7 @@ class GroupGenerator(Generic[GroupT, UnitT, UnitTypeT, TgoT]):
|
||||
self.game = game
|
||||
self.go = ground_object
|
||||
self.position = ground_object.position
|
||||
self.heading = random.randint(0, 359)
|
||||
self.heading: Heading = Heading.random()
|
||||
self.price = 0
|
||||
self.vg: GroupT = group
|
||||
|
||||
@ -53,7 +54,7 @@ class GroupGenerator(Generic[GroupT, UnitT, UnitTypeT, TgoT]):
|
||||
name: str,
|
||||
pos_x: float,
|
||||
pos_y: float,
|
||||
heading: int,
|
||||
heading: Heading,
|
||||
) -> UnitT:
|
||||
return self.add_unit_to_group(
|
||||
self.vg, unit_type, name, Point(pos_x, pos_y), heading
|
||||
@ -65,7 +66,7 @@ class GroupGenerator(Generic[GroupT, UnitT, UnitTypeT, TgoT]):
|
||||
unit_type: UnitTypeT,
|
||||
name: str,
|
||||
position: Point,
|
||||
heading: int,
|
||||
heading: Heading,
|
||||
) -> UnitT:
|
||||
raise NotImplementedError
|
||||
|
||||
@ -91,11 +92,11 @@ class VehicleGroupGenerator(
|
||||
unit_type: Type[VehicleType],
|
||||
name: str,
|
||||
position: Point,
|
||||
heading: int,
|
||||
heading: Heading,
|
||||
) -> Vehicle:
|
||||
unit = Vehicle(self.game.next_unit_id(), f"{group.name}|{name}", unit_type.id)
|
||||
unit.position = position
|
||||
unit.heading = heading
|
||||
unit.heading = heading.degrees
|
||||
group.add_unit(unit)
|
||||
|
||||
# get price of unit to calculate the real price of the whole group
|
||||
@ -109,7 +110,7 @@ class VehicleGroupGenerator(
|
||||
|
||||
def get_circular_position(
|
||||
self, num_units: int, launcher_distance: int, coverage: int = 90
|
||||
) -> Iterable[tuple[float, float, int]]:
|
||||
) -> Iterable[tuple[float, float, Heading]]:
|
||||
"""
|
||||
Given a position on the map, array a group of units in a circle a uniform distance from the unit
|
||||
:param num_units:
|
||||
@ -131,9 +132,9 @@ class VehicleGroupGenerator(
|
||||
positions = []
|
||||
|
||||
if num_units % 2 == 0:
|
||||
current_offset = self.heading - ((coverage / (num_units - 1)) / 2)
|
||||
current_offset = self.heading.degrees - ((coverage / (num_units - 1)) / 2)
|
||||
else:
|
||||
current_offset = self.heading
|
||||
current_offset = self.heading.degrees
|
||||
current_offset -= outer_offset * (math.ceil(num_units / 2) - 1)
|
||||
for _ in range(1, num_units + 1):
|
||||
x: float = self.position.x + launcher_distance * math.cos(
|
||||
@ -142,8 +143,7 @@ class VehicleGroupGenerator(
|
||||
y: float = self.position.y + launcher_distance * math.sin(
|
||||
math.radians(current_offset)
|
||||
)
|
||||
heading = current_offset
|
||||
positions.append((x, y, int(heading)))
|
||||
positions.append((x, y, Heading.from_degrees(current_offset)))
|
||||
current_offset += outer_offset
|
||||
return positions
|
||||
|
||||
@ -172,10 +172,10 @@ class ShipGroupGenerator(
|
||||
unit_type: Type[ShipType],
|
||||
name: str,
|
||||
position: Point,
|
||||
heading: int,
|
||||
heading: Heading,
|
||||
) -> Ship:
|
||||
unit = Ship(self.game.next_unit_id(), f"{self.go.group_name}|{name}", unit_type)
|
||||
unit.position = position
|
||||
unit.heading = heading
|
||||
unit.heading = heading.degrees
|
||||
group.add_unit(unit)
|
||||
return unit
|
||||
|
||||
@ -86,7 +86,7 @@ class VisualGenerator:
|
||||
continue
|
||||
|
||||
for offset in range(0, distance, self.game.settings.perf_smoke_spacing):
|
||||
position = plane_start.point_from_heading(heading, offset)
|
||||
position = plane_start.point_from_heading(heading.degrees, offset)
|
||||
|
||||
for k, v in FRONT_SMOKE_TYPE_CHANCES.items():
|
||||
if random.randint(0, 100) <= k:
|
||||
|
||||
@ -417,12 +417,12 @@ class FrontLineJs(QObject):
|
||||
def extents(self) -> List[LeafletLatLon]:
|
||||
a = self.theater.point_to_ll(
|
||||
self.front_line.position.point_from_heading(
|
||||
self.front_line.attack_heading + 90, nautical_miles(2).meters
|
||||
self.front_line.attack_heading.right.degrees, nautical_miles(2).meters
|
||||
)
|
||||
)
|
||||
b = self.theater.point_to_ll(
|
||||
self.front_line.position.point_from_heading(
|
||||
self.front_line.attack_heading + 270, nautical_miles(2).meters
|
||||
self.front_line.attack_heading.left.degrees, nautical_miles(2).meters
|
||||
)
|
||||
)
|
||||
return [[a.latitude, a.longitude], [b.latitude, b.longitude]]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user