mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Initial support for Armed Recon flight plan
This commit is contained in:
parent
c24fba0ba4
commit
f405ffdfe2
@ -25,6 +25,7 @@
|
|||||||
* **[Modding]** Added support for Su-15 Flagon mod (v1.0)
|
* **[Modding]** Added support for Su-15 Flagon mod (v1.0)
|
||||||
* **[Plugins]** Support for Carsten's Arty Spotter script
|
* **[Plugins]** Support for Carsten's Arty Spotter script
|
||||||
* **[Modding]** Added support for SK-60 mod (v1.2.1)
|
* **[Modding]** Added support for SK-60 mod (v1.2.1)
|
||||||
|
* **[Mission Generation]** Introducing the Armed Recon flight plan, i.e. CAS against any Theater Ground Object
|
||||||
|
|
||||||
## Fixes
|
## Fixes
|
||||||
* **[UI/UX]** A-10A flights can be edited again
|
* **[UI/UX]** A-10A flights can be edited again
|
||||||
|
|||||||
34
game/ato/flightplans/armedrecon.py
Normal file
34
game/ato/flightplans/armedrecon.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Type
|
||||||
|
|
||||||
|
from .formationattack import (
|
||||||
|
FormationAttackBuilder,
|
||||||
|
FormationAttackFlightPlan,
|
||||||
|
FormationAttackLayout,
|
||||||
|
)
|
||||||
|
from .uizonedisplay import UiZone, UiZoneDisplay
|
||||||
|
from ..flightwaypointtype import FlightWaypointType
|
||||||
|
from ...utils import nautical_miles
|
||||||
|
|
||||||
|
|
||||||
|
class ArmedReconFlightPlan(FormationAttackFlightPlan, UiZoneDisplay):
|
||||||
|
@staticmethod
|
||||||
|
def builder_type() -> Type[Builder]:
|
||||||
|
return Builder
|
||||||
|
|
||||||
|
def ui_zone(self) -> UiZone:
|
||||||
|
return UiZone(
|
||||||
|
[self.tot_waypoint.position],
|
||||||
|
nautical_miles(
|
||||||
|
self.flight.coalition.game.settings.armed_recon_engagement_range_distance
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Builder(FormationAttackBuilder[ArmedReconFlightPlan, FormationAttackLayout]):
|
||||||
|
def layout(self) -> FormationAttackLayout:
|
||||||
|
return self._build(FlightWaypointType.INGRESS_ARMED_RECON)
|
||||||
|
|
||||||
|
def build(self, dump_debug_info: bool = False) -> ArmedReconFlightPlan:
|
||||||
|
return ArmedReconFlightPlan(self.flight, self.layout())
|
||||||
@ -125,16 +125,12 @@ class Builder(IBuilder[CasFlightPlan, CasLayout]):
|
|||||||
ingress_point_shapely.x, ingress_point_shapely.y
|
ingress_point_shapely.x, ingress_point_shapely.y
|
||||||
)
|
)
|
||||||
|
|
||||||
patrol_start_waypoint = builder.nav(
|
patrol_start_waypoint = builder.cas(patrol_start, ingress_egress_altitude)
|
||||||
patrol_start, ingress_egress_altitude, use_agl_patrol_altitude
|
|
||||||
)
|
|
||||||
patrol_start_waypoint.name = "FLOT START"
|
patrol_start_waypoint.name = "FLOT START"
|
||||||
patrol_start_waypoint.pretty_name = "FLOT start"
|
patrol_start_waypoint.pretty_name = "FLOT start"
|
||||||
patrol_start_waypoint.description = "FLOT boundary"
|
patrol_start_waypoint.description = "FLOT boundary"
|
||||||
|
|
||||||
patrol_end_waypoint = builder.nav(
|
patrol_end_waypoint = builder.cas(patrol_end, ingress_egress_altitude)
|
||||||
patrol_end, ingress_egress_altitude, use_agl_patrol_altitude
|
|
||||||
)
|
|
||||||
patrol_end_waypoint.name = "FLOT END"
|
patrol_end_waypoint.name = "FLOT END"
|
||||||
patrol_end_waypoint.pretty_name = "FLOT end"
|
patrol_end_waypoint.pretty_name = "FLOT end"
|
||||||
patrol_end_waypoint.description = "FLOT boundary"
|
patrol_end_waypoint.description = "FLOT boundary"
|
||||||
|
|||||||
@ -7,6 +7,7 @@ from .aewc import AewcFlightPlan
|
|||||||
from .airassault import AirAssaultFlightPlan
|
from .airassault import AirAssaultFlightPlan
|
||||||
from .airlift import AirliftFlightPlan
|
from .airlift import AirliftFlightPlan
|
||||||
from .antiship import AntiShipFlightPlan
|
from .antiship import AntiShipFlightPlan
|
||||||
|
from .armedrecon import ArmedReconFlightPlan
|
||||||
from .bai import BaiFlightPlan
|
from .bai import BaiFlightPlan
|
||||||
from .barcap import BarCapFlightPlan
|
from .barcap import BarCapFlightPlan
|
||||||
from .cas import CasFlightPlan
|
from .cas import CasFlightPlan
|
||||||
@ -60,6 +61,7 @@ class FlightPlanBuilderTypes:
|
|||||||
FlightType.TRANSPORT: AirliftFlightPlan.builder_type(),
|
FlightType.TRANSPORT: AirliftFlightPlan.builder_type(),
|
||||||
FlightType.FERRY: FerryFlightPlan.builder_type(),
|
FlightType.FERRY: FerryFlightPlan.builder_type(),
|
||||||
FlightType.AIR_ASSAULT: AirAssaultFlightPlan.builder_type(),
|
FlightType.AIR_ASSAULT: AirAssaultFlightPlan.builder_type(),
|
||||||
|
FlightType.ARMED_RECON: ArmedReconFlightPlan.builder_type(),
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
return builder_dict[flight.flight_type]
|
return builder_dict[flight.flight_type]
|
||||||
|
|||||||
@ -286,6 +286,8 @@ class FormationAttackBuilder(IBuilder[FlightPlanT, LayoutT], ABC):
|
|||||||
return builder.sead_area(location)
|
return builder.sead_area(location)
|
||||||
elif flight.flight_type == FlightType.OCA_AIRCRAFT:
|
elif flight.flight_type == FlightType.OCA_AIRCRAFT:
|
||||||
return builder.oca_strike_area(location)
|
return builder.oca_strike_area(location)
|
||||||
|
elif flight.flight_type == FlightType.ARMED_RECON:
|
||||||
|
return builder.armed_recon_area(location)
|
||||||
else:
|
else:
|
||||||
return builder.strike_area(location)
|
return builder.strike_area(location)
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import math
|
||||||
import random
|
import random
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import (
|
from typing import (
|
||||||
@ -252,18 +253,9 @@ class WaypointBuilder:
|
|||||||
if ingress_type in [
|
if ingress_type in [
|
||||||
FlightWaypointType.INGRESS_CAS,
|
FlightWaypointType.INGRESS_CAS,
|
||||||
FlightWaypointType.INGRESS_OCA_AIRCRAFT,
|
FlightWaypointType.INGRESS_OCA_AIRCRAFT,
|
||||||
|
FlightWaypointType.INGRESS_ARMED_RECON,
|
||||||
]:
|
]:
|
||||||
weather = self.flight.coalition.game.conditions.weather
|
alt = self._adjust_altitude_for_clouds(alt)
|
||||||
max_alt = feet(30000)
|
|
||||||
if weather.clouds and (
|
|
||||||
weather.clouds.preset
|
|
||||||
and "overcast" in weather.clouds.preset.description.lower()
|
|
||||||
or weather.clouds.density > 5
|
|
||||||
):
|
|
||||||
max_alt = meters(
|
|
||||||
max(feet(500).meters, weather.clouds.base - feet(500).meters)
|
|
||||||
)
|
|
||||||
alt = min(alt, max_alt)
|
|
||||||
|
|
||||||
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:
|
||||||
@ -291,6 +283,19 @@ class WaypointBuilder:
|
|||||||
targets=objective.strike_targets,
|
targets=objective.strike_targets,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _adjust_altitude_for_clouds(self, alt: Distance) -> Distance:
|
||||||
|
weather = self.flight.coalition.game.conditions.weather
|
||||||
|
max_alt = feet(math.inf)
|
||||||
|
if weather.clouds and (
|
||||||
|
weather.clouds.preset
|
||||||
|
and "overcast" in weather.clouds.preset.description.lower()
|
||||||
|
or weather.clouds.density > 5
|
||||||
|
):
|
||||||
|
max_alt = meters(
|
||||||
|
max(feet(500).meters, weather.clouds.base - feet(500).meters)
|
||||||
|
)
|
||||||
|
return min(alt, max_alt)
|
||||||
|
|
||||||
def egress(self, position: Point, target: MissionTarget) -> FlightWaypoint:
|
def egress(self, position: Point, target: MissionTarget) -> FlightWaypoint:
|
||||||
alt_type: AltitudeReference = "BARO"
|
alt_type: AltitudeReference = "BARO"
|
||||||
if self.is_helo or self.get_combat_altitude.feet <= AGL_TRANSITION_ALT:
|
if self.is_helo or self.get_combat_altitude.feet <= AGL_TRANSITION_ALT:
|
||||||
@ -354,6 +359,21 @@ class WaypointBuilder:
|
|||||||
def dead_area(self, target: MissionTarget) -> FlightWaypoint:
|
def dead_area(self, target: MissionTarget) -> FlightWaypoint:
|
||||||
return self._target_area(f"DEAD on {target.name}", target)
|
return self._target_area(f"DEAD on {target.name}", target)
|
||||||
|
|
||||||
|
def armed_recon_area(self, target: MissionTarget) -> FlightWaypoint:
|
||||||
|
# Force AI aircraft to fly towards target area
|
||||||
|
alt = self.get_combat_altitude
|
||||||
|
alt = self._adjust_altitude_for_clouds(alt)
|
||||||
|
alt_type: AltitudeReference = "BARO"
|
||||||
|
if self.is_helo or alt.feet <= AGL_TRANSITION_ALT:
|
||||||
|
alt_type = "RADIO"
|
||||||
|
return self._target_area(
|
||||||
|
f"ARMED RECON {target.name}",
|
||||||
|
target,
|
||||||
|
altitude=alt,
|
||||||
|
alt_type=alt_type,
|
||||||
|
flyover=True,
|
||||||
|
)
|
||||||
|
|
||||||
def oca_strike_area(self, target: MissionTarget) -> FlightWaypoint:
|
def oca_strike_area(self, target: MissionTarget) -> FlightWaypoint:
|
||||||
return self._target_area(f"ATTACK {target.name}", target, flyover=True)
|
return self._target_area(f"ATTACK {target.name}", target, flyover=True)
|
||||||
|
|
||||||
@ -398,15 +418,14 @@ class WaypointBuilder:
|
|||||||
waypoint.only_for_player = True
|
waypoint.only_for_player = True
|
||||||
return waypoint
|
return waypoint
|
||||||
|
|
||||||
def cas(self, position: Point) -> FlightWaypoint:
|
def cas(self, position: Point, altitude: Distance) -> FlightWaypoint:
|
||||||
weather = self.flight.coalition.game.conditions.weather
|
weather = self.flight.coalition.game.conditions.weather
|
||||||
max_alt = feet(30000)
|
|
||||||
if weather.clouds and (
|
if weather.clouds and (
|
||||||
weather.clouds.preset
|
weather.clouds.preset
|
||||||
and "overcast" in weather.clouds.preset.description.lower()
|
and "overcast" in weather.clouds.preset.description.lower()
|
||||||
or weather.clouds.density > 5
|
or weather.clouds.density > 5
|
||||||
):
|
):
|
||||||
max_alt = meters(
|
altitude = meters(
|
||||||
max(feet(500).meters, weather.clouds.base - feet(500).meters)
|
max(feet(500).meters, weather.clouds.base - feet(500).meters)
|
||||||
)
|
)
|
||||||
return FlightWaypoint(
|
return FlightWaypoint(
|
||||||
@ -415,7 +434,7 @@ class WaypointBuilder:
|
|||||||
position,
|
position,
|
||||||
feet(self.flight.coalition.game.settings.heli_combat_alt_agl)
|
feet(self.flight.coalition.game.settings.heli_combat_alt_agl)
|
||||||
if self.is_helo
|
if self.is_helo
|
||||||
else min(meters(1000), max_alt),
|
else max(meters(1000), altitude),
|
||||||
"RADIO",
|
"RADIO",
|
||||||
description="Provide CAS",
|
description="Provide CAS",
|
||||||
pretty_name="CAS",
|
pretty_name="CAS",
|
||||||
@ -667,14 +686,13 @@ class WaypointBuilder:
|
|||||||
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)
|
alt = self.get_combat_altitude if self.flight.is_helo else meters(0)
|
||||||
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,
|
||||||
altitude,
|
alt,
|
||||||
"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",
|
||||||
|
|||||||
@ -58,6 +58,7 @@ class FlightType(Enum):
|
|||||||
FERRY = "Ferry"
|
FERRY = "Ferry"
|
||||||
AIR_ASSAULT = "Air Assault"
|
AIR_ASSAULT = "Air Assault"
|
||||||
SEAD_SWEEP = "SEAD Sweep" # Reintroduce legacy "engage-whatever-you-can-find" SEAD
|
SEAD_SWEEP = "SEAD Sweep" # Reintroduce legacy "engage-whatever-you-can-find" SEAD
|
||||||
|
ARMED_RECON = "Armed Recon"
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.value
|
return self.value
|
||||||
@ -93,6 +94,7 @@ class FlightType(Enum):
|
|||||||
FlightType.SEAD_ESCORT,
|
FlightType.SEAD_ESCORT,
|
||||||
FlightType.AIR_ASSAULT,
|
FlightType.AIR_ASSAULT,
|
||||||
FlightType.SEAD_SWEEP,
|
FlightType.SEAD_SWEEP,
|
||||||
|
FlightType.ARMED_RECON,
|
||||||
}
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -104,6 +106,7 @@ class FlightType(Enum):
|
|||||||
return {
|
return {
|
||||||
FlightType.AEWC: AirEntity.AIRBORNE_EARLY_WARNING,
|
FlightType.AEWC: AirEntity.AIRBORNE_EARLY_WARNING,
|
||||||
FlightType.ANTISHIP: AirEntity.ANTISURFACE_WARFARE,
|
FlightType.ANTISHIP: AirEntity.ANTISURFACE_WARFARE,
|
||||||
|
FlightType.ARMED_RECON: AirEntity.ATTACK_STRIKE,
|
||||||
FlightType.BAI: AirEntity.ATTACK_STRIKE,
|
FlightType.BAI: AirEntity.ATTACK_STRIKE,
|
||||||
FlightType.BARCAP: AirEntity.FIGHTER,
|
FlightType.BARCAP: AirEntity.FIGHTER,
|
||||||
FlightType.CAS: AirEntity.ATTACK_STRIKE,
|
FlightType.CAS: AirEntity.ATTACK_STRIKE,
|
||||||
|
|||||||
@ -51,3 +51,4 @@ class FlightWaypointType(IntEnum):
|
|||||||
INGRESS_AIR_ASSAULT = 31
|
INGRESS_AIR_ASSAULT = 31
|
||||||
INGRESS_ANTI_SHIP = 32
|
INGRESS_ANTI_SHIP = 32
|
||||||
INGRESS_SEAD_SWEEP = 33
|
INGRESS_SEAD_SWEEP = 33
|
||||||
|
INGRESS_ARMED_RECON = 34
|
||||||
|
|||||||
@ -208,6 +208,7 @@ class Loadout:
|
|||||||
loadout_names[FlightType.INTERCEPTION].extend(loadout_names[FlightType.BARCAP])
|
loadout_names[FlightType.INTERCEPTION].extend(loadout_names[FlightType.BARCAP])
|
||||||
# OCA/Aircraft falls back to BAI, which falls back to CAS.
|
# OCA/Aircraft falls back to BAI, which falls back to CAS.
|
||||||
loadout_names[FlightType.BAI].extend(loadout_names[FlightType.CAS])
|
loadout_names[FlightType.BAI].extend(loadout_names[FlightType.CAS])
|
||||||
|
loadout_names[FlightType.ARMED_RECON].extend(loadout_names[FlightType.CAS])
|
||||||
loadout_names[FlightType.OCA_AIRCRAFT].extend(loadout_names[FlightType.BAI])
|
loadout_names[FlightType.OCA_AIRCRAFT].extend(loadout_names[FlightType.BAI])
|
||||||
# DEAD also falls back to BAI.
|
# DEAD also falls back to BAI.
|
||||||
loadout_names[FlightType.DEAD].extend(loadout_names[FlightType.BAI])
|
loadout_names[FlightType.DEAD].extend(loadout_names[FlightType.BAI])
|
||||||
|
|||||||
@ -183,6 +183,7 @@ class Package(RadioFrequencyContainer):
|
|||||||
FlightType.SEAD_SWEEP,
|
FlightType.SEAD_SWEEP,
|
||||||
FlightType.TARCAP,
|
FlightType.TARCAP,
|
||||||
FlightType.BARCAP,
|
FlightType.BARCAP,
|
||||||
|
FlightType.ARMED_RECON,
|
||||||
FlightType.AEWC,
|
FlightType.AEWC,
|
||||||
FlightType.FERRY,
|
FlightType.FERRY,
|
||||||
FlightType.REFUELING,
|
FlightType.REFUELING,
|
||||||
|
|||||||
@ -167,24 +167,6 @@ class ObjectiveFinder:
|
|||||||
yield cp
|
yield cp
|
||||||
break
|
break
|
||||||
|
|
||||||
def vulnerable_enemy_control_points(self) -> Iterator[ControlPoint]:
|
|
||||||
"""Iterates over enemy CPs that are vulnerable to Air Assault.
|
|
||||||
Vulnerability is defined as any unit being alive in the CP's "blocking_capture" groups.
|
|
||||||
"""
|
|
||||||
for cp in self.enemy_control_points():
|
|
||||||
include = True
|
|
||||||
for tgo in cp.connected_objectives:
|
|
||||||
if tgo.distance_to(cp) > cp.CAPTURE_DISTANCE.meters:
|
|
||||||
continue
|
|
||||||
for u in tgo.units:
|
|
||||||
if u.is_vehicle and u.alive:
|
|
||||||
include = False
|
|
||||||
break
|
|
||||||
if not include:
|
|
||||||
break
|
|
||||||
if include:
|
|
||||||
yield cp
|
|
||||||
|
|
||||||
def oca_targets(self, min_aircraft: int) -> Iterator[ControlPoint]:
|
def oca_targets(self, min_aircraft: int) -> Iterator[ControlPoint]:
|
||||||
parking_type = ParkingType()
|
parking_type = ParkingType()
|
||||||
parking_type.include_rotary_wing = True
|
parking_type.include_rotary_wing = True
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import math
|
|||||||
from collections.abc import Iterator
|
from collections.abc import Iterator
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional, TYPE_CHECKING, Union
|
from typing import Optional, TYPE_CHECKING, Union, Dict
|
||||||
|
|
||||||
from game.commander.battlepositions import BattlePositions
|
from game.commander.battlepositions import BattlePositions
|
||||||
from game.commander.objectivefinder import ObjectiveFinder
|
from game.commander.objectivefinder import ObjectiveFinder
|
||||||
@ -163,6 +163,15 @@ class TheaterState(WorldState["TheaterState"]):
|
|||||||
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)
|
||||||
|
|
||||||
|
battle_postitions: Dict[ControlPoint, BattlePositions] = {
|
||||||
|
cp: BattlePositions.for_control_point(cp)
|
||||||
|
for cp in ordered_capturable_points
|
||||||
|
}
|
||||||
|
|
||||||
|
vulnerable_control_points = [
|
||||||
|
cp for cp, bp in battle_postitions.items() if not bp.blocking_capture
|
||||||
|
]
|
||||||
|
|
||||||
return TheaterState(
|
return TheaterState(
|
||||||
context=context,
|
context=context,
|
||||||
barcaps_needed={
|
barcaps_needed={
|
||||||
@ -179,10 +188,7 @@ class TheaterState(WorldState["TheaterState"]):
|
|||||||
enemy_convoys=list(finder.convoys()),
|
enemy_convoys=list(finder.convoys()),
|
||||||
enemy_shipping=list(finder.cargo_ships()),
|
enemy_shipping=list(finder.cargo_ships()),
|
||||||
enemy_ships=list(finder.enemy_ships()),
|
enemy_ships=list(finder.enemy_ships()),
|
||||||
enemy_battle_positions={
|
enemy_battle_positions=battle_postitions,
|
||||||
cp: BattlePositions.for_control_point(cp)
|
|
||||||
for cp in ordered_capturable_points
|
|
||||||
},
|
|
||||||
oca_targets=list(
|
oca_targets=list(
|
||||||
finder.oca_targets(
|
finder.oca_targets(
|
||||||
min_aircraft=game.settings.oca_target_autoplanner_min_aircraft_count
|
min_aircraft=game.settings.oca_target_autoplanner_min_aircraft_count
|
||||||
@ -191,5 +197,5 @@ class TheaterState(WorldState["TheaterState"]):
|
|||||||
strike_targets=list(finder.strike_targets()),
|
strike_targets=list(finder.strike_targets()),
|
||||||
enemy_barcaps=list(game.theater.control_points_for(not player)),
|
enemy_barcaps=list(game.theater.control_points_for(not player)),
|
||||||
threat_zones=game.threat_zone_for(not player),
|
threat_zones=game.threat_zone_for(not player),
|
||||||
vulnerable_control_points=list(finder.vulnerable_enemy_control_points()),
|
vulnerable_control_points=vulnerable_control_points,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -241,9 +241,18 @@ class AircraftType(UnitType[Type[FlyingType]]):
|
|||||||
|
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
enrich = {}
|
enrich = {}
|
||||||
for t in self.task_priorities:
|
if FlightType.SEAD_SWEEP not in self.task_priorities:
|
||||||
if t == FlightType.SEAD:
|
if (value := self.task_priorities.get(FlightType.SEAD)) or (
|
||||||
enrich[FlightType.SEAD_SWEEP] = self.task_priorities[t]
|
value := self.task_priorities.get(FlightType.SEAD_ESCORT)
|
||||||
|
):
|
||||||
|
enrich[FlightType.SEAD_SWEEP] = value
|
||||||
|
|
||||||
|
if FlightType.ARMED_RECON not in self.task_priorities:
|
||||||
|
if (value := self.task_priorities.get(FlightType.CAS)) or (
|
||||||
|
value := self.task_priorities.get(FlightType.BAI)
|
||||||
|
):
|
||||||
|
enrich[FlightType.ARMED_RECON] = value
|
||||||
|
|
||||||
self.task_priorities.update(enrich)
|
self.task_priorities.update(enrich)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -526,17 +535,7 @@ class AircraftType(UnitType[Type[FlyingType]]):
|
|||||||
if prop_overrides is not None:
|
if prop_overrides is not None:
|
||||||
cls._set_props_overrides(prop_overrides, aircraft)
|
cls._set_props_overrides(prop_overrides, aircraft)
|
||||||
|
|
||||||
from game.ato.flighttype import FlightType
|
task_priorities = cls.get_task_priorities(data)
|
||||||
|
|
||||||
task_priorities: dict[FlightType, int] = {}
|
|
||||||
for task_name, priority in data.get("tasks", {}).items():
|
|
||||||
task_priorities[FlightType(task_name)] = priority
|
|
||||||
|
|
||||||
if (
|
|
||||||
FlightType.SEAD_SWEEP not in task_priorities
|
|
||||||
and FlightType.SEAD in task_priorities
|
|
||||||
):
|
|
||||||
task_priorities[FlightType.SEAD_SWEEP] = task_priorities[FlightType.SEAD]
|
|
||||||
|
|
||||||
cls._custom_weapon_injections(aircraft, data)
|
cls._custom_weapon_injections(aircraft, data)
|
||||||
cls._user_weapon_injections(aircraft)
|
cls._user_weapon_injections(aircraft)
|
||||||
@ -583,6 +582,27 @@ class AircraftType(UnitType[Type[FlyingType]]):
|
|||||||
use_f15e_waypoint_names=data.get("use_f15e_waypoint_names", False),
|
use_f15e_waypoint_names=data.get("use_f15e_waypoint_names", False),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_task_priorities(cls, data: dict[str, Any]) -> dict[FlightType, int]:
|
||||||
|
task_priorities: dict[FlightType, int] = {}
|
||||||
|
for task_name, priority in data.get("tasks", {}).items():
|
||||||
|
task_priorities[FlightType(task_name)] = priority
|
||||||
|
if (
|
||||||
|
FlightType.SEAD_SWEEP not in task_priorities
|
||||||
|
and FlightType.SEAD in task_priorities
|
||||||
|
):
|
||||||
|
task_priorities[FlightType.SEAD_SWEEP] = task_priorities[FlightType.SEAD]
|
||||||
|
if FlightType.ARMED_RECON not in task_priorities:
|
||||||
|
if FlightType.CAS in task_priorities:
|
||||||
|
task_priorities[FlightType.ARMED_RECON] = task_priorities[
|
||||||
|
FlightType.CAS
|
||||||
|
]
|
||||||
|
elif FlightType.BAI in task_priorities:
|
||||||
|
task_priorities[FlightType.ARMED_RECON] = task_priorities[
|
||||||
|
FlightType.BAI
|
||||||
|
]
|
||||||
|
return task_priorities
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _custom_weapon_injections(
|
def _custom_weapon_injections(
|
||||||
aircraft: Type[FlyingType], data: Dict[str, Any]
|
aircraft: Type[FlyingType], data: Dict[str, Any]
|
||||||
|
|||||||
@ -57,6 +57,8 @@ class AircraftBehavior:
|
|||||||
self.configure_refueling(group, flight)
|
self.configure_refueling(group, flight)
|
||||||
elif self.task in [FlightType.CAS, FlightType.BAI]:
|
elif self.task in [FlightType.CAS, FlightType.BAI]:
|
||||||
self.configure_cas(group, flight)
|
self.configure_cas(group, flight)
|
||||||
|
elif self.task == FlightType.ARMED_RECON:
|
||||||
|
self.configure_armed_recon(group, flight)
|
||||||
elif self.task == FlightType.DEAD:
|
elif self.task == FlightType.DEAD:
|
||||||
self.configure_dead(group, flight)
|
self.configure_dead(group, flight)
|
||||||
elif self.task in [FlightType.SEAD, FlightType.SEAD_SWEEP]:
|
elif self.task in [FlightType.SEAD, FlightType.SEAD_SWEEP]:
|
||||||
@ -183,6 +185,17 @@ class AircraftBehavior:
|
|||||||
restrict_jettison=True,
|
restrict_jettison=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def configure_armed_recon(self, group: FlyingGroup[Any], flight: Flight) -> None:
|
||||||
|
self.configure_task(flight, group, CAS, [AFAC, AntishipStrike])
|
||||||
|
self.configure_behavior(
|
||||||
|
flight,
|
||||||
|
group,
|
||||||
|
react_on_threat=OptReactOnThreat.Values.EvadeFire,
|
||||||
|
roe=OptROE.Values.OpenFire,
|
||||||
|
rtb_winchester=OptRTBOnOutOfAmmo.Values.All,
|
||||||
|
restrict_jettison=True,
|
||||||
|
)
|
||||||
|
|
||||||
def configure_dead(self, group: FlyingGroup[Any], flight: Flight) -> None:
|
def configure_dead(self, group: FlyingGroup[Any], flight: Flight) -> None:
|
||||||
# Only CAS and SEAD are capable of the Attack Group task. SEAD is arguably more
|
# Only CAS and SEAD are capable of the Attack Group task. SEAD is arguably more
|
||||||
# appropriate but it has an extremely limited list of capable aircraft, whereas
|
# appropriate but it has an extremely limited list of capable aircraft, whereas
|
||||||
|
|||||||
@ -0,0 +1,35 @@
|
|||||||
|
from dcs.point import MovingPoint
|
||||||
|
from dcs.task import (
|
||||||
|
OptECMUsing,
|
||||||
|
ControlledTask,
|
||||||
|
Targets,
|
||||||
|
EngageTargetsInZone,
|
||||||
|
)
|
||||||
|
|
||||||
|
from game.utils import nautical_miles
|
||||||
|
from .pydcswaypointbuilder import PydcsWaypointBuilder
|
||||||
|
|
||||||
|
|
||||||
|
class ArmedReconIngressBuilder(PydcsWaypointBuilder):
|
||||||
|
def add_tasks(self, waypoint: MovingPoint) -> None:
|
||||||
|
self.register_special_ingress_points()
|
||||||
|
# Preemptively use ECM to better avoid getting swatted.
|
||||||
|
ecm_option = OptECMUsing(value=OptECMUsing.Values.UseIfDetectedLockByRadar)
|
||||||
|
waypoint.tasks.append(ecm_option)
|
||||||
|
|
||||||
|
waypoint.add_task(
|
||||||
|
ControlledTask(
|
||||||
|
EngageTargetsInZone(
|
||||||
|
position=self.flight.flight_plan.tot_waypoint.position,
|
||||||
|
radius=int(
|
||||||
|
nautical_miles(
|
||||||
|
self.flight.coalition.game.settings.armed_recon_engagement_range_distance
|
||||||
|
).meters
|
||||||
|
),
|
||||||
|
targets=[
|
||||||
|
Targets.All.GroundUnits,
|
||||||
|
Targets.All.Air.Helicopters,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
@ -22,6 +22,7 @@ from game.settings import Settings
|
|||||||
from game.utils import pairwise
|
from game.utils import pairwise
|
||||||
from .airassaultingress import AirAssaultIngressBuilder
|
from .airassaultingress import AirAssaultIngressBuilder
|
||||||
from .antishipingress import AntiShipIngressBuilder
|
from .antishipingress import AntiShipIngressBuilder
|
||||||
|
from .armedreconingress import ArmedReconIngressBuilder
|
||||||
from .baiingress import BaiIngressBuilder
|
from .baiingress import BaiIngressBuilder
|
||||||
from .casingress import CasIngressBuilder
|
from .casingress import CasIngressBuilder
|
||||||
from .deadingress import DeadIngressBuilder
|
from .deadingress import DeadIngressBuilder
|
||||||
@ -136,6 +137,7 @@ class WaypointGenerator:
|
|||||||
FlightWaypointType.DROPOFF_ZONE: LandingZoneBuilder,
|
FlightWaypointType.DROPOFF_ZONE: LandingZoneBuilder,
|
||||||
FlightWaypointType.INGRESS_AIR_ASSAULT: AirAssaultIngressBuilder,
|
FlightWaypointType.INGRESS_AIR_ASSAULT: AirAssaultIngressBuilder,
|
||||||
FlightWaypointType.INGRESS_ANTI_SHIP: AntiShipIngressBuilder,
|
FlightWaypointType.INGRESS_ANTI_SHIP: AntiShipIngressBuilder,
|
||||||
|
FlightWaypointType.INGRESS_ARMED_RECON: ArmedReconIngressBuilder,
|
||||||
FlightWaypointType.INGRESS_BAI: BaiIngressBuilder,
|
FlightWaypointType.INGRESS_BAI: BaiIngressBuilder,
|
||||||
FlightWaypointType.INGRESS_CAS: CasIngressBuilder,
|
FlightWaypointType.INGRESS_CAS: CasIngressBuilder,
|
||||||
FlightWaypointType.INGRESS_DEAD: DeadIngressBuilder,
|
FlightWaypointType.INGRESS_DEAD: DeadIngressBuilder,
|
||||||
|
|||||||
@ -329,6 +329,14 @@ class Settings:
|
|||||||
min=0,
|
min=0,
|
||||||
max=100,
|
max=100,
|
||||||
)
|
)
|
||||||
|
armed_recon_engagement_range_distance: int = bounded_int_option(
|
||||||
|
"Armed Recon engagement range (NM)",
|
||||||
|
page=CAMPAIGN_DOCTRINE_PAGE,
|
||||||
|
section=DOCTRINE_DISTANCES_SECTION,
|
||||||
|
default=5,
|
||||||
|
min=0,
|
||||||
|
max=25,
|
||||||
|
)
|
||||||
sead_sweep_engagement_range_distance: int = bounded_int_option(
|
sead_sweep_engagement_range_distance: int = bounded_int_option(
|
||||||
"SEAD Sweep engagement range (NM)",
|
"SEAD Sweep engagement range (NM)",
|
||||||
page=CAMPAIGN_DOCTRINE_PAGE,
|
page=CAMPAIGN_DOCTRINE_PAGE,
|
||||||
|
|||||||
@ -39,6 +39,7 @@ class MissionTarget:
|
|||||||
FlightType.TARCAP,
|
FlightType.TARCAP,
|
||||||
FlightType.SEAD_ESCORT,
|
FlightType.SEAD_ESCORT,
|
||||||
FlightType.SEAD_SWEEP,
|
FlightType.SEAD_SWEEP,
|
||||||
|
FlightType.ARMED_RECON,
|
||||||
FlightType.SWEEP,
|
FlightType.SWEEP,
|
||||||
# TODO: FlightType.ELINT,
|
# TODO: FlightType.ELINT,
|
||||||
# TODO: FlightType.EWAR,
|
# TODO: FlightType.EWAR,
|
||||||
|
|||||||
@ -161,6 +161,7 @@ class QAutoCreateDialog(QDialog):
|
|||||||
FlightType.ANTISHIP,
|
FlightType.ANTISHIP,
|
||||||
FlightType.BAI,
|
FlightType.BAI,
|
||||||
FlightType.CAS,
|
FlightType.CAS,
|
||||||
|
FlightType.ARMED_RECON,
|
||||||
}
|
}
|
||||||
for mt in self.package.target.mission_types(self.is_ownfor):
|
for mt in self.package.target.mission_types(self.is_ownfor):
|
||||||
if mt in primary_tasks:
|
if mt in primary_tasks:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user