diff --git a/game/helipad.py b/game/helipad.py deleted file mode 100644 index c6d489d0..00000000 --- a/game/helipad.py +++ /dev/null @@ -1,25 +0,0 @@ -from __future__ import annotations - -from typing import Optional - -from dcs import Point -from dcs.unitgroup import StaticGroup - -from game.point_with_heading import PointWithHeading -from game.utils import Heading - - -class Helipad(PointWithHeading): - def __init__(self) -> None: - super(Helipad, self).__init__() - self.heading = Heading.from_degrees(0) - self.occupied = False - self.static_unit: Optional[StaticGroup] = None - - @staticmethod - def from_point(point: Point, heading: Heading) -> Helipad: - h = Helipad() - h.x = point.x - h.y = point.y - h.heading = heading - return h diff --git a/game/operation/operation.py b/game/operation/operation.py index 299aaf15..1f632fc3 100644 --- a/game/operation/operation.py +++ b/game/operation/operation.py @@ -375,6 +375,7 @@ class Operation: cls.laser_code_registry, cls.unit_map, air_support=cls.airsupportgen.air_support, + helipads=cls.groundobjectgen.helipads, ) cls.airgen.clear_parking_slots() diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index 4d8bf750..b0a79271 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -56,7 +56,6 @@ from .landmap import Landmap, load_landmap, poly_contains from .latlon import LatLon from .projections import TransverseMercator from .seasonalconditions import SeasonalConditions -from ..helipad import Helipad from ..point_with_heading import PointWithHeading from ..positioned import Positioned from ..profiling import logged_duration @@ -482,7 +481,7 @@ class MizCampaignLoader: for static in self.helipads: closest, distance = self.objective_info(static) closest.helipads.append( - Helipad.from_point( + PointWithHeading.from_point( static.position, Heading.from_degrees(static.units[0].heading) ) ) diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index d968b9a3..472c2449 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -49,7 +49,6 @@ from .theatergroundobject import ( ) from ..dcs.aircrafttype import AircraftType from ..dcs.groundunittype import GroundUnitType -from ..helipad import Helipad from ..utils import nautical_miles from ..weather import Conditions @@ -307,7 +306,7 @@ class ControlPoint(MissionTarget, ABC): self.at = at self.connected_objectives: List[TheaterGroundObject[Any]] = [] self.preset_locations = PresetLocations() - self.helipads: List[Helipad] = [] + self.helipads: List[PointWithHeading] = [] # TODO: Should be Airbase specific. self.size = size @@ -400,22 +399,6 @@ class ControlPoint(MissionTarget, ABC): """ return len(self.helipads) > 0 - @property - def has_free_helipad(self) -> bool: - """ - Returns true if cp has a free helipad - """ - return not all(h.occupied for h in self.helipads) - - def get_free_helipad(self) -> Optional[Helipad]: - """ - Returns the first free additional helipad - """ - for h in self.helipads: - if not h.occupied: - return h - return None - def can_recruit_ground_units(self, game: Game) -> bool: """Returns True if this control point is capable of recruiting ground units.""" if not self.can_deploy_ground_units: diff --git a/gen/aircraft.py b/gen/aircraft.py index 6b112fd7..bccc8097 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -68,7 +68,6 @@ from game import db from game.data.weapons import Pylon, WeaponType as WeaponTypeEnum from game.dcs.aircrafttype import AircraftType from game.factions.faction import Faction -from game.helipad import Helipad from game.settings import Settings from game.squadrons import Pilot from game.theater.controlpoint import ( @@ -229,6 +228,7 @@ class AircraftConflictGenerator: laser_code_registry: LaserCodeRegistry, unit_map: UnitMap, air_support: AirSupport, + helipads: dict[ControlPoint, list[StaticGroup]], ) -> None: self.m = mission self.game = game @@ -239,6 +239,7 @@ class AircraftConflictGenerator: self.unit_map = unit_map self.flights: List[FlightData] = [] self.air_support = air_support + self.helipads = helipads @cached_property def use_client(self) -> bool: @@ -550,40 +551,37 @@ class AircraftConflictGenerator: unit_type, side.id, cp.name ) ) - helipad = cp.get_free_helipad() - if helipad is not None and helipad.static_unit is not None: - group = self._generate_at_group( - name=name, - side=side, - unit_type=unit_type, - count=count, - start_type=start_type, - at=helipad.static_unit, - ) - # Note : A bit dirty, need better support in pydcs - group.points[0].action = PointAction.FromGroundArea - group.points[0].type = "TakeOffGround" - group.units[0].heading = helipad.heading - if start_type != "Cold": - group.points[0].action = PointAction.FromGroundAreaHot - group.points[0].type = "TakeOffGroundHot" + try: + helipad = self.helipads[cp].pop() + except IndexError as ex: + raise RuntimeError(f"Not enough helipads available at {cp}") from ex - helipad.occupied = True + group = self._generate_at_group( + name=name, + side=side, + unit_type=unit_type, + count=count, + start_type=start_type, + at=helipad, + ) - for i in range(count - 1): - helipad = cp.get_free_helipad() - if helipad is not None: - helipad.occupied = True - group.units[1 + i].position = Point(helipad.x, helipad.y) - group.units[1 + i].heading = helipad.heading - else: - raise RuntimeError( - f"Control Point {cp.name} does not have enough helipads" - ) - return group - else: - raise RuntimeError(f"Control Point {cp.name} does not have enough helipads") + # Note : A bit dirty, need better support in pydcs + group.points[0].action = PointAction.FromGroundArea + group.points[0].type = "TakeOffGround" + group.units[0].heading = helipad.units[0].heading + if start_type != "Cold": + group.points[0].action = PointAction.FromGroundAreaHot + group.points[0].type = "TakeOffGroundHot" + + for i in range(count - 1): + try: + helipad = self.helipads[cp].pop() + group.units[1 + i].position = Point(helipad.x, helipad.y) + group.units[1 + i].heading = helipad.units[0].heading + except IndexError as ex: + raise RuntimeError(f"Not enough helipads available at {cp}") from ex + return group def _add_radio_waypoint( self, diff --git a/gen/groundobjectsgen.py b/gen/groundobjectsgen.py index ffbd579c..b379e764 100644 --- a/gen/groundobjectsgen.py +++ b/gen/groundobjectsgen.py @@ -9,6 +9,7 @@ from __future__ import annotations import logging import random +from collections import defaultdict from typing import ( Dict, Iterator, @@ -587,6 +588,7 @@ class HelipadGenerator: self.game = game self.radio_registry = radio_registry self.tacan_registry = tacan_registry + self.helipads: list[StaticGroup] = [] def generate(self) -> None: @@ -595,7 +597,7 @@ class HelipadGenerator: country = self.m.country(self.game.coalition_for(self.cp.captured).country_name) for i, helipad in enumerate(self.cp.helipads): name = self.cp.name + "_helipad_" + str(i) - logging.info("Generating helipad : " + name) + logging.info("Generating helipad static : " + name) pad = InvisibleFARP(name=name) pad.position = Point(helipad.x, helipad.y) pad.heading = helipad.heading.degrees @@ -606,8 +608,7 @@ class HelipadGenerator: sg.add_point(sp) neutral_country.add_static_group(sg) - helipad.static_unit = sg - helipad.occupied = False + self.helipads.append(sg) # Generate a FARP Ammo and Fuel stack for each pad self.m.static_group( @@ -652,13 +653,18 @@ class GroundObjectsGenerator: self.unit_map = unit_map self.icls_alloc = iter(range(1, 21)) self.runways: Dict[str, RunwayData] = {} + self.helipads: dict[ControlPoint, list[StaticGroup]] = defaultdict(list) def generate(self) -> None: for cp in self.game.theater.controlpoints: country = self.m.country(self.game.coalition_for(cp.captured).country_name) - HelipadGenerator( + + # Generate helipads + helipad_gen = HelipadGenerator( self.m, cp, self.game, self.radio_registry, self.tacan_registry - ).generate() + ) + helipad_gen.generate() + self.helipads[cp] = helipad_gen.helipads for ground_object in cp.ground_objects: generator: GenericGroundObjectGenerator[Any]