Helicopter waypoint altitude configurable (#207)

* Helicopter waypoint altitude configurable

Added a new option in Settings: Helicopter waypoint altitude (feet AGL).
It sets the waypoint altitude for helicopters in feet AGL. In campaigns in more mountainous areas, you might want to increase this setting to avoid the AI flying into the terrain.

* black?

* Distinguish cruise/combat altitudes for helicopters

Also includes a refactor for WaypointBuilder so it doesn't need a coalition. It can already reference the coalition from the flight.

* Update changelog.md

---------

Co-authored-by: Raffson <Raffson@users.noreply.github.com>
This commit is contained in:
MetalStormGhost 2023-10-02 19:54:21 +03:00 committed by GitHub
parent 226a171550
commit 54777a9045
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 123 additions and 50 deletions

View File

@ -19,6 +19,7 @@
* **[Campaign Management]** Improve squadron retreat logic to account for parking-slot sizes
* **[Autoplanner]** Support for auto-planning Air Assaults
* **[UI]** Improved frequency selector to support all modeled bands for every aircraft's intra-flight radio
* **[Options]** New options in Settings: Helicopter waypoint altitude (feet AGL) for combat & cruise waypoints
## Fixes
* **[Mission Generation]** Anti-ship strikes should use "group attack" in their attack-task

View File

@ -49,7 +49,7 @@ class Builder(IBuilder[AewcFlightPlan, PatrollingLayout]):
# Station 80nm outside the threat zone.
threat_buffer = nautical_miles(
self.flight.coalition.game.settings.aewc_threat_buffer_min_distance
self.coalition.game.settings.aewc_threat_buffer_min_distance
)
if self.threat_zones.threatened(location.position):
orbit_distance = distance_to_threat + threat_buffer
@ -68,7 +68,7 @@ class Builder(IBuilder[AewcFlightPlan, PatrollingLayout]):
orbit_heading.left.degrees, racetrack_half_distance
)
builder = WaypointBuilder(self.flight, self.coalition)
builder = WaypointBuilder(self.flight)
if self.flight.unit_type.patrol_altitude is not None:
altitude = self.flight.unit_type.patrol_altitude

View File

@ -105,10 +105,11 @@ class Builder(FormationAttackBuilder[AirAssaultFlightPlan, AirAssaultLayout]):
)
assert self.package.waypoints is not None
altitude = feet(1500) if self.flight.is_helo else self.doctrine.ingress_altitude
heli_alt = feet(self.coalition.game.settings.heli_cruise_alt_agl)
altitude = heli_alt if self.flight.is_helo else self.doctrine.ingress_altitude
altitude_is_agl = self.flight.is_helo
builder = WaypointBuilder(self.flight, self.coalition)
builder = WaypointBuilder(self.flight)
if self.flight.is_hercules or self.flight.departure.cptype in [
ControlPointType.AIRCRAFT_CARRIER_GROUP,

View File

@ -110,10 +110,11 @@ class Builder(IBuilder[AirliftFlightPlan, AirliftLayout]):
"Cannot plan transport mission for flight with no cargo."
)
altitude = feet(1500)
altitude_is_agl = True
heli_alt = feet(self.coalition.game.settings.heli_cruise_alt_agl)
altitude = heli_alt if self.flight.is_helo else self.doctrine.ingress_altitude
altitude_is_agl = self.flight.is_helo
builder = WaypointBuilder(self.flight, self.coalition)
builder = WaypointBuilder(self.flight)
pickup = None
drop_off = None

View File

@ -48,7 +48,7 @@ class Builder(CapBuilder[BarCapFlightPlan, PatrollingLayout]):
min(self.doctrine.max_patrol_altitude, randomized_alt),
)
builder = WaypointBuilder(self.flight, self.coalition)
builder = WaypointBuilder(self.flight)
start, end = builder.race_track(start_pos, end_pos, patrol_alt)
return PatrollingLayout(

View File

@ -6,7 +6,7 @@ from datetime import timedelta
from typing import TYPE_CHECKING, Type
from game.theater import FrontLine
from game.utils import Distance, Speed, kph, meters, nautical_miles
from game.utils import Distance, Speed, kph, feet, nautical_miles
from .ibuilder import IBuilder
from .invalidobjectivelocation import InvalidObjectiveLocation
from .patrolling import PatrollingFlightPlan, PatrollingLayout
@ -97,11 +97,13 @@ class Builder(IBuilder[CasFlightPlan, CasLayout]):
if egress_distance < ingress_distance:
ingress, egress = egress, ingress
builder = WaypointBuilder(self.flight, self.coalition)
builder = WaypointBuilder(self.flight)
is_helo = self.flight.unit_type.dcs_unit_type.helicopter
ingress_egress_altitude = (
self.doctrine.ingress_altitude if not is_helo else meters(50)
self.doctrine.ingress_altitude
if not is_helo
else feet(self.coalition.game.settings.heli_combat_alt_agl)
)
use_agl_ingress_egress = is_helo

View File

@ -69,7 +69,7 @@ class Builder(IBuilder[CustomFlightPlan, CustomLayout]):
self.waypoints = waypoints
def layout(self) -> CustomLayout:
builder = WaypointBuilder(self.flight, self.coalition)
builder = WaypointBuilder(self.flight)
return CustomLayout(builder.takeoff(self.flight.departure), self.waypoints)
def build(self) -> CustomFlightPlan:

View File

@ -39,7 +39,7 @@ class Builder(FormationAttackBuilder[EscortFlightPlan, FormationAttackLayout]):
def layout(self) -> FormationAttackLayout:
assert self.package.waypoints is not None
builder = WaypointBuilder(self.flight, self.coalition)
builder = WaypointBuilder(self.flight)
ingress, target = builder.escort(
self.package.waypoints.ingress, self.package.target
)
@ -58,11 +58,11 @@ class Builder(FormationAttackBuilder[EscortFlightPlan, FormationAttackLayout]):
split = builder.split(self.package.waypoints.split)
ingress_alt = self.doctrine.ingress_altitude
is_helo = builder.flight.is_helo
heli_alt = feet(self.coalition.game.settings.heli_combat_alt_agl)
initial = builder.escort_hold(
target.position
if builder.flight.is_helo
else self.package.waypoints.initial,
min(feet(500), ingress_alt) if builder.flight.is_helo else ingress_alt,
target.position if is_helo else self.package.waypoints.initial,
min(heli_alt, ingress_alt) if is_helo else ingress_alt,
)
pf = self.package.primary_flight

View File

@ -56,14 +56,14 @@ class Builder(IBuilder[FerryFlightPlan, FerryLayout]):
f"{self.flight.departure}"
)
altitude_is_agl = self.flight.unit_type.dcs_unit_type.helicopter
altitude_is_agl = self.flight.is_helo
altitude = (
feet(1500)
feet(self.coalition.game.settings.heli_cruise_alt_agl)
if altitude_is_agl
else self.flight.unit_type.preferred_patrol_altitude
)
builder = WaypointBuilder(self.flight, self.coalition)
builder = WaypointBuilder(self.flight)
return FerryLayout(
departure=builder.takeoff(self.flight.departure),
nav_to=builder.nav_path(

View File

@ -10,7 +10,7 @@ from dcs import Point
from game.flightplan import HoldZoneGeometry
from game.theater import MissionTarget
from game.utils import Speed, meters, nautical_miles
from game.utils import Speed, meters, nautical_miles, feet
from .flightplan import FlightPlan
from .formation import FormationFlightPlan, FormationLayout
from .ibuilder import IBuilder
@ -170,7 +170,7 @@ class FormationAttackBuilder(IBuilder[FlightPlanT, LayoutT], ABC):
targets: list[StrikeTarget] | None = None,
) -> FormationAttackLayout:
assert self.package.waypoints is not None
builder = WaypointBuilder(self.flight, self.coalition, targets)
builder = WaypointBuilder(self.flight, targets)
target_waypoints: list[FlightWaypoint] = []
if targets is not None:
@ -209,13 +209,22 @@ class FormationAttackBuilder(IBuilder[FlightPlanT, LayoutT], ABC):
pos = ingress.position.point_from_heading(hdg, nautical_miles(10).meters)
lineup = builder.nav(pos, self.flight.coalition.doctrine.ingress_altitude)
is_helo = self.flight.is_helo
ingress_egress_altitude = (
self.doctrine.ingress_altitude
if not is_helo
else feet(self.coalition.game.settings.heli_combat_alt_agl)
)
use_agl_ingress_egress = is_helo
return FormationAttackLayout(
departure=builder.takeoff(self.flight.departure),
hold=hold,
nav_to=builder.nav_path(
hold.position if hold else self.flight.departure.position,
join.position if join else ingress.position,
self.doctrine.ingress_altitude,
ingress_egress_altitude,
use_agl_ingress_egress,
),
join=join,
lineup=lineup,
@ -227,7 +236,8 @@ class FormationAttackBuilder(IBuilder[FlightPlanT, LayoutT], ABC):
nav_from=builder.nav_path(
refuel.position if refuel else split.position,
self.flight.arrival.position,
self.doctrine.ingress_altitude,
ingress_egress_altitude,
use_agl_ingress_egress,
),
arrival=builder.land(self.flight.arrival),
divert=builder.divert(self.flight.divert),

View File

@ -96,7 +96,7 @@ class Builder(IBuilder[PackageRefuelingFlightPlan, PatrollingLayout]):
home_heading.degrees, racetrack_half_distance
)
builder = WaypointBuilder(self.flight, self.coalition)
builder = WaypointBuilder(self.flight)
tanker_type = self.flight.unit_type
if tanker_type.patrol_altitude is not None:

View File

@ -61,13 +61,13 @@ class Builder(IBuilder[RtbFlightPlan, RtbLayout]):
current_position = self.flight.state.estimate_position()
current_altitude, altitude_reference = self.flight.state.estimate_altitude()
altitude_is_agl = self.flight.unit_type.dcs_unit_type.helicopter
altitude_is_agl = self.flight.is_helo
altitude = (
feet(1500)
feet(self.coalition.game.settings.heli_cruise_alt_agl)
if altitude_is_agl
else self.flight.unit_type.preferred_patrol_altitude
)
builder = WaypointBuilder(self.flight, self.flight.coalition)
builder = WaypointBuilder(self.flight)
abort_point = builder.nav(
current_position, current_altitude, altitude_reference == "RADIO"
)

View File

@ -101,7 +101,7 @@ class Builder(IBuilder[SweepFlightPlan, SweepLayout]):
heading.degrees, -self.doctrine.sweep_distance.meters
)
builder = WaypointBuilder(self.flight, self.coalition)
builder = WaypointBuilder(self.flight)
start, end = builder.sweep(start_pos, target, self.doctrine.ingress_altitude)
hold = builder.hold(self._hold_point())

View File

@ -102,7 +102,7 @@ class Builder(CapBuilder[TarCapFlightPlan, TarCapLayout]):
min(self.doctrine.max_patrol_altitude, randomized_alt),
)
builder = WaypointBuilder(self.flight, self.coalition)
builder = WaypointBuilder(self.flight)
orbit0p, orbit1p = self.cap_racetrack_for_objective(location, barcap=False)
start, end = builder.race_track(orbit0p, orbit1p, patrol_alt)

View File

@ -37,7 +37,7 @@ class Builder(IBuilder[TheaterRefuelingFlightPlan, PatrollingLayout]):
# Station 70nm outside the threat zone.
threat_buffer = nautical_miles(
self.flight.coalition.game.settings.tanker_threat_buffer_min_distance
self.coalition.game.settings.tanker_threat_buffer_min_distance
)
if self.threat_zones.threatened(location.position):
orbit_distance = distance_to_threat + threat_buffer
@ -56,7 +56,7 @@ class Builder(IBuilder[TheaterRefuelingFlightPlan, PatrollingLayout]):
orbit_heading.left.degrees, racetrack_half_distance
)
builder = WaypointBuilder(self.flight, self.coalition)
builder = WaypointBuilder(self.flight)
tanker_type = self.flight.unit_type
if tanker_type.patrol_altitude is not None:

View File

@ -29,7 +29,6 @@ from game.theater.interfaces.CTLD import CTLD
from game.utils import Distance, meters, nautical_miles, feet
if TYPE_CHECKING:
from game.coalition import Coalition
from game.transfers import MultiGroupTransport
from game.theater.theatergroup import TheaterGroup
from game.ato.flight import Flight
@ -45,9 +44,9 @@ class WaypointBuilder:
def __init__(
self,
flight: Flight,
coalition: Coalition,
targets: Optional[List[StrikeTarget]] = None,
) -> None:
coalition = flight.coalition
self.flight = flight
self.doctrine = coalition.doctrine
self.threat_zones = coalition.opponent.threat_zone
@ -75,7 +74,9 @@ class WaypointBuilder:
"NAV",
FlightWaypointType.NAV,
position,
meters(500) if self.is_helo else self.doctrine.rendezvous_altitude,
feet(self.flight.coalition.game.settings.heli_cruise_alt_agl)
if self.is_helo
else self.doctrine.rendezvous_altitude,
description="Enter theater",
pretty_name="Enter theater",
)
@ -102,7 +103,9 @@ class WaypointBuilder:
"NAV",
FlightWaypointType.NAV,
position,
meters(500) if self.is_helo else self.doctrine.rendezvous_altitude,
feet(self.flight.coalition.game.settings.heli_cruise_alt_agl)
if self.is_helo
else self.doctrine.rendezvous_altitude,
description="Exit theater",
pretty_name="Exit theater",
)
@ -131,7 +134,9 @@ class WaypointBuilder:
altitude_type: AltitudeReference
if isinstance(divert, OffMapSpawn):
altitude = (
meters(500) if self.is_helo else self.doctrine.rendezvous_altitude
feet(self.flight.coalition.game.settings.heli_cruise_alt_agl)
if self.is_helo
else self.doctrine.rendezvous_altitude
)
altitude_type = "BARO"
else:
@ -170,7 +175,9 @@ class WaypointBuilder:
"HOLD",
FlightWaypointType.LOITER,
position,
feet(1000) if self.is_helo else self.doctrine.rendezvous_altitude,
feet(self.flight.coalition.game.settings.heli_cruise_alt_agl)
if self.is_helo
else self.doctrine.ingress_altitude,
alt_type,
description="Wait until push time",
pretty_name="Hold",
@ -185,7 +192,9 @@ class WaypointBuilder:
"JOIN",
FlightWaypointType.JOIN,
position,
meters(80) if self.is_helo else self.doctrine.ingress_altitude,
feet(self.flight.coalition.game.settings.heli_cruise_alt_agl)
if self.is_helo
else self.doctrine.ingress_altitude,
alt_type,
description="Rendezvous with package",
pretty_name="Join",
@ -200,7 +209,9 @@ class WaypointBuilder:
"REFUEL",
FlightWaypointType.REFUEL,
position,
meters(80) if self.is_helo else self.doctrine.ingress_altitude,
feet(self.flight.coalition.game.settings.heli_cruise_alt_agl)
if self.is_helo
else self.doctrine.ingress_altitude,
alt_type,
description="Refuel from tanker",
pretty_name="Refuel",
@ -215,7 +226,9 @@ class WaypointBuilder:
"SPLIT",
FlightWaypointType.SPLIT,
position,
meters(80) if self.is_helo else self.doctrine.ingress_altitude,
feet(self.flight.coalition.game.settings.heli_combat_alt_agl)
if self.is_helo
else self.doctrine.ingress_altitude,
alt_type,
description="Depart from package",
pretty_name="Split",
@ -231,7 +244,11 @@ class WaypointBuilder:
alt_type: AltitudeReference = "BARO"
if self.is_helo or self.flight.is_hercules:
alt_type = "RADIO"
alt = meters(60) if self.is_helo else feet(1000)
alt = (
feet(self.flight.coalition.game.settings.heli_combat_alt_agl)
if self.is_helo
else feet(1000)
)
return FlightWaypoint(
"INGRESS",
@ -253,7 +270,9 @@ class WaypointBuilder:
"EGRESS",
FlightWaypointType.EGRESS,
position,
meters(60) if self.is_helo else self.doctrine.ingress_altitude,
feet(self.flight.coalition.game.settings.heli_combat_alt_agl)
if self.is_helo
else self.doctrine.ingress_altitude,
alt_type,
description=f"EGRESS from {target.name}",
pretty_name=f"EGRESS from {target.name}",
@ -350,7 +369,9 @@ class WaypointBuilder:
"CAS",
FlightWaypointType.CAS,
position,
meters(60) if self.is_helo else meters(1000),
feet(self.flight.coalition.game.settings.heli_combat_alt_agl)
if self.is_helo
else meters(1000),
"RADIO",
description="Provide CAS",
pretty_name="CAS",
@ -430,7 +451,9 @@ class WaypointBuilder:
"SEAD Search",
FlightWaypointType.NAV,
hold,
self.doctrine.ingress_altitude,
feet(self.flight.coalition.game.settings.heli_combat_alt_agl)
if self.is_helo
else self.doctrine.ingress_altitude,
alt_type="BARO",
description="Anchor and search from this point",
pretty_name="SEAD Search",
@ -443,7 +466,9 @@ class WaypointBuilder:
"SEAD Sweep",
FlightWaypointType.NAV,
hold,
self.doctrine.ingress_altitude,
feet(self.flight.coalition.game.settings.heli_combat_alt_agl)
if self.is_helo
else self.doctrine.ingress_altitude,
alt_type="BARO",
description="Anchor and search from this point",
pretty_name="SEAD Sweep",
@ -565,7 +590,9 @@ class WaypointBuilder:
"TARGET",
FlightWaypointType.TARGET_GROUP_LOC,
target.position,
meters(60) if self.is_helo else self.doctrine.ingress_altitude,
feet(self.flight.coalition.game.settings.heli_combat_alt_agl)
if self.is_helo
else self.doctrine.ingress_altitude,
alt_type,
description="Escort the package",
pretty_name="Target area",

View File

@ -255,6 +255,33 @@ class Settings:
"within threatened airspace."
),
)
heli_combat_alt_agl: int = bounded_int_option(
"Helicopter combat altitude (feet AGL)",
page=CAMPAIGN_DOCTRINE_PAGE,
section=GENERAL_SECTION,
default=200,
min=1,
max=10000,
detail=(
"Altitude for helicopters in feet AGL while flying between combat waypoints."
" Combat waypoints are considered INGRESS, CAS, TGT, EGRESS & SPLIT."
" In campaigns in more mountainous areas, you might want to increase this "
"setting to avoid the AI flying into the terrain."
),
)
heli_cruise_alt_agl: int = bounded_int_option(
"Helicopter cruise altitude (feet AGL)",
page=CAMPAIGN_DOCTRINE_PAGE,
section=GENERAL_SECTION,
default=500,
min=1,
max=10000,
detail=(
"Altitude for helicopters in feet AGL while flying between non-combat waypoints."
" In campaigns in more mountainous areas, you might want to increase this "
"setting to avoid the AI flying into the terrain."
),
)
airbase_threat_range: int = bounded_int_option(
"Airbase threat range (nmi)",
page=CAMPAIGN_DOCTRINE_PAGE,

View File

@ -84,7 +84,9 @@ class PilotSelector(QComboBox):
self.roster.set_pilot(self.pilot_index, pilot)
self.available_pilots_changed.emit()
def replace(self, squadron: Optional[Squadron], new_roster: Optional[FlightRoster]) -> None:
def replace(
self, squadron: Optional[Squadron], new_roster: Optional[FlightRoster]
) -> None:
self.squadron = squadron
self.roster = new_roster
self.rebuild()
@ -159,7 +161,9 @@ class PilotControls(QHBoxLayout):
finally:
self.player_checkbox.blockSignals(False)
def replace(self, squadron: Optional[Squadron], new_roster: Optional[FlightRoster]) -> None:
def replace(
self, squadron: Optional[Squadron], new_roster: Optional[FlightRoster]
) -> None:
self.roster = new_roster
if self.roster is None or self.pilot_index >= self.roster.max_size:
self.disable_and_clear()

View File

@ -194,7 +194,7 @@ class QFlightWaypointTab(QFrame):
self.on_change()
def on_rtb_waypoint(self):
rtb = WaypointBuilder(self.flight, self.coalition).land(self.flight.arrival)
rtb = WaypointBuilder(self.flight).land(self.flight.arrival)
self.degrade_to_custom_flight_plan()
assert isinstance(self.flight.flight_plan, CustomFlightPlan)
self.flight.flight_plan.layout.custom_waypoints.append(rtb)