mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Configurable cruise & combat altitude + randomized offsets (phase 1)
This commit is contained in:
parent
7158a5e60d
commit
9303e1cb9e
@ -44,6 +44,8 @@
|
|||||||
* **[Options]** Add option (so it can be disabled when fixed in DCS) to force air-starts (except for the slots that work) at Nevatim due to https://forum.dcs.world/topic/335545-29-nevatim-ramp-starts-still-bugged/
|
* **[Options]** Add option (so it can be disabled when fixed in DCS) to force air-starts (except for the slots that work) at Nevatim due to https://forum.dcs.world/topic/335545-29-nevatim-ramp-starts-still-bugged/
|
||||||
* **[Cheat]** Add cheat option to manually manage REDFOR's TGOs
|
* **[Cheat]** Add cheat option to manually manage REDFOR's TGOs
|
||||||
* **[UX]** Buy/Replace TGOs for free before the campaign has started
|
* **[UX]** Buy/Replace TGOs for free before the campaign has started
|
||||||
|
* **[Data]** Ability to define "cruise" & "combat" altitudes for airplanes
|
||||||
|
* **[Options]** Option to randomize altitudes for flights with airplanes
|
||||||
|
|
||||||
## Fixes
|
## Fixes
|
||||||
* **[Mission Generation]** Anti-ship strikes should use "group attack" in their attack-task
|
* **[Mission Generation]** Anti-ship strikes should use "group attack" in their attack-task
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import random
|
||||||
import uuid
|
import uuid
|
||||||
from collections.abc import Iterator
|
from collections.abc import Iterator
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
@ -123,6 +124,11 @@ class Flight(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# altitude offset for planes
|
||||||
|
offset_factor = self.coalition.game.settings.max_plane_altitude_offset
|
||||||
|
offset_factor = random.randint(0, offset_factor)
|
||||||
|
self.plane_altitude_offset = 1000 * offset_factor * random.choice([-1, 1])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available_callsigns(self) -> List[str]:
|
def available_callsigns(self) -> List[str]:
|
||||||
callsigns = set()
|
callsigns = set()
|
||||||
|
|||||||
@ -6,7 +6,7 @@ from typing import Type
|
|||||||
from game.ato.flightplans.ibuilder import IBuilder
|
from game.ato.flightplans.ibuilder import IBuilder
|
||||||
from game.ato.flightplans.patrolling import PatrollingFlightPlan, PatrollingLayout
|
from game.ato.flightplans.patrolling import PatrollingFlightPlan, PatrollingLayout
|
||||||
from game.ato.flightplans.waypointbuilder import WaypointBuilder
|
from game.ato.flightplans.waypointbuilder import WaypointBuilder
|
||||||
from game.utils import Distance, Heading, Speed, feet, knots, meters, nautical_miles
|
from game.utils import Distance, Heading, Speed, knots, meters, nautical_miles
|
||||||
|
|
||||||
|
|
||||||
class AewcFlightPlan(PatrollingFlightPlan[PatrollingLayout]):
|
class AewcFlightPlan(PatrollingFlightPlan[PatrollingLayout]):
|
||||||
@ -70,10 +70,7 @@ class Builder(IBuilder[AewcFlightPlan, PatrollingLayout]):
|
|||||||
|
|
||||||
builder = WaypointBuilder(self.flight)
|
builder = WaypointBuilder(self.flight)
|
||||||
|
|
||||||
if self.flight.unit_type.patrol_altitude is not None:
|
altitude = builder.get_patrol_altitude
|
||||||
altitude = self.flight.unit_type.patrol_altitude
|
|
||||||
else:
|
|
||||||
altitude = feet(25000)
|
|
||||||
|
|
||||||
racetrack = builder.race_track(racetrack_start, racetrack_end, altitude)
|
racetrack = builder.race_track(racetrack_start, racetrack_end, altitude)
|
||||||
|
|
||||||
|
|||||||
@ -112,12 +112,11 @@ class Builder(FormationAttackBuilder[AirAssaultFlightPlan, AirAssaultLayout]):
|
|||||||
)
|
)
|
||||||
assert self.package.waypoints is not None
|
assert self.package.waypoints is not None
|
||||||
|
|
||||||
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)
|
builder = WaypointBuilder(self.flight)
|
||||||
|
|
||||||
|
altitude = builder.get_cruise_altitude
|
||||||
|
altitude_is_agl = self.flight.is_helo
|
||||||
|
|
||||||
if self.flight.is_hercules or self.flight.departure.cptype in [
|
if self.flight.is_hercules or self.flight.departure.cptype in [
|
||||||
ControlPointType.AIRCRAFT_CARRIER_GROUP,
|
ControlPointType.AIRCRAFT_CARRIER_GROUP,
|
||||||
ControlPointType.LHA_GROUP,
|
ControlPointType.LHA_GROUP,
|
||||||
@ -134,7 +133,7 @@ class Builder(FormationAttackBuilder[AirAssaultFlightPlan, AirAssaultLayout]):
|
|||||||
self._generate_ctld_pickup(),
|
self._generate_ctld_pickup(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
pickup.alt = heli_alt
|
pickup.alt = altitude
|
||||||
pickup_position = pickup.position
|
pickup_position = pickup.position
|
||||||
|
|
||||||
ingress = builder.ingress(
|
ingress = builder.ingress(
|
||||||
@ -160,8 +159,6 @@ class Builder(FormationAttackBuilder[AirAssaultFlightPlan, AirAssaultLayout]):
|
|||||||
drop_pos = tgt.position.point_from_heading(heading, 1200)
|
drop_pos = tgt.position.point_from_heading(heading, 1200)
|
||||||
drop_off_zone = MissionTarget("Dropoff zone", drop_pos)
|
drop_off_zone = MissionTarget("Dropoff zone", drop_pos)
|
||||||
dz = builder.dropoff_zone(drop_off_zone) if self.flight.is_helo else None
|
dz = builder.dropoff_zone(drop_off_zone) if self.flight.is_helo else None
|
||||||
if dz:
|
|
||||||
dz.alt = heli_alt
|
|
||||||
|
|
||||||
return AirAssaultLayout(
|
return AirAssaultLayout(
|
||||||
departure=builder.takeoff(self.flight.departure),
|
departure=builder.takeoff(self.flight.departure),
|
||||||
|
|||||||
@ -8,7 +8,7 @@ from typing import Optional
|
|||||||
from typing import TYPE_CHECKING, Type
|
from typing import TYPE_CHECKING, Type
|
||||||
|
|
||||||
from game.theater.missiontarget import MissionTarget
|
from game.theater.missiontarget import MissionTarget
|
||||||
from game.utils import feet, Distance
|
from game.utils import Distance
|
||||||
from ._common_ctld import generate_random_ctld_point
|
from ._common_ctld import generate_random_ctld_point
|
||||||
from .ibuilder import IBuilder
|
from .ibuilder import IBuilder
|
||||||
from .planningerror import PlanningError
|
from .planningerror import PlanningError
|
||||||
@ -133,12 +133,11 @@ class Builder(IBuilder[AirliftFlightPlan, AirliftLayout]):
|
|||||||
"Cannot plan transport mission for flight with no cargo."
|
"Cannot plan transport mission for flight with no cargo."
|
||||||
)
|
)
|
||||||
|
|
||||||
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)
|
builder = WaypointBuilder(self.flight)
|
||||||
|
|
||||||
|
altitude = builder.get_cruise_altitude
|
||||||
|
altitude_is_agl = self.flight.is_helo
|
||||||
|
|
||||||
pickup_ascent = None
|
pickup_ascent = None
|
||||||
pickup_descent = None
|
pickup_descent = None
|
||||||
pickup = None
|
pickup = None
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import random
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
from game.theater import FrontLine
|
from game.theater import FrontLine
|
||||||
from game.utils import Distance, Speed, feet
|
from game.utils import Distance, Speed
|
||||||
from .capbuilder import CapBuilder
|
from .capbuilder import CapBuilder
|
||||||
from .invalidobjectivelocation import InvalidObjectiveLocation
|
from .invalidobjectivelocation import InvalidObjectiveLocation
|
||||||
from .patrolling import PatrollingFlightPlan, PatrollingLayout
|
from .patrolling import PatrollingFlightPlan, PatrollingLayout
|
||||||
@ -41,14 +40,9 @@ class Builder(CapBuilder[BarCapFlightPlan, PatrollingLayout]):
|
|||||||
|
|
||||||
start_pos, end_pos = self.cap_racetrack_for_objective(location, barcap=True)
|
start_pos, end_pos = self.cap_racetrack_for_objective(location, barcap=True)
|
||||||
|
|
||||||
preferred_alt = self.flight.unit_type.preferred_patrol_altitude
|
|
||||||
randomized_alt = preferred_alt + feet(random.randint(-2, 1) * 1000)
|
|
||||||
patrol_alt = max(
|
|
||||||
self.doctrine.min_patrol_altitude,
|
|
||||||
min(self.doctrine.max_patrol_altitude, randomized_alt),
|
|
||||||
)
|
|
||||||
|
|
||||||
builder = WaypointBuilder(self.flight)
|
builder = WaypointBuilder(self.flight)
|
||||||
|
patrol_alt = builder.get_patrol_altitude
|
||||||
|
|
||||||
start, end = builder.race_track(start_pos, end_pos, patrol_alt)
|
start, end = builder.race_track(start_pos, end_pos, patrol_alt)
|
||||||
|
|
||||||
return PatrollingLayout(
|
return PatrollingLayout(
|
||||||
|
|||||||
@ -7,7 +7,7 @@ from typing import TYPE_CHECKING, Type
|
|||||||
|
|
||||||
from game.theater import FrontLine
|
from game.theater import FrontLine
|
||||||
from game.utils import Distance, Speed, kph, dcs_to_shapely_point
|
from game.utils import Distance, Speed, kph, dcs_to_shapely_point
|
||||||
from game.utils import feet, nautical_miles
|
from game.utils import nautical_miles
|
||||||
from .ibuilder import IBuilder
|
from .ibuilder import IBuilder
|
||||||
from .invalidobjectivelocation import InvalidObjectiveLocation
|
from .invalidobjectivelocation import InvalidObjectiveLocation
|
||||||
from .patrolling import PatrollingFlightPlan, PatrollingLayout
|
from .patrolling import PatrollingFlightPlan, PatrollingLayout
|
||||||
@ -105,11 +105,7 @@ class Builder(IBuilder[CasFlightPlan, CasLayout]):
|
|||||||
builder = WaypointBuilder(self.flight)
|
builder = WaypointBuilder(self.flight)
|
||||||
|
|
||||||
is_helo = self.flight.unit_type.dcs_unit_type.helicopter
|
is_helo = self.flight.unit_type.dcs_unit_type.helicopter
|
||||||
ingress_egress_altitude = (
|
ingress_egress_altitude = builder.get_combat_altitude
|
||||||
self.doctrine.ingress_altitude
|
|
||||||
if not is_helo
|
|
||||||
else feet(self.coalition.game.settings.heli_combat_alt_agl)
|
|
||||||
)
|
|
||||||
use_agl_patrol_altitude = is_helo
|
use_agl_patrol_altitude = is_helo
|
||||||
|
|
||||||
ip_solver = IpSolver(
|
ip_solver = IpSolver(
|
||||||
|
|||||||
@ -11,7 +11,6 @@ from .formationattack import (
|
|||||||
)
|
)
|
||||||
from .waypointbuilder import WaypointBuilder
|
from .waypointbuilder import WaypointBuilder
|
||||||
from .. import FlightType
|
from .. import FlightType
|
||||||
from ...utils import feet
|
|
||||||
|
|
||||||
|
|
||||||
class EscortFlightPlan(FormationAttackFlightPlan):
|
class EscortFlightPlan(FormationAttackFlightPlan):
|
||||||
@ -43,12 +42,9 @@ class Builder(FormationAttackBuilder[EscortFlightPlan, FormationAttackLayout]):
|
|||||||
|
|
||||||
split = builder.split(self._get_split())
|
split = builder.split(self._get_split())
|
||||||
|
|
||||||
ingress_alt = self.doctrine.ingress_altitude
|
|
||||||
is_helo = builder.flight.is_helo
|
is_helo = builder.flight.is_helo
|
||||||
heli_alt = feet(self.coalition.game.settings.heli_combat_alt_agl)
|
|
||||||
initial = builder.escort_hold(
|
initial = builder.escort_hold(
|
||||||
target.position if is_helo else self.package.waypoints.initial,
|
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
|
pf = self.package.primary_flight
|
||||||
@ -69,9 +65,6 @@ class Builder(FormationAttackBuilder[EscortFlightPlan, FormationAttackLayout]):
|
|||||||
if layout.drop_off:
|
if layout.drop_off:
|
||||||
initial = builder.escort_hold(
|
initial = builder.escort_hold(
|
||||||
layout.drop_off.position,
|
layout.drop_off.position,
|
||||||
min(feet(200), ingress_alt)
|
|
||||||
if builder.flight.is_helo
|
|
||||||
else ingress_alt,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
refuel = self._build_refuel(builder)
|
refuel = self._build_refuel(builder)
|
||||||
@ -80,13 +73,13 @@ class Builder(FormationAttackBuilder[EscortFlightPlan, FormationAttackLayout]):
|
|||||||
nav_to = builder.nav_path(
|
nav_to = builder.nav_path(
|
||||||
hold.position if hold else departure.position,
|
hold.position if hold else departure.position,
|
||||||
join.position,
|
join.position,
|
||||||
self.doctrine.ingress_altitude,
|
builder.get_cruise_altitude,
|
||||||
)
|
)
|
||||||
|
|
||||||
nav_from = builder.nav_path(
|
nav_from = builder.nav_path(
|
||||||
refuel.position if refuel else split.position,
|
refuel.position if refuel else split.position,
|
||||||
self.flight.arrival.position,
|
self.flight.arrival.position,
|
||||||
self.doctrine.ingress_altitude,
|
builder.get_cruise_altitude,
|
||||||
)
|
)
|
||||||
|
|
||||||
return FormationAttackLayout(
|
return FormationAttackLayout(
|
||||||
|
|||||||
@ -61,14 +61,14 @@ class Builder(IBuilder[FerryFlightPlan, FerryLayout]):
|
|||||||
f"{self.flight.departure}"
|
f"{self.flight.departure}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
builder = WaypointBuilder(self.flight)
|
||||||
altitude_is_agl = self.flight.is_helo
|
altitude_is_agl = self.flight.is_helo
|
||||||
altitude = (
|
altitude = (
|
||||||
feet(self.coalition.game.settings.heli_cruise_alt_agl)
|
feet(self.coalition.game.settings.heli_cruise_alt_agl)
|
||||||
if altitude_is_agl
|
if altitude_is_agl
|
||||||
else self.flight.unit_type.preferred_patrol_altitude
|
else builder.get_patrol_altitude
|
||||||
)
|
)
|
||||||
|
|
||||||
builder = WaypointBuilder(self.flight)
|
|
||||||
return FerryLayout(
|
return FerryLayout(
|
||||||
departure=builder.takeoff(self.flight.departure),
|
departure=builder.takeoff(self.flight.departure),
|
||||||
nav_to=builder.nav_path(
|
nav_to=builder.nav_path(
|
||||||
|
|||||||
@ -11,7 +11,7 @@ from dcs import Point
|
|||||||
|
|
||||||
from game.flightplan import HoldZoneGeometry
|
from game.flightplan import HoldZoneGeometry
|
||||||
from game.theater import MissionTarget
|
from game.theater import MissionTarget
|
||||||
from game.utils import Speed, meters, nautical_miles, feet
|
from game.utils import Speed, meters, nautical_miles
|
||||||
from .flightplan import FlightPlan
|
from .flightplan import FlightPlan
|
||||||
from .formation import FormationFlightPlan, FormationLayout
|
from .formation import FormationFlightPlan, FormationLayout
|
||||||
from .ibuilder import IBuilder
|
from .ibuilder import IBuilder
|
||||||
@ -210,14 +210,10 @@ class FormationAttackBuilder(IBuilder[FlightPlanT, LayoutT], ABC):
|
|||||||
if self.flight.flight_type == FlightType.STRIKE:
|
if self.flight.flight_type == FlightType.STRIKE:
|
||||||
hdg = self.package.target.position.heading_between_point(ingress.position)
|
hdg = self.package.target.position.heading_between_point(ingress.position)
|
||||||
pos = ingress.position.point_from_heading(hdg, nautical_miles(10).meters)
|
pos = ingress.position.point_from_heading(hdg, nautical_miles(10).meters)
|
||||||
lineup = builder.nav(pos, self.flight.coalition.doctrine.ingress_altitude)
|
lineup = builder.nav(pos, builder.get_combat_altitude)
|
||||||
|
|
||||||
is_helo = self.flight.is_helo
|
is_helo = self.flight.is_helo
|
||||||
ingress_egress_altitude = (
|
ingress_egress_altitude = builder.get_combat_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
|
use_agl_ingress_egress = is_helo
|
||||||
|
|
||||||
return FormationAttackLayout(
|
return FormationAttackLayout(
|
||||||
|
|||||||
@ -25,6 +25,7 @@ class IBuilder(ABC, Generic[FlightPlanT, LayoutT]):
|
|||||||
def __init__(self, flight: Flight) -> None:
|
def __init__(self, flight: Flight) -> None:
|
||||||
self.flight = flight
|
self.flight = flight
|
||||||
self._flight_plan: FlightPlanT | None = None
|
self._flight_plan: FlightPlanT | None = None
|
||||||
|
self.settings = self.flight.coalition.game.settings
|
||||||
|
|
||||||
def get_or_build(self) -> FlightPlanT:
|
def get_or_build(self) -> FlightPlanT:
|
||||||
if self._flight_plan is None:
|
if self._flight_plan is None:
|
||||||
|
|||||||
@ -5,7 +5,7 @@ from typing import Type
|
|||||||
|
|
||||||
from dcs import Point
|
from dcs import Point
|
||||||
|
|
||||||
from game.utils import Distance, Heading, feet, meters
|
from game.utils import Distance, Heading, meters
|
||||||
from .ibuilder import IBuilder
|
from .ibuilder import IBuilder
|
||||||
from .patrolling import PatrollingLayout
|
from .patrolling import PatrollingLayout
|
||||||
from .refuelingflightplan import RefuelingFlightPlan
|
from .refuelingflightplan import RefuelingFlightPlan
|
||||||
@ -98,11 +98,7 @@ class Builder(IBuilder[PackageRefuelingFlightPlan, PatrollingLayout]):
|
|||||||
|
|
||||||
builder = WaypointBuilder(self.flight)
|
builder = WaypointBuilder(self.flight)
|
||||||
|
|
||||||
tanker_type = self.flight.unit_type
|
altitude = builder.get_patrol_altitude
|
||||||
if tanker_type.patrol_altitude is not None:
|
|
||||||
altitude = tanker_type.patrol_altitude
|
|
||||||
else:
|
|
||||||
altitude = feet(21000)
|
|
||||||
|
|
||||||
racetrack = builder.race_track(racetrack_start, racetrack_end, altitude)
|
racetrack = builder.race_track(racetrack_start, racetrack_end, altitude)
|
||||||
|
|
||||||
|
|||||||
@ -66,13 +66,13 @@ class Builder(IBuilder[RtbFlightPlan, RtbLayout]):
|
|||||||
current_position = self.flight.state.estimate_position()
|
current_position = self.flight.state.estimate_position()
|
||||||
current_altitude, altitude_reference = self.flight.state.estimate_altitude()
|
current_altitude, altitude_reference = self.flight.state.estimate_altitude()
|
||||||
|
|
||||||
|
builder = WaypointBuilder(self.flight)
|
||||||
altitude_is_agl = self.flight.is_helo
|
altitude_is_agl = self.flight.is_helo
|
||||||
altitude = (
|
altitude = (
|
||||||
feet(self.coalition.game.settings.heli_cruise_alt_agl)
|
feet(self.coalition.game.settings.heli_cruise_alt_agl)
|
||||||
if altitude_is_agl
|
if altitude_is_agl
|
||||||
else self.flight.unit_type.preferred_patrol_altitude
|
else builder.get_patrol_altitude
|
||||||
)
|
)
|
||||||
builder = WaypointBuilder(self.flight)
|
|
||||||
abort_point = builder.nav(
|
abort_point = builder.nav(
|
||||||
current_position, current_altitude, altitude_reference == "RADIO"
|
current_position, current_altitude, altitude_reference == "RADIO"
|
||||||
)
|
)
|
||||||
|
|||||||
@ -105,20 +105,20 @@ class Builder(IBuilder[SweepFlightPlan, SweepLayout]):
|
|||||||
)
|
)
|
||||||
|
|
||||||
builder = WaypointBuilder(self.flight)
|
builder = WaypointBuilder(self.flight)
|
||||||
start, end = builder.sweep(start_pos, target, self.doctrine.ingress_altitude)
|
altitude = builder.get_patrol_altitude
|
||||||
|
|
||||||
|
start, end = builder.sweep(start_pos, target, altitude)
|
||||||
|
|
||||||
hold = builder.hold(self._hold_point())
|
hold = builder.hold(self._hold_point())
|
||||||
|
|
||||||
return SweepLayout(
|
return SweepLayout(
|
||||||
departure=builder.takeoff(self.flight.departure),
|
departure=builder.takeoff(self.flight.departure),
|
||||||
hold=hold,
|
hold=hold,
|
||||||
nav_to=builder.nav_path(
|
nav_to=builder.nav_path(hold.position, start.position, altitude),
|
||||||
hold.position, start.position, self.doctrine.ingress_altitude
|
|
||||||
),
|
|
||||||
nav_from=builder.nav_path(
|
nav_from=builder.nav_path(
|
||||||
end.position,
|
end.position,
|
||||||
self.flight.arrival.position,
|
self.flight.arrival.position,
|
||||||
self.doctrine.ingress_altitude,
|
altitude,
|
||||||
),
|
),
|
||||||
sweep_start=start,
|
sweep_start=start,
|
||||||
sweep_end=end,
|
sweep_end=end,
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import random
|
|
||||||
from collections.abc import Iterator
|
from collections.abc import Iterator
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import TYPE_CHECKING, Type
|
from typing import TYPE_CHECKING, Type
|
||||||
|
|
||||||
from game.utils import Distance, Speed, feet
|
from game.utils import Distance, Speed
|
||||||
from .capbuilder import CapBuilder
|
from .capbuilder import CapBuilder
|
||||||
from .patrolling import PatrollingFlightPlan, PatrollingLayout
|
from .patrolling import PatrollingFlightPlan, PatrollingLayout
|
||||||
from .waypointbuilder import WaypointBuilder
|
from .waypointbuilder import WaypointBuilder
|
||||||
@ -96,14 +95,9 @@ class Builder(CapBuilder[TarCapFlightPlan, TarCapLayout]):
|
|||||||
def layout(self) -> TarCapLayout:
|
def layout(self) -> TarCapLayout:
|
||||||
location = self.package.target
|
location = self.package.target
|
||||||
|
|
||||||
preferred_alt = self.flight.unit_type.preferred_patrol_altitude
|
|
||||||
randomized_alt = preferred_alt + feet(random.randint(-2, 1) * 1000)
|
|
||||||
patrol_alt = max(
|
|
||||||
self.doctrine.min_patrol_altitude,
|
|
||||||
min(self.doctrine.max_patrol_altitude, randomized_alt),
|
|
||||||
)
|
|
||||||
|
|
||||||
builder = WaypointBuilder(self.flight)
|
builder = WaypointBuilder(self.flight)
|
||||||
|
patrol_alt = builder.get_patrol_altitude
|
||||||
|
|
||||||
orbit0p, orbit1p = self.cap_racetrack_for_objective(location, barcap=False)
|
orbit0p, orbit1p = self.cap_racetrack_for_objective(location, barcap=False)
|
||||||
|
|
||||||
start, end = builder.race_track(orbit0p, orbit1p, patrol_alt)
|
start, end = builder.race_track(orbit0p, orbit1p, patrol_alt)
|
||||||
|
|||||||
@ -3,7 +3,7 @@ from __future__ import annotations
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
from game.utils import Heading, feet, meters, nautical_miles
|
from game.utils import Heading, meters, nautical_miles
|
||||||
from .ibuilder import IBuilder
|
from .ibuilder import IBuilder
|
||||||
from .patrolling import PatrollingLayout
|
from .patrolling import PatrollingLayout
|
||||||
from .refuelingflightplan import RefuelingFlightPlan
|
from .refuelingflightplan import RefuelingFlightPlan
|
||||||
@ -58,11 +58,7 @@ class Builder(IBuilder[TheaterRefuelingFlightPlan, PatrollingLayout]):
|
|||||||
|
|
||||||
builder = WaypointBuilder(self.flight)
|
builder = WaypointBuilder(self.flight)
|
||||||
|
|
||||||
tanker_type = self.flight.unit_type
|
altitude = builder.get_patrol_altitude
|
||||||
if tanker_type.patrol_altitude is not None:
|
|
||||||
altitude = tanker_type.patrol_altitude
|
|
||||||
else:
|
|
||||||
altitude = feet(21000)
|
|
||||||
|
|
||||||
racetrack = builder.race_track(racetrack_start, racetrack_end, altitude)
|
racetrack = builder.race_track(racetrack_start, racetrack_end, altitude)
|
||||||
|
|
||||||
|
|||||||
@ -51,11 +51,34 @@ class WaypointBuilder:
|
|||||||
self.navmesh = coalition.nav_mesh
|
self.navmesh = coalition.nav_mesh
|
||||||
self.targets = targets
|
self.targets = targets
|
||||||
self._bullseye = coalition.bullseye
|
self._bullseye = coalition.bullseye
|
||||||
|
self.settings = self.flight.coalition.game.settings
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_helo(self) -> bool:
|
def is_helo(self) -> bool:
|
||||||
return self.flight.is_helo
|
return self.flight.is_helo
|
||||||
|
|
||||||
|
@property
|
||||||
|
def get_patrol_altitude(self) -> Distance:
|
||||||
|
return self.get_altitude(self.flight.unit_type.preferred_patrol_altitude)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def get_cruise_altitude(self) -> Distance:
|
||||||
|
return self.get_altitude(self.flight.unit_type.preferred_cruise_altitude)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def get_combat_altitude(self) -> Distance:
|
||||||
|
return self.get_altitude(self.flight.unit_type.preferred_combat_altitude)
|
||||||
|
|
||||||
|
def get_altitude(self, alt: Distance) -> Distance:
|
||||||
|
randomized_alt = feet(round(alt.feet + self.flight.plane_altitude_offset))
|
||||||
|
altitude = max(
|
||||||
|
self.doctrine.min_combat_altitude,
|
||||||
|
min(self.doctrine.max_combat_altitude, randomized_alt),
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
feet(self.settings.heli_combat_alt_agl) if self.flight.is_helo else altitude
|
||||||
|
)
|
||||||
|
|
||||||
def takeoff(self, departure: ControlPoint) -> FlightWaypoint:
|
def takeoff(self, departure: ControlPoint) -> FlightWaypoint:
|
||||||
"""Create takeoff waypoint for the given arrival airfield or carrier.
|
"""Create takeoff waypoint for the given arrival airfield or carrier.
|
||||||
|
|
||||||
@ -72,9 +95,7 @@ class WaypointBuilder:
|
|||||||
"NAV",
|
"NAV",
|
||||||
FlightWaypointType.NAV,
|
FlightWaypointType.NAV,
|
||||||
position,
|
position,
|
||||||
feet(self.flight.coalition.game.settings.heli_cruise_alt_agl)
|
self.get_cruise_altitude,
|
||||||
if self.is_helo
|
|
||||||
else self.doctrine.rendezvous_altitude,
|
|
||||||
description="Enter theater",
|
description="Enter theater",
|
||||||
pretty_name="Enter theater",
|
pretty_name="Enter theater",
|
||||||
)
|
)
|
||||||
@ -101,9 +122,7 @@ class WaypointBuilder:
|
|||||||
"NAV",
|
"NAV",
|
||||||
FlightWaypointType.NAV,
|
FlightWaypointType.NAV,
|
||||||
position,
|
position,
|
||||||
feet(self.flight.coalition.game.settings.heli_cruise_alt_agl)
|
self.get_cruise_altitude,
|
||||||
if self.is_helo
|
|
||||||
else self.doctrine.rendezvous_altitude,
|
|
||||||
description="Exit theater",
|
description="Exit theater",
|
||||||
pretty_name="Exit theater",
|
pretty_name="Exit theater",
|
||||||
)
|
)
|
||||||
@ -129,14 +148,10 @@ class WaypointBuilder:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
position = divert.position
|
position = divert.position
|
||||||
altitude_type: AltitudeReference
|
altitude_type: AltitudeReference = "BARO"
|
||||||
if isinstance(divert, OffMapSpawn):
|
if isinstance(divert, OffMapSpawn):
|
||||||
altitude = (
|
altitude = self.get_cruise_altitude
|
||||||
feet(self.flight.coalition.game.settings.heli_cruise_alt_agl)
|
altitude_type = "RADIO" if self.is_helo else altitude_type
|
||||||
if self.is_helo
|
|
||||||
else self.doctrine.rendezvous_altitude
|
|
||||||
)
|
|
||||||
altitude_type = "BARO"
|
|
||||||
else:
|
else:
|
||||||
altitude = meters(0)
|
altitude = meters(0)
|
||||||
altitude_type = "RADIO"
|
altitude_type = "RADIO"
|
||||||
@ -173,9 +188,8 @@ class WaypointBuilder:
|
|||||||
"HOLD",
|
"HOLD",
|
||||||
FlightWaypointType.LOITER,
|
FlightWaypointType.LOITER,
|
||||||
position,
|
position,
|
||||||
feet(self.flight.coalition.game.settings.heli_cruise_alt_agl)
|
# TODO: dedicated altitude setting for holding
|
||||||
if self.is_helo
|
self.get_cruise_altitude if self.is_helo else self.get_combat_altitude,
|
||||||
else self.doctrine.ingress_altitude,
|
|
||||||
alt_type,
|
alt_type,
|
||||||
description="Wait until push time",
|
description="Wait until push time",
|
||||||
pretty_name="Hold",
|
pretty_name="Hold",
|
||||||
@ -190,9 +204,7 @@ class WaypointBuilder:
|
|||||||
"JOIN",
|
"JOIN",
|
||||||
FlightWaypointType.JOIN,
|
FlightWaypointType.JOIN,
|
||||||
position,
|
position,
|
||||||
feet(self.flight.coalition.game.settings.heli_cruise_alt_agl)
|
self.get_cruise_altitude,
|
||||||
if self.is_helo
|
|
||||||
else self.doctrine.ingress_altitude,
|
|
||||||
alt_type,
|
alt_type,
|
||||||
description="Rendezvous with package",
|
description="Rendezvous with package",
|
||||||
pretty_name="Join",
|
pretty_name="Join",
|
||||||
@ -207,9 +219,7 @@ class WaypointBuilder:
|
|||||||
"REFUEL",
|
"REFUEL",
|
||||||
FlightWaypointType.REFUEL,
|
FlightWaypointType.REFUEL,
|
||||||
position,
|
position,
|
||||||
feet(self.flight.coalition.game.settings.heli_cruise_alt_agl)
|
self.get_cruise_altitude,
|
||||||
if self.is_helo
|
|
||||||
else self.doctrine.ingress_altitude,
|
|
||||||
alt_type,
|
alt_type,
|
||||||
description="Refuel from tanker",
|
description="Refuel from tanker",
|
||||||
pretty_name="Refuel",
|
pretty_name="Refuel",
|
||||||
@ -224,9 +234,7 @@ class WaypointBuilder:
|
|||||||
"SPLIT",
|
"SPLIT",
|
||||||
FlightWaypointType.SPLIT,
|
FlightWaypointType.SPLIT,
|
||||||
position,
|
position,
|
||||||
feet(self.flight.coalition.game.settings.heli_combat_alt_agl)
|
self.get_combat_altitude,
|
||||||
if self.is_helo
|
|
||||||
else self.doctrine.ingress_altitude,
|
|
||||||
alt_type,
|
alt_type,
|
||||||
description="Depart from package",
|
description="Depart from package",
|
||||||
pretty_name="Split",
|
pretty_name="Split",
|
||||||
@ -238,7 +246,7 @@ class WaypointBuilder:
|
|||||||
position: Point,
|
position: Point,
|
||||||
objective: MissionTarget,
|
objective: MissionTarget,
|
||||||
) -> FlightWaypoint:
|
) -> FlightWaypoint:
|
||||||
alt = self.doctrine.ingress_altitude
|
alt = self.get_combat_altitude
|
||||||
alt_type: AltitudeReference = "BARO"
|
alt_type: AltitudeReference = "BARO"
|
||||||
if self.is_helo or self.flight.is_hercules:
|
if self.is_helo or self.flight.is_hercules:
|
||||||
alt_type = "RADIO"
|
alt_type = "RADIO"
|
||||||
@ -272,9 +280,7 @@ class WaypointBuilder:
|
|||||||
"EGRESS",
|
"EGRESS",
|
||||||
FlightWaypointType.EGRESS,
|
FlightWaypointType.EGRESS,
|
||||||
position,
|
position,
|
||||||
feet(self.flight.coalition.game.settings.heli_combat_alt_agl)
|
self.get_combat_altitude,
|
||||||
if self.is_helo
|
|
||||||
else self.doctrine.ingress_altitude,
|
|
||||||
alt_type,
|
alt_type,
|
||||||
description=f"EGRESS from {target.name}",
|
description=f"EGRESS from {target.name}",
|
||||||
pretty_name=f"EGRESS from {target.name}",
|
pretty_name=f"EGRESS from {target.name}",
|
||||||
@ -315,7 +321,7 @@ class WaypointBuilder:
|
|||||||
return self._target_area(
|
return self._target_area(
|
||||||
f"SEAD on {target.name}",
|
f"SEAD on {target.name}",
|
||||||
target,
|
target,
|
||||||
altitude=self.doctrine.ingress_altitude,
|
altitude=self.get_combat_altitude,
|
||||||
alt_type="BARO",
|
alt_type="BARO",
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -453,9 +459,7 @@ class WaypointBuilder:
|
|||||||
"SEAD Search",
|
"SEAD Search",
|
||||||
FlightWaypointType.NAV,
|
FlightWaypointType.NAV,
|
||||||
hold,
|
hold,
|
||||||
feet(self.flight.coalition.game.settings.heli_combat_alt_agl)
|
self.get_combat_altitude,
|
||||||
if self.is_helo
|
|
||||||
else self.doctrine.ingress_altitude,
|
|
||||||
alt_type="BARO",
|
alt_type="BARO",
|
||||||
description="Anchor and search from this point",
|
description="Anchor and search from this point",
|
||||||
pretty_name="SEAD Search",
|
pretty_name="SEAD Search",
|
||||||
@ -468,10 +472,8 @@ class WaypointBuilder:
|
|||||||
"SEAD Sweep",
|
"SEAD Sweep",
|
||||||
FlightWaypointType.NAV,
|
FlightWaypointType.NAV,
|
||||||
hold,
|
hold,
|
||||||
feet(self.flight.coalition.game.settings.heli_combat_alt_agl)
|
self.get_combat_altitude,
|
||||||
if self.is_helo
|
alt_type="BARO", # SEAD Sweep shouldn't be used for helicopters
|
||||||
else self.doctrine.ingress_altitude,
|
|
||||||
alt_type="BARO",
|
|
||||||
description="Anchor and search from this point",
|
description="Anchor and search from this point",
|
||||||
pretty_name="SEAD Sweep",
|
pretty_name="SEAD Sweep",
|
||||||
)
|
)
|
||||||
@ -499,13 +501,15 @@ class WaypointBuilder:
|
|||||||
)
|
)
|
||||||
return hold
|
return hold
|
||||||
|
|
||||||
def escort_hold(self, start: Point, altitude: Distance) -> FlightWaypoint:
|
def escort_hold(self, start: Point) -> FlightWaypoint:
|
||||||
"""Creates custom waypoint for escort flights that need to hold.
|
"""Creates custom waypoint for escort flights that need to hold.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
start: Position of the waypoint.
|
start: Position of the waypoint.
|
||||||
altitude: Altitude of the holding pattern.
|
altitude: Altitude of the holding pattern.
|
||||||
"""
|
"""
|
||||||
|
altitude = self.get_combat_altitude
|
||||||
|
|
||||||
alt_type: Literal["BARO", "RADIO"] = "BARO"
|
alt_type: Literal["BARO", "RADIO"] = "BARO"
|
||||||
if self.is_helo:
|
if self.is_helo:
|
||||||
alt_type = "RADIO"
|
alt_type = "RADIO"
|
||||||
@ -592,9 +596,7 @@ class WaypointBuilder:
|
|||||||
"TARGET",
|
"TARGET",
|
||||||
FlightWaypointType.TARGET_GROUP_LOC,
|
FlightWaypointType.TARGET_GROUP_LOC,
|
||||||
target.position,
|
target.position,
|
||||||
feet(self.flight.coalition.game.settings.heli_combat_alt_agl)
|
self.get_combat_altitude,
|
||||||
if self.is_helo
|
|
||||||
else self.doctrine.ingress_altitude,
|
|
||||||
alt_type,
|
alt_type,
|
||||||
description="Escort the package",
|
description="Escort the package",
|
||||||
pretty_name="Target area",
|
pretty_name="Target area",
|
||||||
@ -616,17 +618,19 @@ class WaypointBuilder:
|
|||||||
pretty_name="Pick-up zone",
|
pretty_name="Pick-up zone",
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
def dropoff_zone(self, drop_off: MissionTarget) -> FlightWaypoint:
|
||||||
def dropoff_zone(drop_off: MissionTarget) -> FlightWaypoint:
|
|
||||||
"""Creates a dropoff landing zone waypoint
|
"""Creates a dropoff landing zone waypoint
|
||||||
This waypoint is used to generate the Trigger Zone used for AirAssault and
|
This waypoint is used to generate the Trigger Zone used for AirAssault and
|
||||||
AirLift using the CTLD plugin (see LogisticsGenerator)
|
AirLift using the CTLD plugin (see LogisticsGenerator)
|
||||||
"""
|
"""
|
||||||
|
heli_alt = feet(self.flight.coalition.game.settings.heli_cruise_alt_agl)
|
||||||
|
altitude = heli_alt if self.flight.is_helo else meters(0)
|
||||||
|
|
||||||
return FlightWaypoint(
|
return FlightWaypoint(
|
||||||
"DROPOFFZONE",
|
"DROPOFFZONE",
|
||||||
FlightWaypointType.DROPOFF_ZONE,
|
FlightWaypointType.DROPOFF_ZONE,
|
||||||
drop_off.position,
|
drop_off.position,
|
||||||
meters(0),
|
altitude,
|
||||||
"RADIO",
|
"RADIO",
|
||||||
description=f"Drop off cargo at {drop_off.name}",
|
description=f"Drop off cargo at {drop_off.name}",
|
||||||
pretty_name="Drop-off zone",
|
pretty_name="Drop-off zone",
|
||||||
|
|||||||
@ -26,8 +26,6 @@ class Doctrine:
|
|||||||
strike: bool
|
strike: bool
|
||||||
antiship: bool
|
antiship: bool
|
||||||
|
|
||||||
rendezvous_altitude: Distance
|
|
||||||
|
|
||||||
#: The minimum distance between the departure airfield and the hold point.
|
#: The minimum distance between the departure airfield and the hold point.
|
||||||
hold_distance: Distance
|
hold_distance: Distance
|
||||||
|
|
||||||
@ -46,11 +44,14 @@ class Doctrine:
|
|||||||
#: target.
|
#: target.
|
||||||
min_ingress_distance: Distance
|
min_ingress_distance: Distance
|
||||||
|
|
||||||
ingress_altitude: Distance
|
|
||||||
|
|
||||||
min_patrol_altitude: Distance
|
min_patrol_altitude: Distance
|
||||||
max_patrol_altitude: Distance
|
max_patrol_altitude: Distance
|
||||||
pattern_altitude: Distance
|
|
||||||
|
min_cruise_altitude: Distance
|
||||||
|
max_cruise_altitude: Distance
|
||||||
|
|
||||||
|
min_combat_altitude: Distance
|
||||||
|
max_combat_altitude: Distance
|
||||||
|
|
||||||
#: The duration that CAP flights will remain on-station.
|
#: The duration that CAP flights will remain on-station.
|
||||||
cap_duration: timedelta
|
cap_duration: timedelta
|
||||||
@ -97,16 +98,17 @@ MODERN_DOCTRINE = Doctrine(
|
|||||||
sead=True,
|
sead=True,
|
||||||
strike=True,
|
strike=True,
|
||||||
antiship=True,
|
antiship=True,
|
||||||
rendezvous_altitude=feet(25000),
|
|
||||||
hold_distance=nautical_miles(25),
|
hold_distance=nautical_miles(25),
|
||||||
push_distance=nautical_miles(20),
|
push_distance=nautical_miles(20),
|
||||||
join_distance=nautical_miles(20),
|
join_distance=nautical_miles(20),
|
||||||
max_ingress_distance=nautical_miles(45),
|
max_ingress_distance=nautical_miles(45),
|
||||||
min_ingress_distance=nautical_miles(10),
|
min_ingress_distance=nautical_miles(10),
|
||||||
ingress_altitude=feet(20000),
|
|
||||||
min_patrol_altitude=feet(15000),
|
min_patrol_altitude=feet(15000),
|
||||||
max_patrol_altitude=feet(33000),
|
max_patrol_altitude=feet(33000),
|
||||||
pattern_altitude=feet(5000),
|
min_cruise_altitude=feet(20000),
|
||||||
|
max_cruise_altitude=feet(40000),
|
||||||
|
min_combat_altitude=feet(15000),
|
||||||
|
max_combat_altitude=feet(35000),
|
||||||
cap_duration=timedelta(minutes=30),
|
cap_duration=timedelta(minutes=30),
|
||||||
cap_min_track_length=nautical_miles(15),
|
cap_min_track_length=nautical_miles(15),
|
||||||
cap_max_track_length=nautical_miles(40),
|
cap_max_track_length=nautical_miles(40),
|
||||||
@ -140,16 +142,17 @@ COLDWAR_DOCTRINE = Doctrine(
|
|||||||
sead=True,
|
sead=True,
|
||||||
strike=True,
|
strike=True,
|
||||||
antiship=True,
|
antiship=True,
|
||||||
rendezvous_altitude=feet(22000),
|
|
||||||
hold_distance=nautical_miles(15),
|
hold_distance=nautical_miles(15),
|
||||||
push_distance=nautical_miles(10),
|
push_distance=nautical_miles(10),
|
||||||
join_distance=nautical_miles(10),
|
join_distance=nautical_miles(10),
|
||||||
max_ingress_distance=nautical_miles(30),
|
max_ingress_distance=nautical_miles(30),
|
||||||
min_ingress_distance=nautical_miles(10),
|
min_ingress_distance=nautical_miles(10),
|
||||||
ingress_altitude=feet(18000),
|
|
||||||
min_patrol_altitude=feet(10000),
|
min_patrol_altitude=feet(10000),
|
||||||
max_patrol_altitude=feet(24000),
|
max_patrol_altitude=feet(24000),
|
||||||
pattern_altitude=feet(5000),
|
min_cruise_altitude=feet(20000),
|
||||||
|
max_cruise_altitude=feet(30000),
|
||||||
|
min_combat_altitude=feet(5000),
|
||||||
|
max_combat_altitude=feet(25000),
|
||||||
cap_duration=timedelta(minutes=30),
|
cap_duration=timedelta(minutes=30),
|
||||||
cap_min_track_length=nautical_miles(12),
|
cap_min_track_length=nautical_miles(12),
|
||||||
cap_max_track_length=nautical_miles(24),
|
cap_max_track_length=nautical_miles(24),
|
||||||
@ -186,13 +189,14 @@ WWII_DOCTRINE = Doctrine(
|
|||||||
hold_distance=nautical_miles(10),
|
hold_distance=nautical_miles(10),
|
||||||
push_distance=nautical_miles(5),
|
push_distance=nautical_miles(5),
|
||||||
join_distance=nautical_miles(5),
|
join_distance=nautical_miles(5),
|
||||||
rendezvous_altitude=feet(10000),
|
|
||||||
max_ingress_distance=nautical_miles(7),
|
max_ingress_distance=nautical_miles(7),
|
||||||
min_ingress_distance=nautical_miles(5),
|
min_ingress_distance=nautical_miles(5),
|
||||||
ingress_altitude=feet(8000),
|
|
||||||
min_patrol_altitude=feet(4000),
|
min_patrol_altitude=feet(4000),
|
||||||
max_patrol_altitude=feet(15000),
|
max_patrol_altitude=feet(15000),
|
||||||
pattern_altitude=feet(5000),
|
min_cruise_altitude=feet(5000),
|
||||||
|
max_cruise_altitude=feet(30000),
|
||||||
|
min_combat_altitude=feet(3000),
|
||||||
|
max_combat_altitude=feet(10000),
|
||||||
cap_duration=timedelta(minutes=30),
|
cap_duration=timedelta(minutes=30),
|
||||||
cap_min_track_length=nautical_miles(8),
|
cap_min_track_length=nautical_miles(8),
|
||||||
cap_max_track_length=nautical_miles(18),
|
cap_max_track_length=nautical_miles(18),
|
||||||
|
|||||||
@ -132,6 +132,21 @@ class PatrolConfig:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class AltitudesConfig:
|
||||||
|
cruise: Optional[Distance]
|
||||||
|
combat: Optional[Distance]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_data(cls, data: dict[str, Any]) -> AltitudesConfig:
|
||||||
|
cruise = data.get("cruise", None)
|
||||||
|
combat = data.get("combat", None)
|
||||||
|
return AltitudesConfig(
|
||||||
|
feet(cruise) if cruise is not None else None,
|
||||||
|
feet(combat) if combat is not None else None,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class FuelConsumption:
|
class FuelConsumption:
|
||||||
#: The estimated taxi fuel requirement, in pounds.
|
#: The estimated taxi fuel requirement, in pounds.
|
||||||
@ -182,6 +197,9 @@ class AircraftType(UnitType[Type[FlyingType]]):
|
|||||||
patrol_altitude: Optional[Distance]
|
patrol_altitude: Optional[Distance]
|
||||||
patrol_speed: Optional[Speed]
|
patrol_speed: Optional[Speed]
|
||||||
|
|
||||||
|
cruise_altitude: Optional[Distance]
|
||||||
|
combat_altitude: Optional[Distance]
|
||||||
|
|
||||||
#: The maximum range between the origin airfield and the target for which the auto-
|
#: The maximum range between the origin airfield and the target for which the auto-
|
||||||
#: planner will consider this aircraft usable for a mission.
|
#: planner will consider this aircraft usable for a mission.
|
||||||
max_mission_range: Distance
|
max_mission_range: Distance
|
||||||
@ -245,33 +263,13 @@ class AircraftType(UnitType[Type[FlyingType]]):
|
|||||||
def max_speed(self) -> Speed:
|
def max_speed(self) -> Speed:
|
||||||
return kph(self.dcs_unit_type.max_speed)
|
return kph(self.dcs_unit_type.max_speed)
|
||||||
|
|
||||||
@property
|
@cached_property
|
||||||
def preferred_patrol_altitude(self) -> Distance:
|
def preferred_patrol_altitude(self) -> Distance:
|
||||||
if self.patrol_altitude is not None:
|
if self.patrol_altitude:
|
||||||
return self.patrol_altitude
|
return self.patrol_altitude
|
||||||
else:
|
else:
|
||||||
# Estimate based on max speed.
|
# TODO: somehow make the upper and lower limit configurable
|
||||||
# Aircaft with max speed 600 kph will prefer patrol at 10 000 ft
|
return self.preferred_altitude(10, 33, "patrol")
|
||||||
# Aircraft with max speed 2800 kph will prefer pratrol at 33 000 ft
|
|
||||||
altitude_for_lowest_speed = feet(10 * 1000)
|
|
||||||
altitude_for_highest_speed = feet(33 * 1000)
|
|
||||||
lowest_speed = kph(600)
|
|
||||||
highest_speed = kph(2800)
|
|
||||||
factor = (self.max_speed - lowest_speed).kph / (
|
|
||||||
highest_speed - lowest_speed
|
|
||||||
).kph
|
|
||||||
altitude = (
|
|
||||||
altitude_for_lowest_speed
|
|
||||||
+ (altitude_for_highest_speed - altitude_for_lowest_speed) * factor
|
|
||||||
)
|
|
||||||
logging.debug(
|
|
||||||
f"Preferred patrol altitude for {self.dcs_unit_type.id}: {altitude.feet}"
|
|
||||||
)
|
|
||||||
rounded_altitude = feet(round(1000 * round(altitude.feet / 1000)))
|
|
||||||
return max(
|
|
||||||
altitude_for_lowest_speed,
|
|
||||||
min(altitude_for_highest_speed, rounded_altitude),
|
|
||||||
)
|
|
||||||
|
|
||||||
def preferred_patrol_speed(self, altitude: Distance) -> Speed:
|
def preferred_patrol_speed(self, altitude: Distance) -> Speed:
|
||||||
"""Preferred true airspeed when patrolling"""
|
"""Preferred true airspeed when patrolling"""
|
||||||
@ -309,6 +307,46 @@ class AircraftType(UnitType[Type[FlyingType]]):
|
|||||||
)
|
)
|
||||||
return min(Speed.from_mach(0.35, altitude), max_speed * 0.5)
|
return min(Speed.from_mach(0.35, altitude), max_speed * 0.5)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def preferred_cruise_altitude(self) -> Distance:
|
||||||
|
if self.cruise_altitude:
|
||||||
|
return self.cruise_altitude
|
||||||
|
else:
|
||||||
|
# TODO: somehow make the upper and lower limit configurable
|
||||||
|
return self.preferred_altitude(20, 20, "cruise")
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def preferred_combat_altitude(self) -> Distance:
|
||||||
|
if self.combat_altitude:
|
||||||
|
return self.combat_altitude
|
||||||
|
else:
|
||||||
|
# TODO: somehow make the upper and lower limit configurable
|
||||||
|
return self.preferred_altitude(20, 20, "combat")
|
||||||
|
|
||||||
|
def preferred_altitude(self, low: int, high: int, type: str) -> Distance:
|
||||||
|
# Estimate based on max speed.
|
||||||
|
# Aircraft with max speed 600 kph will prefer low
|
||||||
|
# Aircraft with max speed 2800 kph will prefer high
|
||||||
|
altitude_for_lowest_speed = feet(low * 1000)
|
||||||
|
altitude_for_highest_speed = feet(high * 1000)
|
||||||
|
lowest_speed = kph(600)
|
||||||
|
highest_speed = kph(2800)
|
||||||
|
factor = (self.max_speed - lowest_speed).kph / (
|
||||||
|
highest_speed - lowest_speed
|
||||||
|
).kph
|
||||||
|
altitude = (
|
||||||
|
altitude_for_lowest_speed
|
||||||
|
+ (altitude_for_highest_speed - altitude_for_lowest_speed) * factor
|
||||||
|
)
|
||||||
|
logging.debug(
|
||||||
|
f"Preferred {type} altitude for {self.dcs_unit_type.id}: {altitude.feet}"
|
||||||
|
)
|
||||||
|
rounded_altitude = feet(round(1000 * round(altitude.feet / 1000)))
|
||||||
|
return max(
|
||||||
|
altitude_for_lowest_speed,
|
||||||
|
min(altitude_for_highest_speed, rounded_altitude),
|
||||||
|
)
|
||||||
|
|
||||||
def alloc_flight_radio(self, radio_registry: RadioRegistry) -> RadioFrequency:
|
def alloc_flight_radio(self, radio_registry: RadioRegistry) -> RadioFrequency:
|
||||||
from game.radio.radios import ChannelInUseError, kHz
|
from game.radio.radios import ChannelInUseError, kHz
|
||||||
|
|
||||||
@ -442,6 +480,7 @@ class AircraftType(UnitType[Type[FlyingType]]):
|
|||||||
|
|
||||||
radio_config = RadioConfig.from_data(data.get("radios", {}))
|
radio_config = RadioConfig.from_data(data.get("radios", {}))
|
||||||
patrol_config = PatrolConfig.from_data(data.get("patrol", {}))
|
patrol_config = PatrolConfig.from_data(data.get("patrol", {}))
|
||||||
|
altitudes_config = AltitudesConfig.from_data(data.get("altitudes", {}))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
mission_range = nautical_miles(int(data["max_range"]))
|
mission_range = nautical_miles(int(data["max_range"]))
|
||||||
@ -510,6 +549,8 @@ class AircraftType(UnitType[Type[FlyingType]]):
|
|||||||
max_group_size=data.get("max_group_size", aircraft.group_size_max),
|
max_group_size=data.get("max_group_size", aircraft.group_size_max),
|
||||||
patrol_altitude=patrol_config.altitude,
|
patrol_altitude=patrol_config.altitude,
|
||||||
patrol_speed=patrol_config.speed,
|
patrol_speed=patrol_config.speed,
|
||||||
|
cruise_altitude=altitudes_config.cruise,
|
||||||
|
combat_altitude=altitudes_config.combat,
|
||||||
max_mission_range=mission_range,
|
max_mission_range=mission_range,
|
||||||
fuel_consumption=fuel_consumption,
|
fuel_consumption=fuel_consumption,
|
||||||
default_livery=data.get("default_livery"),
|
default_livery=data.get("default_livery"),
|
||||||
|
|||||||
@ -52,7 +52,7 @@ class Migrator:
|
|||||||
continue
|
continue
|
||||||
found = False
|
found = False
|
||||||
for d in doctrines:
|
for d in doctrines:
|
||||||
if c.faction.doctrine.rendezvous_altitude == d.rendezvous_altitude:
|
if c.faction.doctrine.max_patrol_altitude == d.max_patrol_altitude:
|
||||||
c.faction.doctrine = d
|
c.faction.doctrine = d
|
||||||
found = True
|
found = True
|
||||||
break
|
break
|
||||||
|
|||||||
@ -8,9 +8,7 @@ class LandingPointBuilder(PydcsWaypointBuilder):
|
|||||||
def build(self) -> MovingPoint:
|
def build(self) -> MovingPoint:
|
||||||
waypoint = super().build()
|
waypoint = super().build()
|
||||||
if self.ai_despawn(waypoint):
|
if self.ai_despawn(waypoint):
|
||||||
waypoint.alt = round(
|
waypoint.alt = round(self.flight.unit_type.preferred_patrol_altitude.meters)
|
||||||
self.flight.coalition.doctrine.max_patrol_altitude.meters
|
|
||||||
)
|
|
||||||
waypoint.alt_type = "BARO"
|
waypoint.alt_type = "BARO"
|
||||||
else:
|
else:
|
||||||
waypoint.type = "Land"
|
waypoint.type = "Land"
|
||||||
|
|||||||
@ -297,8 +297,18 @@ class Settings:
|
|||||||
page=CAMPAIGN_DOCTRINE_PAGE,
|
page=CAMPAIGN_DOCTRINE_PAGE,
|
||||||
section=GENERAL_SECTION,
|
section=GENERAL_SECTION,
|
||||||
default=False,
|
default=False,
|
||||||
detail=("AI will jettison their fuel tanks as soon as they're empty."),
|
detail="AI will jettison their fuel tanks as soon as they're empty.",
|
||||||
)
|
)
|
||||||
|
max_plane_altitude_offset: int = bounded_int_option(
|
||||||
|
"Maximum randomized altitude offset (x1000 ft) for airplanes.",
|
||||||
|
page=CAMPAIGN_DOCTRINE_PAGE,
|
||||||
|
section=GENERAL_SECTION,
|
||||||
|
min=0,
|
||||||
|
max=5,
|
||||||
|
default=2,
|
||||||
|
detail="Creates a randomized altitude offset for airplanes.",
|
||||||
|
)
|
||||||
|
# Doctrine Distances Section
|
||||||
airbase_threat_range: int = bounded_int_option(
|
airbase_threat_range: int = bounded_int_option(
|
||||||
"Airbase threat range (nmi)",
|
"Airbase threat range (nmi)",
|
||||||
page=CAMPAIGN_DOCTRINE_PAGE,
|
page=CAMPAIGN_DOCTRINE_PAGE,
|
||||||
|
|||||||
@ -86,7 +86,7 @@ class QFlightWaypointList(QTableView):
|
|||||||
|
|
||||||
self.model.setItem(row, 0, QWaypointItem(waypoint, row))
|
self.model.setItem(row, 0, QWaypointItem(waypoint, row))
|
||||||
|
|
||||||
altitude = int(waypoint.alt.feet)
|
altitude = round(waypoint.alt.feet)
|
||||||
altitude_item = QStandardItem(f"{altitude}")
|
altitude_item = QStandardItem(f"{altitude}")
|
||||||
altitude_item.setEditable(True)
|
altitude_item.setEditable(True)
|
||||||
self.model.setItem(row, 1, altitude_item)
|
self.model.setItem(row, 1, altitude_item)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user