From b4b9bbf476b644fb4a37b408b6c767f4144782d7 Mon Sep 17 00:00:00 2001 From: RndName Date: Tue, 1 Nov 2022 21:01:48 +0100 Subject: [PATCH] Cleanup and refine airlift and airassault waypoints - Drop Off and Pickup now correctly worded - Helo waypoints now represent LandingZones for pickup and dropoff --- game/ato/flightplans/airassault.py | 27 +++++++++---- game/ato/flightplans/airlift.py | 23 ++++++----- game/ato/flightplans/waypointbuilder.py | 39 +++++++++++++++---- game/ato/flightwaypointtype.py | 4 +- .../aircraft/waypoints/landingzone.py | 17 ++++++++ .../aircraft/waypoints/waypointgenerator.py | 6 +-- game/missiongenerator/logisticsgenerator.py | 6 +-- 7 files changed, 89 insertions(+), 33 deletions(-) create mode 100644 game/missiongenerator/aircraft/waypoints/landingzone.py diff --git a/game/ato/flightplans/airassault.py b/game/ato/flightplans/airassault.py index 9351d236..46769a6c 100644 --- a/game/ato/flightplans/airassault.py +++ b/game/ato/flightplans/airassault.py @@ -4,8 +4,7 @@ from dataclasses import dataclass from datetime import timedelta from typing import Iterator, TYPE_CHECKING, Type -from game.ato.flightplans.airlift import AirliftLayout -from game.ato.flightplans.standard import StandardFlightPlan +from game.ato.flightplans.standard import StandardFlightPlan, StandardLayout from game.theater.controlpoint import ControlPointType from game.theater.missiontarget import MissionTarget from game.utils import Distance, feet, meters @@ -17,13 +16,18 @@ if TYPE_CHECKING: @dataclass(frozen=True) -class AirAssaultLayout(AirliftLayout): +class AirAssaultLayout(StandardLayout): + nav_to_pickup: list[FlightWaypoint] + pickup: FlightWaypoint | None + nav_to_drop_off: list[FlightWaypoint] + drop_off: FlightWaypoint target: FlightWaypoint + nav_to_home: list[FlightWaypoint] def iter_waypoints(self) -> Iterator[FlightWaypoint]: yield self.departure yield from self.nav_to_pickup - if self.pickup: + if self.pickup is not None: yield self.pickup yield from self.nav_to_drop_off yield self.drop_off @@ -81,19 +85,27 @@ class Builder(IBuilder[AirAssaultFlightPlan, AirAssaultLayout]): pickup_position = self.flight.departure.position else: # Create a special pickup zone for Helos from Airbase / FOB - pickup = builder.pickup( + pickup = builder.cargo_pickup( MissionTarget( "Pickup Zone", self.flight.departure.position.random_point_within(1200, 600), - ) + ), + self.flight.is_helo, ) pickup_position = pickup.position assault_area = builder.assault_area(self.package.target) heading = self.package.target.position.heading_between_point(pickup_position) + + # Once there is a plane which is capable of AirDrop Paratrooper + # we can make use of the AIRDROP Wayppoint type. + # This would also need a special Waypointbuilder. + # Currently AirAssault can only be used by Helos so we just create + # the drop_off Landing Zone drop_off_zone = MissionTarget( "Dropoff zone", self.package.target.position.point_from_heading(heading, 1200), ) + drop_off = builder.cargo_dropoff(drop_off_zone, self.flight.is_helo) return AirAssaultLayout( departure=builder.takeoff(self.flight.departure), @@ -110,8 +122,7 @@ class Builder(IBuilder[AirAssaultFlightPlan, AirAssaultLayout]): altitude, altitude_is_agl, ), - drop_off=builder.drop_off(drop_off_zone), - refuel=None, + drop_off=drop_off, target=assault_area, nav_to_home=builder.nav_path( drop_off_zone.position, diff --git a/game/ato/flightplans/airlift.py b/game/ato/flightplans/airlift.py index a2a64f6b..85f9c49c 100644 --- a/game/ato/flightplans/airlift.py +++ b/game/ato/flightplans/airlift.py @@ -21,7 +21,7 @@ class AirliftLayout(StandardLayout): nav_to_pickup: list[FlightWaypoint] pickup: FlightWaypoint | None nav_to_drop_off: list[FlightWaypoint] - drop_off: FlightWaypoint + drop_off: FlightWaypoint | None refuel: FlightWaypoint | None nav_to_home: list[FlightWaypoint] @@ -31,7 +31,8 @@ class AirliftLayout(StandardLayout): if self.pickup is not None: yield self.pickup yield from self.nav_to_drop_off - yield self.drop_off + if self.drop_off is not None: + yield self.drop_off if self.refuel is not None: yield self.refuel yield from self.nav_to_home @@ -48,7 +49,7 @@ class AirliftFlightPlan(StandardFlightPlan[AirliftLayout]): @property def tot_waypoint(self) -> FlightWaypoint: - return self.layout.drop_off + return self.layout.drop_off or self.layout.arrival def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None: # TOT planning isn't really useful for transports. They're behind the front @@ -78,12 +79,13 @@ class Builder(IBuilder[AirliftFlightPlan, AirliftLayout]): pickup = None refuel = None + drop_off = None if self.flight.is_helo: # Create a pickupzone where the cargo will be spawned pickup_zone = MissionTarget( "Pickup Zone", cargo.origin.position.random_point_within(1000, 200) ) - pickup = builder.pickup(pickup_zone) + pickup = builder.cargo_pickup(pickup_zone, True) # If The cargo is at the departure controlpoint, the pickup waypoint should # only be created for client flights pickup.only_for_player = cargo.origin == self.flight.departure @@ -93,15 +95,16 @@ class Builder(IBuilder[AirliftFlightPlan, AirliftLayout]): "Dropoff zone", cargo.next_stop.position.random_point_within(1000, 200), ) - drop_off = builder.drop_off(drop_off_zone) + drop_off = builder.cargo_dropoff(drop_off_zone, True) - # Add an additional stopover point so that the flight can refuel + # Add an additional refuel waypoint refuel = builder.land_refuel(cargo.next_stop) else: - # Fixed Wing will get stopover points for pickup and dropoff + # Fixed Wing will get landing&refuel waypoints for pickup and dropoff if cargo.origin != self.flight.departure: - pickup = builder.land_refuel(cargo.origin) - drop_off = builder.land_refuel(cargo.next_stop) + pickup = builder.cargo_pickup(cargo.origin, False) + if cargo.next_stop != self.flight.arrival: + drop_off = builder.cargo_dropoff(cargo.next_stop, False) nav_to_pickup = builder.nav_path( self.flight.departure.position, @@ -114,7 +117,7 @@ class Builder(IBuilder[AirliftFlightPlan, AirliftLayout]): # Normal Landing Waypoint arrival = builder.land(self.flight.arrival) else: - # The AI Needs another Stopover point to actually fly back to the original + # The AI Needs another landing&refuel point to actually fly back to the original # base. Otherwise the Cargo drop will be the new Landing Waypoint and the # AI will end its mission there instead of flying back. # https://forum.dcs.world/topic/211775-landing-to-refuel-and-rearm-the-landingrefuar-waypoint/ diff --git a/game/ato/flightplans/waypointbuilder.py b/game/ato/flightplans/waypointbuilder.py index 9980bb51..ad0d97fd 100644 --- a/game/ato/flightplans/waypointbuilder.py +++ b/game/ato/flightplans/waypointbuilder.py @@ -526,40 +526,65 @@ class WaypointBuilder: ) @staticmethod - def pickup(pick_up: MissionTarget) -> FlightWaypoint: + def cargo_pickup(pick_up: MissionTarget, is_helo: bool) -> FlightWaypoint: """Creates a cargo pickup waypoint. Args: control_point: Pick up location. """ control_point = pick_up if isinstance(pick_up, ControlPoint) else None + if is_helo: + return FlightWaypoint( + "PICKUP", + FlightWaypointType.PICKUP_ZONE, + pick_up.position, + meters(0), + "RADIO", + description=f"Pick up cargo from {pick_up.name}", + pretty_name="Pick-up zone", + control_point=control_point, + ) return FlightWaypoint( "PICKUP", - FlightWaypointType.PICKUP, + FlightWaypointType.LAND_REFUEL, pick_up.position, meters(0), "RADIO", description=f"Pick up cargo from {pick_up.name}", - pretty_name="Pick up location", + pretty_name="Cargo pick-up", control_point=control_point, ) @staticmethod - def drop_off(drop_off: MissionTarget) -> FlightWaypoint: + def cargo_dropoff(drop_off: MissionTarget, is_helo: bool) -> FlightWaypoint: """Creates a cargo drop-off waypoint. + This waypoint is used by AirLift and AirAssault to drop cargo or troops + at the given location Args: control_point: Drop-off location. + is_helo: Differentiate behaviour between plane and helo """ control_point = drop_off if isinstance(drop_off, ControlPoint) else None + if is_helo: + return FlightWaypoint( + "DROPOFF", + FlightWaypointType.DROPOFF_ZONE, + drop_off.position, + meters(0), + "RADIO", + description=f"Drop off cargo at {drop_off.name}", + pretty_name="Drop-off zone", + control_point=control_point, + ) return FlightWaypoint( - "DROP OFF", - FlightWaypointType.DROP_OFF, + "DROPOFF", + FlightWaypointType.LAND_REFUEL, drop_off.position, meters(0), "RADIO", description=f"Drop off cargo at {drop_off.name}", - pretty_name="Drop off location", + pretty_name="Cargo drop-off", control_point=control_point, ) diff --git a/game/ato/flightwaypointtype.py b/game/ato/flightwaypointtype.py index f51a621b..7376a54a 100644 --- a/game/ato/flightwaypointtype.py +++ b/game/ato/flightwaypointtype.py @@ -43,8 +43,8 @@ class FlightWaypointType(IntEnum): DIVERT = 23 INGRESS_OCA_RUNWAY = 24 INGRESS_OCA_AIRCRAFT = 25 - PICKUP = 26 - DROP_OFF = 27 + PICKUP_ZONE = 26 # Pickup Zone for cargo or troops + DROPOFF_ZONE = 27 # Dropoff Zone for cargo or troops BULLSEYE = 28 REFUEL = 29 # Should look for nearby tanker to refuel from. CARGO_STOP = 30 # Stopover landing point using the LandingReFuAr waypoint type diff --git a/game/missiongenerator/aircraft/waypoints/landingzone.py b/game/missiongenerator/aircraft/waypoints/landingzone.py new file mode 100644 index 00000000..338bb97c --- /dev/null +++ b/game/missiongenerator/aircraft/waypoints/landingzone.py @@ -0,0 +1,17 @@ +from dcs.point import MovingPoint +from dcs.task import Land + + +from .pydcswaypointbuilder import PydcsWaypointBuilder + + +class LandingZoneBuilder(PydcsWaypointBuilder): + def build(self) -> MovingPoint: + waypoint = super().build() + # Create a landing task, currently only for Helos! + # Calculate a landing point with a small buffer to prevent AI from landing + # directly at the static ammo depot and exploding + landing_point = waypoint.position.random_point_within(15, 5) + # Use Land Task with 30s duration for helos + waypoint.add_task(Land(landing_point, duration=30)) + return waypoint diff --git a/game/missiongenerator/aircraft/waypoints/waypointgenerator.py b/game/missiongenerator/aircraft/waypoints/waypointgenerator.py index 1ff4dfc8..da59387a 100644 --- a/game/missiongenerator/aircraft/waypoints/waypointgenerator.py +++ b/game/missiongenerator/aircraft/waypoints/waypointgenerator.py @@ -21,7 +21,7 @@ from game.missiongenerator.missiondata import MissionData from game.settings import Settings from game.utils import pairwise from .baiingress import BaiIngressBuilder -from .cargostop import CargoStopBuilder +from .landingzone import LandingZoneBuilder from .casingress import CasIngressBuilder from .deadingress import DeadIngressBuilder from .default import DefaultWaypointBuilder @@ -118,7 +118,6 @@ class WaypointGenerator: def builder_for_waypoint(self, waypoint: FlightWaypoint) -> PydcsWaypointBuilder: builders = { - FlightWaypointType.DROP_OFF: CargoStopBuilder, FlightWaypointType.INGRESS_BAI: BaiIngressBuilder, FlightWaypointType.INGRESS_CAS: CasIngressBuilder, FlightWaypointType.INGRESS_DEAD: DeadIngressBuilder, @@ -133,7 +132,8 @@ class WaypointGenerator: FlightWaypointType.LOITER: HoldPointBuilder, FlightWaypointType.PATROL: RaceTrackEndBuilder, FlightWaypointType.PATROL_TRACK: RaceTrackBuilder, - FlightWaypointType.PICKUP: CargoStopBuilder, + FlightWaypointType.PICKUP_ZONE: LandingZoneBuilder, + FlightWaypointType.DROPOFF_ZONE: LandingZoneBuilder, FlightWaypointType.REFUEL: RefuelPointBuilder, FlightWaypointType.CARGO_STOP: CargoStopBuilder, } diff --git a/game/missiongenerator/logisticsgenerator.py b/game/missiongenerator/logisticsgenerator.py index 3ee7c12f..08f6cc84 100644 --- a/game/missiongenerator/logisticsgenerator.py +++ b/game/missiongenerator/logisticsgenerator.py @@ -56,8 +56,8 @@ class LogisticsGenerator: if ( waypoint.waypoint_type not in [ - FlightWaypointType.PICKUP, - FlightWaypointType.DROP_OFF, + FlightWaypointType.PICKUP_ZONE, + FlightWaypointType.DROPOFF_ZONE, ] or waypoint.only_for_player and not self.flight.client_count @@ -68,7 +68,7 @@ class LogisticsGenerator: self.mission.triggers.add_triggerzone( waypoint.position, ZONE_RADIUS, False, zone_name ) - if waypoint.waypoint_type == FlightWaypointType.PICKUP: + if waypoint.waypoint_type == FlightWaypointType.PICKUP_ZONE: pickup_point = waypoint.position logistics_info.pickup_zone = zone_name else: