mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Doctrine cleanup (#3318)
This PR: - Refactors the doctrine class to have a bit more structure, in anticipation of adding more elements to Doctrine. - Moves previously hard coded helo-specific altitudes into the Doctrine class, aligning a bunch of altitudes ~200ft in the process. - Refactors ingress_altitude to combat_altitude to clarify that the altitude is applied to multiple waypoint types, not just the ingress altitude.
This commit is contained in:
parent
c695e7724a
commit
5b858886c0
@ -88,7 +88,7 @@ class Builder(IBuilder[AirAssaultFlightPlan, AirAssaultLayout]):
|
|||||||
raise PlanningError("Air assault is only usable by helicopters")
|
raise PlanningError("Air assault is only usable by helicopters")
|
||||||
assert self.package.waypoints is not None
|
assert self.package.waypoints is not None
|
||||||
|
|
||||||
altitude = feet(1500) if self.flight.is_helo else self.doctrine.ingress_altitude
|
altitude = self.doctrine.helicopter.air_assault_nav_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)
|
||||||
|
|||||||
@ -19,7 +19,7 @@ class BarCapFlightPlan(PatrollingFlightPlan[PatrollingLayout]):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def patrol_duration(self) -> timedelta:
|
def patrol_duration(self) -> timedelta:
|
||||||
return self.flight.coalition.doctrine.cap_duration
|
return self.flight.coalition.doctrine.cap.duration
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def patrol_speed(self) -> Speed:
|
def patrol_speed(self) -> Speed:
|
||||||
@ -29,7 +29,7 @@ class BarCapFlightPlan(PatrollingFlightPlan[PatrollingLayout]):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def engagement_distance(self) -> Distance:
|
def engagement_distance(self) -> Distance:
|
||||||
return self.flight.coalition.doctrine.cap_engagement_range
|
return self.flight.coalition.doctrine.cap.engagement_range
|
||||||
|
|
||||||
|
|
||||||
class Builder(CapBuilder[BarCapFlightPlan, PatrollingLayout]):
|
class Builder(CapBuilder[BarCapFlightPlan, PatrollingLayout]):
|
||||||
@ -44,8 +44,8 @@ class Builder(CapBuilder[BarCapFlightPlan, PatrollingLayout]):
|
|||||||
preferred_alt = self.flight.unit_type.preferred_patrol_altitude
|
preferred_alt = self.flight.unit_type.preferred_patrol_altitude
|
||||||
randomized_alt = preferred_alt + feet(random.randint(-2, 1) * 1000)
|
randomized_alt = preferred_alt + feet(random.randint(-2, 1) * 1000)
|
||||||
patrol_alt = max(
|
patrol_alt = max(
|
||||||
self.doctrine.min_patrol_altitude,
|
self.doctrine.cap.min_patrol_altitude,
|
||||||
min(self.doctrine.max_patrol_altitude, randomized_alt),
|
min(self.doctrine.cap.max_patrol_altitude, randomized_alt),
|
||||||
)
|
)
|
||||||
|
|
||||||
builder = WaypointBuilder(self.flight, self.coalition)
|
builder = WaypointBuilder(self.flight, self.coalition)
|
||||||
|
|||||||
@ -90,10 +90,10 @@ class CapBuilder(IBuilder[FlightPlanT, LayoutT], ABC):
|
|||||||
# buffer.
|
# buffer.
|
||||||
distance_to_no_fly = (
|
distance_to_no_fly = (
|
||||||
meters(position.distance(self.threat_zones.all))
|
meters(position.distance(self.threat_zones.all))
|
||||||
- self.doctrine.cap_engagement_range
|
- self.doctrine.cap.engagement_range
|
||||||
- nautical_miles(5)
|
- nautical_miles(5)
|
||||||
)
|
)
|
||||||
max_track_length = self.doctrine.cap_max_track_length
|
max_track_length = self.doctrine.cap.max_track_length
|
||||||
else:
|
else:
|
||||||
# Other race tracks (TARCAPs, currently) just try to keep some
|
# Other race tracks (TARCAPs, currently) just try to keep some
|
||||||
# distance from the nearest enemy airbase, but since they are by
|
# distance from the nearest enemy airbase, but since they are by
|
||||||
@ -108,15 +108,15 @@ class CapBuilder(IBuilder[FlightPlanT, LayoutT], ABC):
|
|||||||
distance_to_no_fly = distance_to_airfield - min_distance_from_enemy
|
distance_to_no_fly = distance_to_airfield - min_distance_from_enemy
|
||||||
|
|
||||||
# TARCAPs fly short racetracks because they need to react faster.
|
# TARCAPs fly short racetracks because they need to react faster.
|
||||||
max_track_length = self.doctrine.cap_min_track_length + 0.3 * (
|
max_track_length = self.doctrine.cap.min_track_length + 0.3 * (
|
||||||
self.doctrine.cap_max_track_length - self.doctrine.cap_min_track_length
|
self.doctrine.cap.max_track_length - self.doctrine.cap.min_track_length
|
||||||
)
|
)
|
||||||
|
|
||||||
min_cap_distance = min(
|
min_cap_distance = min(
|
||||||
self.doctrine.cap_min_distance_from_cp, distance_to_no_fly
|
self.doctrine.cap.min_distance_from_cp, distance_to_no_fly
|
||||||
)
|
)
|
||||||
max_cap_distance = min(
|
max_cap_distance = min(
|
||||||
self.doctrine.cap_max_distance_from_cp, distance_to_no_fly
|
self.doctrine.cap.max_distance_from_cp, distance_to_no_fly
|
||||||
)
|
)
|
||||||
|
|
||||||
end = location.position.point_from_heading(
|
end = location.position.point_from_heading(
|
||||||
@ -125,7 +125,7 @@ class CapBuilder(IBuilder[FlightPlanT, LayoutT], ABC):
|
|||||||
)
|
)
|
||||||
|
|
||||||
track_length = random.randint(
|
track_length = random.randint(
|
||||||
int(self.doctrine.cap_min_track_length.meters),
|
int(self.doctrine.cap.min_track_length.meters),
|
||||||
int(max_track_length.meters),
|
int(max_track_length.meters),
|
||||||
)
|
)
|
||||||
start = end.point_from_heading(heading.opposite.degrees, track_length)
|
start = end.point_from_heading(heading.opposite.degrees, track_length)
|
||||||
|
|||||||
@ -44,7 +44,7 @@ class CasFlightPlan(PatrollingFlightPlan[CasLayout], UiZoneDisplay):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def patrol_duration(self) -> timedelta:
|
def patrol_duration(self) -> timedelta:
|
||||||
return self.flight.coalition.doctrine.cas_duration
|
return self.flight.coalition.doctrine.cas.duration
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def patrol_speed(self) -> Speed:
|
def patrol_speed(self) -> Speed:
|
||||||
@ -96,7 +96,7 @@ class Builder(IBuilder[CasFlightPlan, CasLayout]):
|
|||||||
builder = WaypointBuilder(self.flight, self.coalition)
|
builder = WaypointBuilder(self.flight, self.coalition)
|
||||||
|
|
||||||
is_helo = self.flight.unit_type.dcs_unit_type.helicopter
|
is_helo = self.flight.unit_type.dcs_unit_type.helicopter
|
||||||
patrol_altitude = self.doctrine.ingress_altitude if not is_helo else meters(50)
|
patrol_altitude = self.doctrine.resolve_combat_altitude(is_helo)
|
||||||
use_agl_patrol_altitude = is_helo
|
use_agl_patrol_altitude = is_helo
|
||||||
|
|
||||||
ip_solver = IpSolver(
|
ip_solver = IpSolver(
|
||||||
|
|||||||
@ -33,7 +33,7 @@ class Builder(FormationAttackBuilder[EscortFlightPlan, FormationAttackLayout]):
|
|||||||
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, join.position, self.doctrine.ingress_altitude
|
hold.position, join.position, self.doctrine.combat_altitude
|
||||||
),
|
),
|
||||||
join=join,
|
join=join,
|
||||||
ingress=ingress,
|
ingress=ingress,
|
||||||
@ -43,7 +43,7 @@ class Builder(FormationAttackBuilder[EscortFlightPlan, FormationAttackLayout]):
|
|||||||
nav_from=builder.nav_path(
|
nav_from=builder.nav_path(
|
||||||
refuel.position,
|
refuel.position,
|
||||||
self.flight.arrival.position,
|
self.flight.arrival.position,
|
||||||
self.doctrine.ingress_altitude,
|
self.doctrine.combat_altitude,
|
||||||
),
|
),
|
||||||
arrival=builder.land(self.flight.arrival),
|
arrival=builder.land(self.flight.arrival),
|
||||||
divert=builder.divert(self.flight.divert),
|
divert=builder.divert(self.flight.divert),
|
||||||
|
|||||||
@ -163,7 +163,7 @@ class FormationAttackBuilder(IBuilder[FlightPlanT, LayoutT], ABC):
|
|||||||
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, join.position, self.doctrine.ingress_altitude
|
hold.position, join.position, self.doctrine.combat_altitude
|
||||||
),
|
),
|
||||||
join=join,
|
join=join,
|
||||||
ingress=ingress,
|
ingress=ingress,
|
||||||
@ -173,7 +173,7 @@ class FormationAttackBuilder(IBuilder[FlightPlanT, LayoutT], ABC):
|
|||||||
nav_from=builder.nav_path(
|
nav_from=builder.nav_path(
|
||||||
refuel.position,
|
refuel.position,
|
||||||
self.flight.arrival.position,
|
self.flight.arrival.position,
|
||||||
self.doctrine.ingress_altitude,
|
self.doctrine.combat_altitude,
|
||||||
),
|
),
|
||||||
arrival=builder.land(self.flight.arrival),
|
arrival=builder.land(self.flight.arrival),
|
||||||
divert=builder.divert(self.flight.divert),
|
divert=builder.divert(self.flight.divert),
|
||||||
|
|||||||
@ -114,11 +114,11 @@ class Builder(IBuilder[SweepFlightPlan, SweepLayout]):
|
|||||||
self.package.waypoints.join.heading_between_point(target)
|
self.package.waypoints.join.heading_between_point(target)
|
||||||
)
|
)
|
||||||
start_pos = target.point_from_heading(
|
start_pos = target.point_from_heading(
|
||||||
heading.degrees, -self.doctrine.sweep_distance.meters
|
heading.degrees, -self.doctrine.sweep.distance.meters
|
||||||
)
|
)
|
||||||
|
|
||||||
builder = WaypointBuilder(self.flight, self.coalition)
|
builder = WaypointBuilder(self.flight, self.coalition)
|
||||||
start, end = builder.sweep(start_pos, target, self.doctrine.ingress_altitude)
|
start, end = builder.sweep(start_pos, target, self.doctrine.combat_altitude)
|
||||||
|
|
||||||
hold = builder.hold(self._hold_point())
|
hold = builder.hold(self._hold_point())
|
||||||
|
|
||||||
@ -126,12 +126,12 @@ class Builder(IBuilder[SweepFlightPlan, 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, self.doctrine.ingress_altitude
|
hold.position, start.position, self.doctrine.combat_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,
|
self.doctrine.combat_altitude,
|
||||||
),
|
),
|
||||||
sweep_start=start,
|
sweep_start=start,
|
||||||
sweep_end=end,
|
sweep_end=end,
|
||||||
|
|||||||
@ -40,7 +40,7 @@ class TarCapFlightPlan(PatrollingFlightPlan[TarCapLayout]):
|
|||||||
# flights in the package that have requested escort. If the package
|
# flights in the package that have requested escort. If the package
|
||||||
# requests an escort the CAP self.flight will remain on station for the
|
# requests an escort the CAP self.flight will remain on station for the
|
||||||
# duration of the escorted mission, or until it is winchester/bingo.
|
# duration of the escorted mission, or until it is winchester/bingo.
|
||||||
return self.flight.coalition.doctrine.cap_duration
|
return self.flight.coalition.doctrine.cap.duration
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def patrol_speed(self) -> Speed:
|
def patrol_speed(self) -> Speed:
|
||||||
@ -50,7 +50,7 @@ class TarCapFlightPlan(PatrollingFlightPlan[TarCapLayout]):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def engagement_distance(self) -> Distance:
|
def engagement_distance(self) -> Distance:
|
||||||
return self.flight.coalition.doctrine.cap_engagement_range
|
return self.flight.coalition.doctrine.cap.engagement_range
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def builder_type() -> Type[Builder]:
|
def builder_type() -> Type[Builder]:
|
||||||
@ -90,8 +90,8 @@ class Builder(CapBuilder[TarCapFlightPlan, TarCapLayout]):
|
|||||||
preferred_alt = self.flight.unit_type.preferred_patrol_altitude
|
preferred_alt = self.flight.unit_type.preferred_patrol_altitude
|
||||||
randomized_alt = preferred_alt + feet(random.randint(-2, 1) * 1000)
|
randomized_alt = preferred_alt + feet(random.randint(-2, 1) * 1000)
|
||||||
patrol_alt = max(
|
patrol_alt = max(
|
||||||
self.doctrine.min_patrol_altitude,
|
self.doctrine.cap.min_patrol_altitude,
|
||||||
min(self.doctrine.max_patrol_altitude, randomized_alt),
|
min(self.doctrine.cap.max_patrol_altitude, randomized_alt),
|
||||||
)
|
)
|
||||||
|
|
||||||
builder = WaypointBuilder(self.flight, self.coalition)
|
builder = WaypointBuilder(self.flight, self.coalition)
|
||||||
|
|||||||
@ -72,7 +72,7 @@ class WaypointBuilder:
|
|||||||
"NAV",
|
"NAV",
|
||||||
FlightWaypointType.NAV,
|
FlightWaypointType.NAV,
|
||||||
position,
|
position,
|
||||||
meters(500) if self.is_helo else self.doctrine.rendezvous_altitude,
|
self.doctrine.resolve_rendezvous_altitude(self.is_helo),
|
||||||
description="Enter theater",
|
description="Enter theater",
|
||||||
pretty_name="Enter theater",
|
pretty_name="Enter theater",
|
||||||
)
|
)
|
||||||
@ -99,7 +99,7 @@ class WaypointBuilder:
|
|||||||
"NAV",
|
"NAV",
|
||||||
FlightWaypointType.NAV,
|
FlightWaypointType.NAV,
|
||||||
position,
|
position,
|
||||||
meters(500) if self.is_helo else self.doctrine.rendezvous_altitude,
|
self.doctrine.resolve_rendezvous_altitude(self.is_helo),
|
||||||
description="Exit theater",
|
description="Exit theater",
|
||||||
pretty_name="Exit theater",
|
pretty_name="Exit theater",
|
||||||
)
|
)
|
||||||
@ -127,10 +127,7 @@ class WaypointBuilder:
|
|||||||
position = divert.position
|
position = divert.position
|
||||||
altitude_type: AltitudeReference
|
altitude_type: AltitudeReference
|
||||||
if isinstance(divert, OffMapSpawn):
|
if isinstance(divert, OffMapSpawn):
|
||||||
if self.is_helo:
|
altitude = self.doctrine.resolve_rendezvous_altitude(self.is_helo)
|
||||||
altitude = meters(500)
|
|
||||||
else:
|
|
||||||
altitude = self.doctrine.rendezvous_altitude
|
|
||||||
altitude_type = "BARO"
|
altitude_type = "BARO"
|
||||||
else:
|
else:
|
||||||
altitude = meters(0)
|
altitude = meters(0)
|
||||||
@ -168,10 +165,7 @@ class WaypointBuilder:
|
|||||||
"HOLD",
|
"HOLD",
|
||||||
FlightWaypointType.LOITER,
|
FlightWaypointType.LOITER,
|
||||||
position,
|
position,
|
||||||
# Bug: DCS only accepts MSL altitudes for the orbit task and 500 meters is
|
self.doctrine.resolve_rendezvous_altitude(self.is_helo),
|
||||||
# below the ground for most if not all of NTTR (and lots of places in other
|
|
||||||
# maps).
|
|
||||||
meters(500) if self.is_helo else self.doctrine.rendezvous_altitude,
|
|
||||||
alt_type,
|
alt_type,
|
||||||
description="Wait until push time",
|
description="Wait until push time",
|
||||||
pretty_name="Hold",
|
pretty_name="Hold",
|
||||||
@ -186,7 +180,7 @@ class WaypointBuilder:
|
|||||||
"JOIN",
|
"JOIN",
|
||||||
FlightWaypointType.JOIN,
|
FlightWaypointType.JOIN,
|
||||||
position,
|
position,
|
||||||
meters(80) if self.is_helo else self.doctrine.ingress_altitude,
|
self.doctrine.resolve_combat_altitude(self.is_helo),
|
||||||
alt_type,
|
alt_type,
|
||||||
description="Rendezvous with package",
|
description="Rendezvous with package",
|
||||||
pretty_name="Join",
|
pretty_name="Join",
|
||||||
@ -201,7 +195,7 @@ class WaypointBuilder:
|
|||||||
"REFUEL",
|
"REFUEL",
|
||||||
FlightWaypointType.REFUEL,
|
FlightWaypointType.REFUEL,
|
||||||
position,
|
position,
|
||||||
meters(80) if self.is_helo else self.doctrine.ingress_altitude,
|
self.doctrine.resolve_combat_altitude(self.is_helo),
|
||||||
alt_type,
|
alt_type,
|
||||||
description="Refuel from tanker",
|
description="Refuel from tanker",
|
||||||
pretty_name="Refuel",
|
pretty_name="Refuel",
|
||||||
@ -229,7 +223,7 @@ class WaypointBuilder:
|
|||||||
"SPLIT",
|
"SPLIT",
|
||||||
FlightWaypointType.SPLIT,
|
FlightWaypointType.SPLIT,
|
||||||
position,
|
position,
|
||||||
meters(80) if self.is_helo else self.doctrine.ingress_altitude,
|
self.doctrine.resolve_combat_altitude(self.is_helo),
|
||||||
alt_type,
|
alt_type,
|
||||||
description="Depart from package",
|
description="Depart from package",
|
||||||
pretty_name="Split",
|
pretty_name="Split",
|
||||||
@ -249,7 +243,7 @@ class WaypointBuilder:
|
|||||||
"INGRESS",
|
"INGRESS",
|
||||||
ingress_type,
|
ingress_type,
|
||||||
position,
|
position,
|
||||||
meters(60) if self.is_helo else self.doctrine.ingress_altitude,
|
self.doctrine.resolve_combat_altitude(self.is_helo),
|
||||||
alt_type,
|
alt_type,
|
||||||
description=f"INGRESS on {objective.name}",
|
description=f"INGRESS on {objective.name}",
|
||||||
pretty_name=f"INGRESS on {objective.name}",
|
pretty_name=f"INGRESS on {objective.name}",
|
||||||
@ -294,7 +288,7 @@ class WaypointBuilder:
|
|||||||
f"SEAD on {target.name}",
|
f"SEAD on {target.name}",
|
||||||
target,
|
target,
|
||||||
flyover=True,
|
flyover=True,
|
||||||
altitude=self.doctrine.ingress_altitude,
|
altitude=self.doctrine.combat_altitude,
|
||||||
alt_type="BARO",
|
alt_type="BARO",
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -484,7 +478,7 @@ class WaypointBuilder:
|
|||||||
"TARGET",
|
"TARGET",
|
||||||
FlightWaypointType.TARGET_GROUP_LOC,
|
FlightWaypointType.TARGET_GROUP_LOC,
|
||||||
target.position,
|
target.position,
|
||||||
meters(60) if self.is_helo else self.doctrine.ingress_altitude,
|
self.doctrine.resolve_combat_altitude(self.is_helo),
|
||||||
alt_type,
|
alt_type,
|
||||||
description="Escort the package",
|
description="Escort the package",
|
||||||
pretty_name="Target area",
|
pretty_name="Target area",
|
||||||
|
|||||||
@ -158,7 +158,7 @@ class TheaterState(WorldState["TheaterState"]):
|
|||||||
# Plan enough rounds of CAP that the target has coverage over the expected
|
# Plan enough rounds of CAP that the target has coverage over the expected
|
||||||
# mission duration.
|
# mission duration.
|
||||||
mission_duration = game.settings.desired_player_mission_duration.total_seconds()
|
mission_duration = game.settings.desired_player_mission_duration.total_seconds()
|
||||||
barcap_duration = coalition.doctrine.cap_duration.total_seconds()
|
barcap_duration = coalition.doctrine.cap.duration.total_seconds()
|
||||||
barcap_rounds = math.ceil(mission_duration / barcap_duration)
|
barcap_rounds = math.ceil(mission_duration / barcap_duration)
|
||||||
|
|
||||||
refueling_targets: list[MissionTarget] = []
|
refueling_targets: list[MissionTarget] = []
|
||||||
|
|||||||
@ -2,7 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import yaml
|
import yaml
|
||||||
from typing import ClassVar
|
from typing import Any, ClassVar
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
@ -32,18 +32,94 @@ class GroundUnitProcurementRatios:
|
|||||||
return GroundUnitProcurementRatios(r)
|
return GroundUnitProcurementRatios(r)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Helicopter:
|
||||||
|
#: The altitude used for combat section of a flight, overrides the base combat_altitude parameter for helos
|
||||||
|
combat_altitude: Distance
|
||||||
|
|
||||||
|
#: The altitude used for forming up a pacakge. Overrides the base rendezvous_altitude parameter for helos
|
||||||
|
rendezvous_altitude: Distance
|
||||||
|
|
||||||
|
#: Altitude of the nav points (cruise section) of air assault missions.
|
||||||
|
air_assault_nav_altitude: Distance
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_dict(data: dict[str, Any]) -> Helicopter:
|
||||||
|
return Helicopter(
|
||||||
|
combat_altitude=feet(data["combat_altitude_ft_agl"]),
|
||||||
|
rendezvous_altitude=feet(data["rendezvous_altitude_ft_agl"]),
|
||||||
|
air_assault_nav_altitude=feet(data["air_assault_nav_altitude_ft_agl"]),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Cas:
|
||||||
|
#: The duration that CAP flights will remain on-station.
|
||||||
|
duration: timedelta
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_dict(data: dict[str, Any]) -> Cas:
|
||||||
|
return Cas(duration=timedelta(minutes=data["duration_minutes"]))
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Sweep:
|
||||||
|
#: Length of the sweep / patrol leg
|
||||||
|
distance: Distance
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_dict(data: dict[str, Any]) -> Sweep:
|
||||||
|
return Sweep(
|
||||||
|
distance=nautical_miles(data["distance_nm"]),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Cap:
|
||||||
|
#: The duration that CAP flights will remain on-station.
|
||||||
|
duration: timedelta
|
||||||
|
|
||||||
|
#: The minimum length of the CAP race track.
|
||||||
|
min_track_length: Distance
|
||||||
|
|
||||||
|
#: The maximum length of the CAP race track.
|
||||||
|
max_track_length: Distance
|
||||||
|
|
||||||
|
#: The minimum distance between the defended position and the *end* of the
|
||||||
|
#: CAP race track.
|
||||||
|
min_distance_from_cp: Distance
|
||||||
|
|
||||||
|
#: The maximum distance between the defended position and the *end* of the
|
||||||
|
#: CAP race track.
|
||||||
|
max_distance_from_cp: Distance
|
||||||
|
|
||||||
|
#: The engagement range of CAP flights. Any enemy aircraft within this range
|
||||||
|
#: of the CAP's current position will be engaged by the CAP.
|
||||||
|
engagement_range: Distance
|
||||||
|
|
||||||
|
#: Defines the range of altitudes CAP racetracks are planned at.
|
||||||
|
min_patrol_altitude: Distance
|
||||||
|
max_patrol_altitude: Distance
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_dict(data: dict[str, Any]) -> Cap:
|
||||||
|
return Cap(
|
||||||
|
duration=timedelta(minutes=data["duration_minutes"]),
|
||||||
|
min_track_length=nautical_miles(data["min_track_length_nm"]),
|
||||||
|
max_track_length=nautical_miles(data["max_track_length_nm"]),
|
||||||
|
min_distance_from_cp=nautical_miles(data["min_distance_from_cp_nm"]),
|
||||||
|
max_distance_from_cp=nautical_miles(data["max_distance_from_cp_nm"]),
|
||||||
|
engagement_range=nautical_miles(data["engagement_range_nm"]),
|
||||||
|
min_patrol_altitude=feet(data["min_patrol_altitude_ft_msl"]),
|
||||||
|
max_patrol_altitude=feet(data["max_patrol_altitude_ft_msl"]),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class Doctrine:
|
class Doctrine:
|
||||||
|
#: Name of the doctrine, used to assign a doctrine in a faction.
|
||||||
name: str
|
name: str
|
||||||
|
|
||||||
cas: bool
|
|
||||||
cap: bool
|
|
||||||
sead: bool
|
|
||||||
strike: 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
|
||||||
|
|
||||||
@ -62,42 +138,40 @@ class Doctrine:
|
|||||||
#: target.
|
#: target.
|
||||||
min_ingress_distance: Distance
|
min_ingress_distance: Distance
|
||||||
|
|
||||||
ingress_altitude: Distance
|
#: The altitude used for combat section of a flight.
|
||||||
|
combat_altitude: Distance
|
||||||
|
|
||||||
min_patrol_altitude: Distance
|
#: The altitude used for forming up a pacakge.
|
||||||
max_patrol_altitude: Distance
|
rendezvous_altitude: Distance
|
||||||
pattern_altitude: Distance
|
|
||||||
|
|
||||||
#: The duration that CAP flights will remain on-station.
|
|
||||||
cap_duration: timedelta
|
|
||||||
|
|
||||||
#: The minimum length of the CAP race track.
|
|
||||||
cap_min_track_length: Distance
|
|
||||||
|
|
||||||
#: The maximum length of the CAP race track.
|
|
||||||
cap_max_track_length: Distance
|
|
||||||
|
|
||||||
#: The minimum distance between the defended position and the *end* of the
|
|
||||||
#: CAP race track.
|
|
||||||
cap_min_distance_from_cp: Distance
|
|
||||||
|
|
||||||
#: The maximum distance between the defended position and the *end* of the
|
|
||||||
#: CAP race track.
|
|
||||||
cap_max_distance_from_cp: Distance
|
|
||||||
|
|
||||||
#: The engagement range of CAP flights. Any enemy aircraft within this range
|
|
||||||
#: of the CAP's current position will be engaged by the CAP.
|
|
||||||
cap_engagement_range: Distance
|
|
||||||
|
|
||||||
cas_duration: timedelta
|
|
||||||
|
|
||||||
sweep_distance: Distance
|
|
||||||
|
|
||||||
|
#: Defines prioritization of ground unit purchases.
|
||||||
ground_unit_procurement_ratios: GroundUnitProcurementRatios
|
ground_unit_procurement_ratios: GroundUnitProcurementRatios
|
||||||
|
|
||||||
|
#: Helicopter specific doctrines.
|
||||||
|
helicopter: Helicopter
|
||||||
|
|
||||||
|
#: Doctrine for CAS missions.
|
||||||
|
cas: Cas
|
||||||
|
|
||||||
|
#: Doctrine for CAP missions.
|
||||||
|
cap: Cap
|
||||||
|
|
||||||
|
#: Doctrine for Fighter Sweep missions.
|
||||||
|
sweep: Sweep
|
||||||
|
|
||||||
_by_name: ClassVar[dict[str, Doctrine]] = {}
|
_by_name: ClassVar[dict[str, Doctrine]] = {}
|
||||||
_loaded: ClassVar[bool] = False
|
_loaded: ClassVar[bool] = False
|
||||||
|
|
||||||
|
def resolve_combat_altitude(self, is_helo: bool = False) -> Distance:
|
||||||
|
if is_helo:
|
||||||
|
return self.helicopter.combat_altitude
|
||||||
|
return self.combat_altitude
|
||||||
|
|
||||||
|
def resolve_rendezvous_altitude(self, is_helo: bool = False) -> Distance:
|
||||||
|
if is_helo:
|
||||||
|
return self.helicopter.rendezvous_altitude
|
||||||
|
return self.rendezvous_altitude
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def register(cls, doctrine: Doctrine) -> None:
|
def register(cls, doctrine: Doctrine) -> None:
|
||||||
if doctrine.name in cls._by_name:
|
if doctrine.name in cls._by_name:
|
||||||
@ -127,11 +201,6 @@ class Doctrine:
|
|||||||
cls.register(
|
cls.register(
|
||||||
Doctrine(
|
Doctrine(
|
||||||
name=data["name"],
|
name=data["name"],
|
||||||
cap=data["cap"],
|
|
||||||
cas=data["cas"],
|
|
||||||
sead=data["sead"],
|
|
||||||
strike=data["strike"],
|
|
||||||
antiship=data["antiship"],
|
|
||||||
rendezvous_altitude=feet(data["rendezvous_altitude_ft_msl"]),
|
rendezvous_altitude=feet(data["rendezvous_altitude_ft_msl"]),
|
||||||
hold_distance=nautical_miles(data["hold_distance_nm"]),
|
hold_distance=nautical_miles(data["hold_distance_nm"]),
|
||||||
push_distance=nautical_miles(data["push_distance_nm"]),
|
push_distance=nautical_miles(data["push_distance_nm"]),
|
||||||
@ -142,31 +211,14 @@ class Doctrine:
|
|||||||
min_ingress_distance=nautical_miles(
|
min_ingress_distance=nautical_miles(
|
||||||
data["min_ingress_distance_nm"]
|
data["min_ingress_distance_nm"]
|
||||||
),
|
),
|
||||||
ingress_altitude=feet(data["ingress_altitude_ft_msl"]),
|
combat_altitude=feet(data["combat_altitude_ft_msl"]),
|
||||||
min_patrol_altitude=feet(data["min_patrol_altitude_ft_msl"]),
|
|
||||||
max_patrol_altitude=feet(data["max_patrol_altitude_ft_msl"]),
|
|
||||||
pattern_altitude=feet(data["pattern_altitude_ft_msl"]),
|
|
||||||
cap_duration=timedelta(minutes=data["cap_duration_minutes"]),
|
|
||||||
cap_min_track_length=nautical_miles(
|
|
||||||
data["cap_min_track_length_nm"]
|
|
||||||
),
|
|
||||||
cap_max_track_length=nautical_miles(
|
|
||||||
data["cap_max_track_length_nm"]
|
|
||||||
),
|
|
||||||
cap_min_distance_from_cp=nautical_miles(
|
|
||||||
data["cap_min_distance_from_cp_nm"]
|
|
||||||
),
|
|
||||||
cap_max_distance_from_cp=nautical_miles(
|
|
||||||
data["cap_max_distance_from_cp_nm"]
|
|
||||||
),
|
|
||||||
cap_engagement_range=nautical_miles(
|
|
||||||
data["cap_engagement_range_nm"]
|
|
||||||
),
|
|
||||||
cas_duration=timedelta(minutes=data["cas_duration_minutes"]),
|
|
||||||
sweep_distance=nautical_miles(data["sweep_distance_nm"]),
|
|
||||||
ground_unit_procurement_ratios=GroundUnitProcurementRatios.from_dict(
|
ground_unit_procurement_ratios=GroundUnitProcurementRatios.from_dict(
|
||||||
data["ground_unit_procurement_ratios"]
|
data["ground_unit_procurement_ratios"]
|
||||||
),
|
),
|
||||||
|
helicopter=Helicopter.from_dict(data["helicopter"]),
|
||||||
|
cas=Cas.from_dict(data["cas"]),
|
||||||
|
cap=Cap.from_dict(data["cap"]),
|
||||||
|
sweep=Sweep.from_dict(data["sweep"]),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
cls._loaded = True
|
cls._loaded = True
|
||||||
|
|||||||
@ -165,7 +165,7 @@ class ThreatZones:
|
|||||||
cls, doctrine: Doctrine, control_point: ControlPoint
|
cls, doctrine: Doctrine, control_point: ControlPoint
|
||||||
) -> Distance:
|
) -> Distance:
|
||||||
cap_threat_range = (
|
cap_threat_range = (
|
||||||
doctrine.cap_max_distance_from_cp + doctrine.cap_engagement_range
|
doctrine.cap.max_distance_from_cp + doctrine.cap.engagement_range
|
||||||
)
|
)
|
||||||
opposing_airfield = cls.closest_enemy_airbase(
|
opposing_airfield = cls.closest_enemy_airbase(
|
||||||
control_point, cap_threat_range * 2
|
control_point, cap_threat_range * 2
|
||||||
|
|||||||
@ -1,27 +1,24 @@
|
|||||||
name: coldwar
|
name: coldwar
|
||||||
cap: true
|
|
||||||
cas: true
|
|
||||||
sead: true
|
|
||||||
strike: true
|
|
||||||
antiship: true
|
|
||||||
rendezvous_altitude_ft_msl: 22000
|
|
||||||
hold_distance_nm: 15
|
hold_distance_nm: 15
|
||||||
push_distance_nm: 10
|
push_distance_nm: 10
|
||||||
join_distance_nm: 10
|
join_distance_nm: 10
|
||||||
max_ingress_distance_nm: 30
|
max_ingress_distance_nm: 30
|
||||||
min_ingress_distance_nm: 10
|
min_ingress_distance_nm: 10
|
||||||
ingress_altitude_ft_msl: 18000
|
rendezvous_altitude_ft_msl: 22000
|
||||||
min_patrol_altitude_ft_msl: 10000
|
combat_altitude_ft_msl: 18000
|
||||||
max_patrol_altitude_ft_msl: 24000
|
cap:
|
||||||
pattern_altitude_ft_msl: 5000
|
duration_minutes: 30
|
||||||
cap_duration_minutes: 30
|
min_track_length_nm: 12
|
||||||
cap_min_track_length_nm: 12
|
max_track_length_nm: 24
|
||||||
cap_max_track_length_nm: 24
|
min_distance_from_cp_nm: 8
|
||||||
cap_min_distance_from_cp_nm: 8
|
max_distance_from_cp_nm: 25
|
||||||
cap_max_distance_from_cp_nm: 25
|
engagement_range_nm: 35
|
||||||
cap_engagement_range_nm: 35
|
min_patrol_altitude_ft_msl: 10000
|
||||||
cas_duration_minutes: 30
|
max_patrol_altitude_ft_msl: 24000
|
||||||
sweep_distance_nm: 40
|
cas:
|
||||||
|
duration_minutes: 30
|
||||||
|
sweep:
|
||||||
|
distance_nm: 40
|
||||||
ground_unit_procurement_ratios:
|
ground_unit_procurement_ratios:
|
||||||
Tank: 4
|
Tank: 4
|
||||||
ATGM: 2
|
ATGM: 2
|
||||||
@ -30,3 +27,8 @@ ground_unit_procurement_ratios:
|
|||||||
Artillery: 1
|
Artillery: 1
|
||||||
SHORAD: 2
|
SHORAD: 2
|
||||||
Recon: 1
|
Recon: 1
|
||||||
|
helicopter:
|
||||||
|
combat_altitude_ft_agl: 200
|
||||||
|
rendezvous_altitude_ft_agl: 1500
|
||||||
|
air_assault_nav_altitude_ft_agl: 1500
|
||||||
|
|
||||||
|
|||||||
@ -1,27 +1,24 @@
|
|||||||
name: modern
|
name: modern
|
||||||
cap: true
|
|
||||||
cas: true
|
|
||||||
sead: true
|
|
||||||
strike: true
|
|
||||||
antiship: true
|
|
||||||
rendezvous_altitude_ft_msl: 25000
|
|
||||||
hold_distance_nm: 25
|
hold_distance_nm: 25
|
||||||
push_distance_nm: 20
|
push_distance_nm: 20
|
||||||
join_distance_nm: 20
|
join_distance_nm: 20
|
||||||
max_ingress_distance_nm: 45
|
max_ingress_distance_nm: 45
|
||||||
min_ingress_distance_nm: 10
|
min_ingress_distance_nm: 10
|
||||||
ingress_altitude_ft_msl: 20000
|
rendezvous_altitude_ft_msl: 25000
|
||||||
min_patrol_altitude_ft_msl: 15000
|
combat_altitude_ft_msl: 20000
|
||||||
max_patrol_altitude_ft_msl: 33000
|
cap:
|
||||||
pattern_altitude_ft_msl: 5000
|
duration_minutes: 30
|
||||||
cap_duration_minutes: 30
|
min_track_length_nm: 15
|
||||||
cap_min_track_length_nm: 15
|
max_track_length_nm: 40
|
||||||
cap_max_track_length_nm: 40
|
min_distance_from_cp_nm: 10
|
||||||
cap_min_distance_from_cp_nm: 10
|
max_distance_from_cp_nm: 40
|
||||||
cap_max_distance_from_cp_nm: 40
|
engagement_range_nm: 50
|
||||||
cap_engagement_range_nm: 50
|
min_patrol_altitude_ft_msl: 15000
|
||||||
cas_duration_minutes: 30
|
max_patrol_altitude_ft_msl: 33000
|
||||||
sweep_distance_nm: 60
|
cas:
|
||||||
|
duration_minutes: 30
|
||||||
|
sweep:
|
||||||
|
distance_nm: 60
|
||||||
ground_unit_procurement_ratios:
|
ground_unit_procurement_ratios:
|
||||||
Tank: 3
|
Tank: 3
|
||||||
ATGM: 2
|
ATGM: 2
|
||||||
@ -30,3 +27,7 @@ ground_unit_procurement_ratios:
|
|||||||
Artillery: 1
|
Artillery: 1
|
||||||
SHORAD: 2
|
SHORAD: 2
|
||||||
Recon: 1
|
Recon: 1
|
||||||
|
helicopter:
|
||||||
|
combat_altitude_ft_agl: 200
|
||||||
|
rendezvous_altitude_ft_agl: 1500
|
||||||
|
air_assault_nav_altitude_ft_agl: 1500
|
||||||
|
|||||||
@ -1,27 +1,24 @@
|
|||||||
name: ww2
|
name: ww2
|
||||||
cap: true
|
|
||||||
cas: true
|
|
||||||
sead: false
|
|
||||||
strike: true
|
|
||||||
antiship: true
|
|
||||||
hold_distance_nm: 10
|
hold_distance_nm: 10
|
||||||
push_distance_nm: 5
|
push_distance_nm: 5
|
||||||
join_distance_nm: 5
|
join_distance_nm: 5
|
||||||
rendezvous_altitude_ft_msl: 10000
|
|
||||||
max_ingress_distance_nm: 7
|
max_ingress_distance_nm: 7
|
||||||
min_ingress_distance_nm: 5
|
min_ingress_distance_nm: 5
|
||||||
ingress_altitude_ft_msl: 8000
|
rendezvous_altitude_ft_msl: 10000
|
||||||
min_patrol_altitude_ft_msl: 4000
|
combat_altitude_ft_msl: 8000
|
||||||
max_patrol_altitude_ft_msl: 15000
|
cap:
|
||||||
pattern_altitude_ft_msl: 5000
|
duration_minutes: 30
|
||||||
cap_duration_minutes: 30
|
min_track_length_nm: 8
|
||||||
cap_min_track_length_nm: 8
|
max_track_length_nm: 18
|
||||||
cap_max_track_length_nm: 18
|
min_distance_from_cp_nm: 0
|
||||||
cap_min_distance_from_cp_nm: 0
|
max_distance_from_cp_nm: 5
|
||||||
cap_max_distance_from_cp_nm: 5
|
engagement_range_nm: 20
|
||||||
cap_engagement_range_nm: 20
|
min_patrol_altitude_ft_msl: 4000
|
||||||
cas_duration_minutes: 30
|
max_patrol_altitude_ft_msl: 15000
|
||||||
sweep_distance_nm: 10
|
cas:
|
||||||
|
duration_minutes: 30
|
||||||
|
sweep:
|
||||||
|
distance_nm: 10
|
||||||
ground_unit_procurement_ratios:
|
ground_unit_procurement_ratios:
|
||||||
Tank: 3
|
Tank: 3
|
||||||
ATGM: 3
|
ATGM: 3
|
||||||
@ -29,3 +26,7 @@ ground_unit_procurement_ratios:
|
|||||||
Artillery: 1
|
Artillery: 1
|
||||||
SHORAD: 3
|
SHORAD: 3
|
||||||
Recon: 1
|
Recon: 1
|
||||||
|
helicopter:
|
||||||
|
combat_altitude_ft_agl: 200
|
||||||
|
rendezvous_altitude_ft_agl: 1500
|
||||||
|
air_assault_nav_altitude_ft_agl: 1500
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user