Airlift & AirAssault updates from Liberation

Cleanup and reword refuel waypoints

- rename Stopover back to CargoStop
- precise some waypoint naming

Cleanup and refine airlift and airassault waypoints

- Drop Off and Pickup now correctly worded
- Helo waypoints now represent LandingZones for pickup and dropoff

Forbid planes from air assault.

Make air assault drop-off non-optional.

There is always a drop-off location for troops.

Add docs explaining what the assault area is.

Add error handling for cargo drop waypoints.

Document some airlift waypoint behavior.

Remove unnecessary refuel waypoint in airlifts.

Remove CTLD logic from Airlift flightplan for AI

Add Ingress Point to AirAssault FlightPlan

Add simulation halt at AirAssault ingress

Remove AirAssault completly from AutoPlanner

Remove unneeded LandRefuel from Airlift

Clarify cargo waypoints for AirLift and AirAssault
This commit is contained in:
RndName 2022-11-01 20:30:50 +01:00 committed by Raffson
parent 5f15ddc52c
commit b49562f4bc
No known key found for this signature in database
GPG Key ID: B0402B2C9B764D99
12 changed files with 157 additions and 136 deletions

View File

@ -4,28 +4,38 @@ from dataclasses import dataclass
from datetime import timedelta from datetime import timedelta
from typing import Iterator, TYPE_CHECKING, Type from typing import Iterator, TYPE_CHECKING, Type
from game.ato.flightplans.airlift import AirliftLayout from game.ato.flightplans.standard import StandardFlightPlan, StandardLayout
from game.ato.flightplans.standard import StandardFlightPlan
from game.theater.controlpoint import ControlPointType from game.theater.controlpoint import ControlPointType
from game.theater.missiontarget import MissionTarget from game.theater.missiontarget import MissionTarget
from game.utils import Distance, feet, meters from game.utils import Distance, feet, meters
from .ibuilder import IBuilder from .ibuilder import IBuilder
from .planningerror import PlanningError
from .waypointbuilder import WaypointBuilder from .waypointbuilder import WaypointBuilder
from ..flightwaypoint import FlightWaypointType
if TYPE_CHECKING: if TYPE_CHECKING:
from ..flightwaypoint import FlightWaypoint from ..flightwaypoint import FlightWaypoint
@dataclass(frozen=True) @dataclass(frozen=True)
class AirAssaultLayout(AirliftLayout): class AirAssaultLayout(StandardLayout):
# The pickup point is optional because we don't always need to load the cargo. When
# departing from a carrier, LHA, or off-map spawn, the cargo is pre-loaded.
pickup: FlightWaypoint | None
nav_to_ingress: list[FlightWaypoint]
ingress: FlightWaypoint
drop_off: FlightWaypoint
# This is an implementation detail used by CTLD. The aircraft will not go to this
# waypoint. It is used by CTLD as the destination for unloaded troops.
target: FlightWaypoint target: FlightWaypoint
nav_to_home: list[FlightWaypoint]
def iter_waypoints(self) -> Iterator[FlightWaypoint]: def iter_waypoints(self) -> Iterator[FlightWaypoint]:
yield self.departure yield self.departure
yield from self.nav_to_pickup if self.pickup is not None:
if self.pickup:
yield self.pickup yield self.pickup
yield from self.nav_to_drop_off yield from self.nav_to_ingress
yield self.ingress
yield self.drop_off yield self.drop_off
yield self.target yield self.target
yield from self.nav_to_home yield from self.nav_to_home
@ -64,24 +74,32 @@ class AirAssaultFlightPlan(StandardFlightPlan[AirAssaultLayout]):
class Builder(IBuilder[AirAssaultFlightPlan, AirAssaultLayout]): class Builder(IBuilder[AirAssaultFlightPlan, AirAssaultLayout]):
def layout(self) -> AirAssaultLayout: def layout(self) -> AirAssaultLayout:
if not self.flight.is_helo:
raise PlanningError("Air assault is only usable by helicopters")
assert self.package.waypoints is not None
altitude = feet(1500) if self.flight.is_helo else self.doctrine.ingress_altitude altitude = feet(1500) if self.flight.is_helo else self.doctrine.ingress_altitude
altitude_is_agl = self.flight.is_helo altitude_is_agl = self.flight.is_helo
builder = WaypointBuilder(self.flight, self.coalition) builder = WaypointBuilder(self.flight, self.coalition)
if not self.flight.is_helo or self.flight.departure.cptype in [ if self.flight.departure.cptype in [
ControlPointType.AIRCRAFT_CARRIER_GROUP, ControlPointType.AIRCRAFT_CARRIER_GROUP,
ControlPointType.LHA_GROUP, ControlPointType.LHA_GROUP,
ControlPointType.OFF_MAP, ControlPointType.OFF_MAP,
]: ]:
# Non-Helo flights or Off_Map will be preloaded # Off_Map spawns will be preloaded
# Carrier operations load the logistics directly from the carrier # Carrier operations load the logistics directly from the carrier
pickup = None pickup = None
pickup_position = self.flight.departure.position pickup_position = self.flight.departure.position
else: else:
# TODO The calculation of the Pickup LZ is currently randomized. This
# leads to the problem that we can not gurantee that the LZ is clear of
# obstacles. This has to be improved in the future so that the Mission can
# be autoplanned. In the current state the User has to check the created
# Waypoints for the Pickup and Dropoff LZs are free of obstacles.
# Create a special pickup zone for Helos from Airbase / FOB # Create a special pickup zone for Helos from Airbase / FOB
pickup = builder.pickup( pickup = builder.pickup_zone(
MissionTarget( MissionTarget(
"Pickup Zone", "Pickup Zone",
self.flight.departure.position.random_point_within(1200, 600), self.flight.departure.position.random_point_within(1200, 600),
@ -90,6 +108,8 @@ class Builder(IBuilder[AirAssaultFlightPlan, AirAssaultLayout]):
pickup_position = pickup.position pickup_position = pickup.position
assault_area = builder.assault_area(self.package.target) assault_area = builder.assault_area(self.package.target)
heading = self.package.target.position.heading_between_point(pickup_position) heading = self.package.target.position.heading_between_point(pickup_position)
# TODO we can not gurantee a safe LZ for DropOff. See comment above.
drop_off_zone = MissionTarget( drop_off_zone = MissionTarget(
"Dropoff zone", "Dropoff zone",
self.package.target.position.point_from_heading(heading, 1200), self.package.target.position.point_from_heading(heading, 1200),
@ -97,21 +117,19 @@ class Builder(IBuilder[AirAssaultFlightPlan, AirAssaultLayout]):
return AirAssaultLayout( return AirAssaultLayout(
departure=builder.takeoff(self.flight.departure), departure=builder.takeoff(self.flight.departure),
nav_to_pickup=builder.nav_path(
self.flight.departure.position,
pickup_position,
altitude,
altitude_is_agl,
),
pickup=pickup, pickup=pickup,
nav_to_drop_off=builder.nav_path( nav_to_ingress=builder.nav_path(
pickup_position, pickup_position,
drop_off_zone.position, self.package.waypoints.ingress,
altitude, altitude,
altitude_is_agl, altitude_is_agl,
), ),
drop_off=builder.drop_off(drop_off_zone), ingress=builder.ingress(
stopover=None, FlightWaypointType.INGRESS_AIR_ASSAULT,
self.package.waypoints.ingress,
self.package.target,
),
drop_off=builder.dropoff_zone(drop_off_zone),
target=assault_area, target=assault_area,
nav_to_home=builder.nav_path( nav_to_home=builder.nav_path(
drop_off_zone.position, drop_off_zone.position,

View File

@ -19,10 +19,20 @@ if TYPE_CHECKING:
@dataclass(frozen=True) @dataclass(frozen=True)
class AirliftLayout(StandardLayout): class AirliftLayout(StandardLayout):
nav_to_pickup: list[FlightWaypoint] nav_to_pickup: list[FlightWaypoint]
# There will not be a pickup waypoint when the pickup airfield is the departure
# airfield for cargo planes, as the cargo is pre-loaded. Helicopters will still pick
# up the cargo near the airfield.
pickup: FlightWaypoint | None pickup: FlightWaypoint | None
# pickup_zone will be used for player flights to create the CTLD stuff
ctld_pickup_zone: FlightWaypoint | None
nav_to_drop_off: list[FlightWaypoint] nav_to_drop_off: list[FlightWaypoint]
drop_off: FlightWaypoint # There will not be a drop-off waypoint when the drop-off airfield and the arrival
stopover: FlightWaypoint | None # airfield is the same for a cargo plane, as planes will land to unload and we don't
# want a double landing. Helicopters will still drop their cargo near the airfield
# before landing.
drop_off: FlightWaypoint | None
# drop_off_zone will be used for player flights to create the CTLD stuff
ctld_drop_off_zone: FlightWaypoint | None
nav_to_home: list[FlightWaypoint] nav_to_home: list[FlightWaypoint]
def iter_waypoints(self) -> Iterator[FlightWaypoint]: def iter_waypoints(self) -> Iterator[FlightWaypoint]:
@ -30,10 +40,13 @@ class AirliftLayout(StandardLayout):
yield from self.nav_to_pickup yield from self.nav_to_pickup
if self.pickup is not None: if self.pickup is not None:
yield self.pickup yield self.pickup
if self.ctld_pickup_zone is not None:
yield self.ctld_pickup_zone
yield from self.nav_to_drop_off yield from self.nav_to_drop_off
if self.drop_off is not None:
yield self.drop_off yield self.drop_off
if self.stopover is not None: if self.ctld_drop_off_zone is not None:
yield self.stopover yield self.ctld_drop_off_zone
yield from self.nav_to_home yield from self.nav_to_home
yield self.arrival yield self.arrival
if self.divert is not None: if self.divert is not None:
@ -48,7 +61,11 @@ class AirliftFlightPlan(StandardFlightPlan[AirliftLayout]):
@property @property
def tot_waypoint(self) -> FlightWaypoint: def tot_waypoint(self) -> FlightWaypoint:
return self.layout.drop_off # The TOT is the time that the cargo will be dropped off. If the drop-off
# location is the arrival airfield and this is not a helicopter flight, there
# will not be a separate drop-off waypoint; the arrival landing waypoint is the
# drop-off waypoint.
return self.layout.drop_off or self.layout.arrival
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None: def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
# TOT planning isn't really useful for transports. They're behind the front # TOT planning isn't really useful for transports. They're behind the front
@ -77,31 +94,31 @@ class Builder(IBuilder[AirliftFlightPlan, AirliftLayout]):
builder = WaypointBuilder(self.flight, self.coalition) builder = WaypointBuilder(self.flight, self.coalition)
pickup = None pickup = None
stopover = None drop_off = None
pickup_zone = None
drop_off_zone = None
if cargo.origin != self.flight.departure:
pickup = builder.cargo_stop(cargo.origin)
if cargo.next_stop != self.flight.arrival:
drop_off = builder.cargo_stop(cargo.next_stop)
if self.flight.is_helo: if self.flight.is_helo:
# Create a pickupzone where the cargo will be spawned # Create CTLD Zones for Helo flights
pickup_zone = MissionTarget( pickup_zone = builder.pickup_zone(
MissionTarget(
"Pickup Zone", cargo.origin.position.random_point_within(1000, 200) "Pickup Zone", cargo.origin.position.random_point_within(1000, 200)
) )
pickup = builder.pickup(pickup_zone) )
# If The cargo is at the departure controlpoint, the pickup waypoint should drop_off_zone = builder.dropoff_zone(
# only be created for client flights MissionTarget(
pickup.only_for_player = cargo.origin == self.flight.departure
# Create a dropoff zone where the cargo should be dropped
drop_off_zone = MissionTarget(
"Dropoff zone", "Dropoff zone",
cargo.next_stop.position.random_point_within(1000, 200), cargo.next_stop.position.random_point_within(1000, 200),
) )
drop_off = builder.drop_off(drop_off_zone) )
# Show the zone waypoints only to the player
# Add an additional stopover point so that the flight can refuel pickup_zone.only_for_player = True
stopover = builder.stopover(cargo.next_stop) drop_off_zone.only_for_player = True
else:
# Fixed Wing will get stopover points for pickup and dropoff
if cargo.origin != self.flight.departure:
pickup = builder.stopover(cargo.origin, "PICKUP")
drop_off = builder.stopover(cargo.next_stop, "DROP OFF")
nav_to_pickup = builder.nav_path( nav_to_pickup = builder.nav_path(
self.flight.departure.position, self.flight.departure.position,
@ -110,20 +127,11 @@ class Builder(IBuilder[AirliftFlightPlan, AirliftLayout]):
altitude_is_agl, altitude_is_agl,
) )
if self.flight.client_count > 0:
# Normal Landing Waypoint
arrival = builder.land(self.flight.arrival)
else:
# The AI Needs another Stopover 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/
arrival = builder.stopover(self.flight.arrival, "LANDING")
return AirliftLayout( return AirliftLayout(
departure=builder.takeoff(self.flight.departure), departure=builder.takeoff(self.flight.departure),
nav_to_pickup=nav_to_pickup, nav_to_pickup=nav_to_pickup,
pickup=pickup, pickup=pickup,
ctld_pickup_zone=pickup_zone,
nav_to_drop_off=builder.nav_path( nav_to_drop_off=builder.nav_path(
cargo.origin.position, cargo.origin.position,
cargo.next_stop.position, cargo.next_stop.position,
@ -131,14 +139,14 @@ class Builder(IBuilder[AirliftFlightPlan, AirliftLayout]):
altitude_is_agl, altitude_is_agl,
), ),
drop_off=drop_off, drop_off=drop_off,
stopover=stopover, ctld_drop_off_zone=drop_off_zone,
nav_to_home=builder.nav_path( nav_to_home=builder.nav_path(
cargo.origin.position, cargo.origin.position,
self.flight.arrival.position, self.flight.arrival.position,
altitude, altitude,
altitude_is_agl, altitude_is_agl,
), ),
arrival=arrival, arrival=builder.land(self.flight.arrival),
divert=builder.divert(self.flight.divert), divert=builder.divert(self.flight.divert),
bullseye=builder.bullseye(), bullseye=builder.bullseye(),
) )

View File

@ -304,6 +304,13 @@ class WaypointBuilder:
return self._target_area(f"ATTACK {target.name}", target, flyover=True) return self._target_area(f"ATTACK {target.name}", target, flyover=True)
def assault_area(self, target: MissionTarget) -> FlightWaypoint: def assault_area(self, target: MissionTarget) -> FlightWaypoint:
"""A destination waypoint used by air-assault ground troops.
This waypoint is an implementation detail for CTLD and should not be followed by
aircraft.
"""
# TODO: Add a property that can hide this waypoint from the player's flight
# plan.
return self._target_area(f"ASSAULT {target.name}", target) return self._target_area(f"ASSAULT {target.name}", target)
@staticmethod @staticmethod
@ -512,58 +519,50 @@ class WaypointBuilder:
) )
@staticmethod @staticmethod
def stopover(stopover: ControlPoint, name: str = "STOPOVER") -> FlightWaypoint: def pickup_zone(pick_up: MissionTarget) -> FlightWaypoint:
"""Creates a stopover waypoint. """Creates a pickup landing zone waypoint
This waypoint is used to generate the Trigger Zone used for AirAssault and
Args: AirLift using the CTLD plugin (see LogisticsGenerator)
control_point: Pick up location.
""" """
return FlightWaypoint( return FlightWaypoint(
name, "PICKUPZONE",
FlightWaypointType.STOPOVER, FlightWaypointType.PICKUP_ZONE,
stopover.position,
meters(0),
"RADIO",
description=f"Stopover at {stopover}",
pretty_name="Stopover location",
control_point=stopover,
)
@staticmethod
def pickup(pick_up: MissionTarget) -> FlightWaypoint:
"""Creates a cargo pickup waypoint.
Args:
control_point: Pick up location.
"""
control_point = pick_up if isinstance(pick_up, ControlPoint) else None
return FlightWaypoint(
"PICKUP",
FlightWaypointType.PICKUP,
pick_up.position, pick_up.position,
meters(0), meters(0),
"RADIO", "RADIO",
description=f"Pick up cargo from {pick_up.name}", description=f"Pick up cargo from {pick_up.name}",
pretty_name="Pick up location", pretty_name="Pick-up zone",
control_point=control_point,
) )
@staticmethod @staticmethod
def drop_off(drop_off: MissionTarget) -> FlightWaypoint: def dropoff_zone(drop_off: MissionTarget) -> FlightWaypoint:
"""Creates a cargo drop-off waypoint. """Creates a dropoff landing zone waypoint
This waypoint is used to generate the Trigger Zone used for AirAssault and
Args: AirLift using the CTLD plugin (see LogisticsGenerator)
control_point: Drop-off location.
""" """
control_point = drop_off if isinstance(drop_off, ControlPoint) else None
return FlightWaypoint( return FlightWaypoint(
"DROP OFF", "DROPOFFZONE",
FlightWaypointType.DROP_OFF, FlightWaypointType.DROPOFF_ZONE,
drop_off.position, drop_off.position,
meters(0), meters(0),
"RADIO", "RADIO",
description=f"Drop off cargo at {drop_off.name}", description=f"Drop off cargo at {drop_off.name}",
pretty_name="Drop off location", pretty_name="Drop-off zone",
)
@staticmethod
def cargo_stop(control_point: ControlPoint) -> FlightWaypoint:
"""Creates a cargo stop waypoint.
This waypoint is used by AirLift as a landing and stopover waypoint
"""
return FlightWaypoint(
"CARGOSTOP",
FlightWaypointType.CARGO_STOP,
control_point.position,
meters(0),
"RADIO",
description=f"Stop for cargo at {control_point.name}",
pretty_name="Cargo stop",
control_point=control_point, control_point=control_point,
) )

View File

@ -137,6 +137,7 @@ class InFlight(FlightState, ABC):
FlightWaypointType.INGRESS_OCA_RUNWAY, FlightWaypointType.INGRESS_OCA_RUNWAY,
FlightWaypointType.INGRESS_SEAD, FlightWaypointType.INGRESS_SEAD,
FlightWaypointType.INGRESS_STRIKE, FlightWaypointType.INGRESS_STRIKE,
FlightWaypointType.INGRESS_AIR_ASSAULT,
} }
return self.current_waypoint.waypoint_type in contact_types return self.current_waypoint.waypoint_type in contact_types

View File

@ -43,8 +43,9 @@ class FlightWaypointType(IntEnum):
DIVERT = 23 DIVERT = 23
INGRESS_OCA_RUNWAY = 24 INGRESS_OCA_RUNWAY = 24
INGRESS_OCA_AIRCRAFT = 25 INGRESS_OCA_AIRCRAFT = 25
PICKUP = 26 PICKUP_ZONE = 26 # Pickup Zone for cargo or troops
DROP_OFF = 27 DROPOFF_ZONE = 27 # Dropoff Zone for cargo or troops
BULLSEYE = 28 BULLSEYE = 28
REFUEL = 29 # Should look for nearby tanker to refuel from. REFUEL = 29 # Should look for nearby tanker to refuel from.
STOPOVER = 30 # Stopover landing point using the LandingReFuAr waypoint type CARGO_STOP = 30 # Stopover landing point using the LandingReFuAr waypoint type
INGRESS_AIR_ASSAULT = 31

View File

@ -139,14 +139,6 @@ class ObjectiveFinder:
"""Iterates over all active front lines in the theater.""" """Iterates over all active front lines in the theater."""
yield from self.game.theater.conflicts() yield from self.game.theater.conflicts()
def air_assault_targets(self) -> Iterator[ControlPoint]:
"""Iterates over all capturable controlpoints for all active front lines"""
if not self.game.settings.plugin_option("ctld"):
# Air Assault should only be tasked with CTLD enabled
return
for front_line in self.front_lines():
yield front_line.control_point_hostile_to(self.is_player)
def vulnerable_control_points(self) -> Iterator[ControlPoint]: def vulnerable_control_points(self) -> Iterator[ControlPoint]:
"""Iterates over friendly CPs that are vulnerable to enemy CPs. """Iterates over friendly CPs that are vulnerable to enemy CPs.

View File

@ -7,7 +7,6 @@ from game.commander.tasks.compound.destroyenemygroundunits import (
from game.commander.tasks.compound.reduceenemyfrontlinecapacity import ( from game.commander.tasks.compound.reduceenemyfrontlinecapacity import (
ReduceEnemyFrontLineCapacity, ReduceEnemyFrontLineCapacity,
) )
from game.commander.tasks.primitive.airassault import PlanAirAssault
from game.commander.tasks.primitive.breakthroughattack import BreakthroughAttack from game.commander.tasks.primitive.breakthroughattack import BreakthroughAttack
from game.commander.theaterstate import TheaterState from game.commander.theaterstate import TheaterState
from game.htn import CompoundTask, Method from game.htn import CompoundTask, Method
@ -19,7 +18,6 @@ class CaptureBase(CompoundTask[TheaterState]):
front_line: FrontLine front_line: FrontLine
def each_valid_method(self, state: TheaterState) -> Iterator[Method[TheaterState]]: def each_valid_method(self, state: TheaterState) -> Iterator[Method[TheaterState]]:
yield [PlanAirAssault(self.enemy_cp(state))]
yield [BreakthroughAttack(self.front_line, state.context.coalition.player)] yield [BreakthroughAttack(self.front_line, state.context.coalition.player)]
yield [DestroyEnemyGroundUnits(self.front_line)] yield [DestroyEnemyGroundUnits(self.front_line)]
if self.worth_destroying_ammo_depots(state): if self.worth_destroying_ammo_depots(state):

View File

@ -45,7 +45,6 @@ class TheaterState(WorldState["TheaterState"]):
context: PersistentContext context: PersistentContext
barcaps_needed: dict[ControlPoint, int] barcaps_needed: dict[ControlPoint, int]
active_front_lines: list[FrontLine] active_front_lines: list[FrontLine]
air_assault_targets: list[ControlPoint]
front_line_stances: dict[FrontLine, Optional[CombatStance]] front_line_stances: dict[FrontLine, Optional[CombatStance]]
vulnerable_front_lines: list[FrontLine] vulnerable_front_lines: list[FrontLine]
aewc_targets: list[MissionTarget] aewc_targets: list[MissionTarget]
@ -110,7 +109,6 @@ class TheaterState(WorldState["TheaterState"]):
context=self.context, context=self.context,
barcaps_needed=dict(self.barcaps_needed), barcaps_needed=dict(self.barcaps_needed),
active_front_lines=list(self.active_front_lines), active_front_lines=list(self.active_front_lines),
air_assault_targets=list(self.air_assault_targets),
front_line_stances=dict(self.front_line_stances), front_line_stances=dict(self.front_line_stances),
vulnerable_front_lines=list(self.vulnerable_front_lines), vulnerable_front_lines=list(self.vulnerable_front_lines),
aewc_targets=list(self.aewc_targets), aewc_targets=list(self.aewc_targets),
@ -161,7 +159,6 @@ class TheaterState(WorldState["TheaterState"]):
cp: barcap_rounds for cp in finder.vulnerable_control_points() cp: barcap_rounds for cp in finder.vulnerable_control_points()
}, },
active_front_lines=list(finder.front_lines()), active_front_lines=list(finder.front_lines()),
air_assault_targets=list(finder.air_assault_targets()),
front_line_stances={f: None for f in finder.front_lines()}, front_line_stances={f: None for f in finder.front_lines()},
vulnerable_front_lines=list(finder.front_lines()), vulnerable_front_lines=list(finder.front_lines()),
aewc_targets=[finder.farthest_friendly_control_point()], aewc_targets=[finder.farthest_friendly_control_point()],

View File

@ -1,24 +1,14 @@
from dcs.point import MovingPoint from dcs.point import MovingPoint, PointAction
from dcs.point import PointAction
from dcs.task import Land
from game.utils import feet
from .pydcswaypointbuilder import PydcsWaypointBuilder from .pydcswaypointbuilder import PydcsWaypointBuilder
class CargoStopBuilder(PydcsWaypointBuilder): class CargoStopBuilder(PydcsWaypointBuilder):
def build(self) -> MovingPoint: def build(self) -> MovingPoint:
waypoint = super().build() waypoint = super().build()
# Create a landing task, currently only for Helos! waypoint.type = "LandingReFuAr"
if self.flight.is_helo: waypoint.action = PointAction.LandingReFuAr
# Calculate a landing point with a small buffer to prevent AI from landing waypoint.landing_refuel_rearm_time = 2 # Minutes.
# directly at the static ammo depot and exploding if (control_point := self.waypoint.control_point) is not None:
landing_point = waypoint.position.random_point_within(15, 5) waypoint.airdrome_id = control_point.airdrome_id_for_landing
# Use Land Task with 30s duration for helos
waypoint.add_task(Land(landing_point, duration=30))
else:
# Fixed wing will drop the cargo at the waypoint so we set a lower altitude
waypoint.alt = int(feet(10000).meters)
waypoint.alt_type = "BARO"
waypoint.action = PointAction.FlyOverPoint
return waypoint return waypoint

View File

@ -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

View File

@ -16,12 +16,12 @@ from game.ato import Flight, FlightWaypoint
from game.ato.flightstate import InFlight, WaitingForStart from game.ato.flightstate import InFlight, WaitingForStart
from game.ato.flightwaypointtype import FlightWaypointType from game.ato.flightwaypointtype import FlightWaypointType
from game.ato.starttype import StartType from game.ato.starttype import StartType
from game.missiongenerator.aircraft.waypoints.stopover import StopoverBuilder from game.missiongenerator.aircraft.waypoints.cargostop import CargoStopBuilder
from game.missiongenerator.missiondata import MissionData from game.missiongenerator.missiondata import MissionData
from game.settings import Settings from game.settings import Settings
from game.utils import pairwise from game.utils import pairwise
from .baiingress import BaiIngressBuilder from .baiingress import BaiIngressBuilder
from .cargostop import CargoStopBuilder from .landingzone import LandingZoneBuilder
from .casingress import CasIngressBuilder from .casingress import CasIngressBuilder
from .deadingress import DeadIngressBuilder from .deadingress import DeadIngressBuilder
from .default import DefaultWaypointBuilder from .default import DefaultWaypointBuilder
@ -118,7 +118,6 @@ class WaypointGenerator:
def builder_for_waypoint(self, waypoint: FlightWaypoint) -> PydcsWaypointBuilder: def builder_for_waypoint(self, waypoint: FlightWaypoint) -> PydcsWaypointBuilder:
builders = { builders = {
FlightWaypointType.DROP_OFF: CargoStopBuilder,
FlightWaypointType.INGRESS_BAI: BaiIngressBuilder, FlightWaypointType.INGRESS_BAI: BaiIngressBuilder,
FlightWaypointType.INGRESS_CAS: CasIngressBuilder, FlightWaypointType.INGRESS_CAS: CasIngressBuilder,
FlightWaypointType.INGRESS_DEAD: DeadIngressBuilder, FlightWaypointType.INGRESS_DEAD: DeadIngressBuilder,
@ -133,9 +132,10 @@ class WaypointGenerator:
FlightWaypointType.LOITER: HoldPointBuilder, FlightWaypointType.LOITER: HoldPointBuilder,
FlightWaypointType.PATROL: RaceTrackEndBuilder, FlightWaypointType.PATROL: RaceTrackEndBuilder,
FlightWaypointType.PATROL_TRACK: RaceTrackBuilder, FlightWaypointType.PATROL_TRACK: RaceTrackBuilder,
FlightWaypointType.PICKUP: CargoStopBuilder, FlightWaypointType.PICKUP_ZONE: LandingZoneBuilder,
FlightWaypointType.DROPOFF_ZONE: LandingZoneBuilder,
FlightWaypointType.REFUEL: RefuelPointBuilder, FlightWaypointType.REFUEL: RefuelPointBuilder,
FlightWaypointType.STOPOVER: StopoverBuilder, FlightWaypointType.CARGO_STOP: CargoStopBuilder,
} }
builder = builders.get(waypoint.waypoint_type, DefaultWaypointBuilder) builder = builders.get(waypoint.waypoint_type, DefaultWaypointBuilder)
return builder( return builder(

View File

@ -57,8 +57,8 @@ class LogisticsGenerator:
if ( if (
waypoint.waypoint_type waypoint.waypoint_type
not in [ not in [
FlightWaypointType.PICKUP, FlightWaypointType.PICKUP_ZONE,
FlightWaypointType.DROP_OFF, FlightWaypointType.DROPOFF_ZONE,
] ]
or waypoint.only_for_player or waypoint.only_for_player
and not self.flight.client_count and not self.flight.client_count
@ -69,7 +69,7 @@ class LogisticsGenerator:
self.mission.triggers.add_triggerzone( self.mission.triggers.add_triggerzone(
waypoint.position, ZONE_RADIUS, False, zone_name waypoint.position, ZONE_RADIUS, False, zone_name
) )
if waypoint.waypoint_type == FlightWaypointType.PICKUP: if waypoint.waypoint_type == FlightWaypointType.PICKUP_ZONE:
pickup_point = waypoint.position pickup_point = waypoint.position
logistics_info.pickup_zone = zone_name logistics_info.pickup_zone = zone_name
else: else: