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:
zhexu14 2024-01-02 08:31:26 +11:00 committed by GitHub
parent c695e7724a
commit 5b858886c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 213 additions and 163 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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] = []

View File

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

View File

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

View File

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

View File

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

View File

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