mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Initial implementation of AEW&C missions.
Still a work in progress (the missions don't actually perform their task, just orbit). Currently: * AEW&C aircraft can be bought. * AEW&C missions can be planned at any control point and at front lines. * AEW&C will return after 4H or Bingo.
This commit is contained in:
parent
07ce20fd29
commit
a004f62fe8
@ -4,6 +4,8 @@ Saves from 2.4 are not compatible with 2.5.
|
|||||||
|
|
||||||
## Features/Improvements
|
## Features/Improvements
|
||||||
|
|
||||||
|
* **[Flight Planner]** (WIP) Added AEW&C missions.
|
||||||
|
|
||||||
## Fixes
|
## Fixes
|
||||||
|
|
||||||
# 2.4.0
|
# 2.4.0
|
||||||
|
|||||||
@ -1117,7 +1117,8 @@ COMMON_OVERRIDE = {
|
|||||||
GroundAttack: "STRIKE",
|
GroundAttack: "STRIKE",
|
||||||
Escort: "CAP",
|
Escort: "CAP",
|
||||||
RunwayAttack: "RUNWAY_ATTACK",
|
RunwayAttack: "RUNWAY_ATTACK",
|
||||||
FighterSweep: "CAP"
|
FighterSweep: "CAP",
|
||||||
|
AWACS: "AEW&C",
|
||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@ -1328,6 +1329,7 @@ CARRIER_CAPABLE = [
|
|||||||
A_4E_C,
|
A_4E_C,
|
||||||
Rafale_M,
|
Rafale_M,
|
||||||
S_3B,
|
S_3B,
|
||||||
|
E_2C,
|
||||||
|
|
||||||
UH_1H,
|
UH_1H,
|
||||||
Mi_8MT,
|
Mi_8MT,
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import math
|
|||||||
import typing
|
import typing
|
||||||
from typing import Dict, Type
|
from typing import Dict, Type
|
||||||
|
|
||||||
from dcs.task import CAP, CAS, Embarking, PinpointStrike, Task
|
from dcs.task import AWACS, CAP, CAS, Embarking, PinpointStrike, Task
|
||||||
from dcs.unittype import FlyingType, UnitType, VehicleType
|
from dcs.unittype import FlyingType, UnitType, VehicleType
|
||||||
from dcs.vehicles import AirDefence, Armor
|
from dcs.vehicles import AirDefence, Armor
|
||||||
|
|
||||||
@ -122,7 +122,7 @@ class Base:
|
|||||||
for_task = db.unit_task(unit_type)
|
for_task = db.unit_task(unit_type)
|
||||||
|
|
||||||
target_dict = None
|
target_dict = None
|
||||||
if for_task == CAS or for_task == CAP or for_task == Embarking:
|
if for_task == AWACS or for_task == CAS or for_task == CAP or for_task == Embarking:
|
||||||
target_dict = self.aircraft
|
target_dict = self.aircraft
|
||||||
elif for_task == PinpointStrike:
|
elif for_task == PinpointStrike:
|
||||||
target_dict = self.armor
|
target_dict = self.armor
|
||||||
|
|||||||
@ -812,6 +812,7 @@ class FrontLine(MissionTarget):
|
|||||||
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
||||||
yield from [
|
yield from [
|
||||||
FlightType.CAS,
|
FlightType.CAS,
|
||||||
|
FlightType.AEWC,
|
||||||
# TODO: FlightType.TROOP_TRANSPORT
|
# TODO: FlightType.TROOP_TRANSPORT
|
||||||
# TODO: FlightType.EVAC
|
# TODO: FlightType.EVAC
|
||||||
]
|
]
|
||||||
|
|||||||
@ -603,6 +603,14 @@ class ControlPoint(MissionTarget, ABC):
|
|||||||
def income_per_turn(self) -> int:
|
def income_per_turn(self) -> int:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
||||||
|
from gen.flights.flight import FlightType
|
||||||
|
if self.is_friendly(for_player):
|
||||||
|
yield from [
|
||||||
|
FlightType.AEWC,
|
||||||
|
]
|
||||||
|
yield from super().mission_types(for_player)
|
||||||
|
|
||||||
|
|
||||||
class Airfield(ControlPoint):
|
class Airfield(ControlPoint):
|
||||||
|
|
||||||
|
|||||||
@ -41,6 +41,7 @@ from dcs.planes import (
|
|||||||
)
|
)
|
||||||
from dcs.point import MovingPoint, PointAction
|
from dcs.point import MovingPoint, PointAction
|
||||||
from dcs.task import (
|
from dcs.task import (
|
||||||
|
AWACS,
|
||||||
AntishipStrike,
|
AntishipStrike,
|
||||||
AttackGroup,
|
AttackGroup,
|
||||||
Bombing,
|
Bombing,
|
||||||
@ -90,7 +91,6 @@ from game.utils import Distance, meters, nautical_miles
|
|||||||
from gen.airsupportgen import AirSupport
|
from gen.airsupportgen import AirSupport
|
||||||
from gen.ato import AirTaskingOrder, Package
|
from gen.ato import AirTaskingOrder, Package
|
||||||
from gen.callsigns import create_group_callsign_from_unit
|
from gen.callsigns import create_group_callsign_from_unit
|
||||||
from gen.conflictgen import FRONTLINE_LENGTH
|
|
||||||
from gen.flights.flight import (
|
from gen.flights.flight import (
|
||||||
Flight,
|
Flight,
|
||||||
FlightType,
|
FlightType,
|
||||||
@ -132,7 +132,8 @@ TARGET_WAYPOINTS = (
|
|||||||
FlightWaypointType.TARGET_GROUP_LOC,
|
FlightWaypointType.TARGET_GROUP_LOC,
|
||||||
FlightWaypointType.TARGET_POINT,
|
FlightWaypointType.TARGET_POINT,
|
||||||
FlightWaypointType.TARGET_SHIP,
|
FlightWaypointType.TARGET_SHIP,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# TODO: Get radio information for all the special cases.
|
# TODO: Get radio information for all the special cases.
|
||||||
def get_fallback_channel(unit_type: UnitType) -> RadioFrequency:
|
def get_fallback_channel(unit_type: UnitType) -> RadioFrequency:
|
||||||
@ -731,11 +732,14 @@ class AircraftConflictGenerator:
|
|||||||
group.load_loadout(payload_name)
|
group.load_loadout(payload_name)
|
||||||
if not group.units[0].pylons and for_task == RunwayAttack:
|
if not group.units[0].pylons and for_task == RunwayAttack:
|
||||||
if PinpointStrike in db.PLANE_PAYLOAD_OVERRIDES[unit_type]:
|
if PinpointStrike in db.PLANE_PAYLOAD_OVERRIDES[unit_type]:
|
||||||
logging.warning("No loadout for \"Runway Attack\" for the {}, defaulting to Strike loadout".format(str(unit_type)))
|
logging.warning(
|
||||||
|
"No loadout for \"Runway Attack\" for the {}, defaulting to Strike loadout".format(
|
||||||
|
str(unit_type)))
|
||||||
payload_name = db.PLANE_PAYLOAD_OVERRIDES[unit_type][PinpointStrike]
|
payload_name = db.PLANE_PAYLOAD_OVERRIDES[unit_type][PinpointStrike]
|
||||||
group.load_loadout(payload_name)
|
group.load_loadout(payload_name)
|
||||||
did_load_loadout = True
|
did_load_loadout = True
|
||||||
logging.info("Loaded overridden payload for {} - {} for task {}".format(unit_type, payload_name, for_task))
|
logging.info(
|
||||||
|
"Loaded overridden payload for {} - {} for task {}".format(unit_type, payload_name, for_task))
|
||||||
|
|
||||||
if not did_load_loadout:
|
if not did_load_loadout:
|
||||||
group.load_task_default_loadout(for_task)
|
group.load_task_default_loadout(for_task)
|
||||||
@ -1058,7 +1062,7 @@ class AircraftConflictGenerator:
|
|||||||
trigger.add_condition(
|
trigger.add_condition(
|
||||||
CoalitionHasAirdrome(coalition, flight.from_cp.id))
|
CoalitionHasAirdrome(coalition, flight.from_cp.id))
|
||||||
|
|
||||||
def generate_planned_flight(self, cp, country, flight:Flight):
|
def generate_planned_flight(self, cp, country, flight: Flight):
|
||||||
name = namegen.next_aircraft_name(country, cp.id, flight)
|
name = namegen.next_aircraft_name(country, cp.id, flight)
|
||||||
try:
|
try:
|
||||||
if flight.start_type == "In Flight":
|
if flight.start_type == "In Flight":
|
||||||
@ -1249,6 +1253,17 @@ class AircraftConflictGenerator:
|
|||||||
roe=OptROE.Values.OpenFire,
|
roe=OptROE.Values.OpenFire,
|
||||||
restrict_jettison=True)
|
restrict_jettison=True)
|
||||||
|
|
||||||
|
def configure_awacs(
|
||||||
|
self, group: FlyingGroup, package: Package, flight: Flight,
|
||||||
|
dynamic_runways: Dict[str, RunwayData]) -> None:
|
||||||
|
group.task = AWACS.name
|
||||||
|
self._setup_group(group, AWACS, package, flight, dynamic_runways)
|
||||||
|
self.configure_behavior(
|
||||||
|
group,
|
||||||
|
react_on_threat=OptReactOnThreat.Values.EvadeFire,
|
||||||
|
roe=OptROE.Values.WeaponHold,
|
||||||
|
restrict_jettison=True)
|
||||||
|
|
||||||
def configure_escort(self, group: FlyingGroup, package: Package,
|
def configure_escort(self, group: FlyingGroup, package: Package,
|
||||||
flight: Flight,
|
flight: Flight,
|
||||||
dynamic_runways: Dict[str, RunwayData]) -> None:
|
dynamic_runways: Dict[str, RunwayData]) -> None:
|
||||||
@ -1274,6 +1289,8 @@ class AircraftConflictGenerator:
|
|||||||
self.configure_cap(group, package, flight, dynamic_runways)
|
self.configure_cap(group, package, flight, dynamic_runways)
|
||||||
elif flight_type == FlightType.SWEEP:
|
elif flight_type == FlightType.SWEEP:
|
||||||
self.configure_sweep(group, package, flight, dynamic_runways)
|
self.configure_sweep(group, package, flight, dynamic_runways)
|
||||||
|
elif flight_type == FlightType.AEWC:
|
||||||
|
self.configure_awacs(group, package, flight, dynamic_runways)
|
||||||
elif flight_type in [FlightType.CAS, FlightType.BAI]:
|
elif flight_type in [FlightType.CAS, FlightType.BAI]:
|
||||||
self.configure_cas(group, package, flight, dynamic_runways)
|
self.configure_cas(group, package, flight, dynamic_runways)
|
||||||
elif flight_type == FlightType.DEAD:
|
elif flight_type == FlightType.DEAD:
|
||||||
|
|||||||
@ -174,6 +174,7 @@ class Package:
|
|||||||
FlightType.TARCAP,
|
FlightType.TARCAP,
|
||||||
FlightType.BARCAP,
|
FlightType.BARCAP,
|
||||||
FlightType.SWEEP,
|
FlightType.SWEEP,
|
||||||
|
FlightType.AEWC,
|
||||||
FlightType.ESCORT,
|
FlightType.ESCORT,
|
||||||
]
|
]
|
||||||
for task in task_priorities:
|
for task in task_priorities:
|
||||||
|
|||||||
@ -12,8 +12,8 @@ from dcs.helicopters import (
|
|||||||
OH_58D,
|
OH_58D,
|
||||||
SA342L,
|
SA342L,
|
||||||
SA342M,
|
SA342M,
|
||||||
|
SH_60B,
|
||||||
UH_1H,
|
UH_1H,
|
||||||
SH_60B
|
|
||||||
)
|
)
|
||||||
from dcs.planes import (
|
from dcs.planes import (
|
||||||
AJS37,
|
AJS37,
|
||||||
@ -22,11 +22,14 @@ from dcs.planes import (
|
|||||||
A_10C,
|
A_10C,
|
||||||
A_10C_2,
|
A_10C_2,
|
||||||
A_20G,
|
A_20G,
|
||||||
|
A_50,
|
||||||
B_17G,
|
B_17G,
|
||||||
B_1B,
|
B_1B,
|
||||||
B_52H,
|
B_52H,
|
||||||
Bf_109K_4,
|
Bf_109K_4,
|
||||||
C_101CC,
|
C_101CC,
|
||||||
|
E_2C,
|
||||||
|
E_3A,
|
||||||
FA_18C_hornet,
|
FA_18C_hornet,
|
||||||
FW_190A8,
|
FW_190A8,
|
||||||
FW_190D9,
|
FW_190D9,
|
||||||
@ -40,9 +43,11 @@ from dcs.planes import (
|
|||||||
F_4E,
|
F_4E,
|
||||||
F_5E_3,
|
F_5E_3,
|
||||||
F_86F_Sabre,
|
F_86F_Sabre,
|
||||||
|
I_16,
|
||||||
JF_17,
|
JF_17,
|
||||||
J_11A,
|
J_11A,
|
||||||
Ju_88A4,
|
Ju_88A4,
|
||||||
|
KJ_2000,
|
||||||
L_39ZA,
|
L_39ZA,
|
||||||
MQ_9_Reaper,
|
MQ_9_Reaper,
|
||||||
M_2000C,
|
M_2000C,
|
||||||
@ -83,18 +88,16 @@ from dcs.planes import (
|
|||||||
Tu_22M3,
|
Tu_22M3,
|
||||||
Tu_95MS,
|
Tu_95MS,
|
||||||
WingLoong_I,
|
WingLoong_I,
|
||||||
I_16
|
|
||||||
)
|
)
|
||||||
from dcs.unittype import FlyingType
|
from dcs.unittype import FlyingType
|
||||||
|
|
||||||
from gen.flights.flight import FlightType
|
from gen.flights.flight import FlightType
|
||||||
|
|
||||||
from pydcs_extensions.a4ec.a4ec import A_4E_C
|
from pydcs_extensions.a4ec.a4ec import A_4E_C
|
||||||
from pydcs_extensions.f22a.f22a import F_22A
|
from pydcs_extensions.f22a.f22a import F_22A
|
||||||
from pydcs_extensions.mb339.mb339 import MB_339PAN
|
|
||||||
from pydcs_extensions.rafale.rafale import Rafale_A_S, Rafale_M, Rafale_B
|
|
||||||
from pydcs_extensions.su57.su57 import Su_57
|
|
||||||
from pydcs_extensions.hercules.hercules import Hercules
|
from pydcs_extensions.hercules.hercules import Hercules
|
||||||
|
from pydcs_extensions.mb339.mb339 import MB_339PAN
|
||||||
|
from pydcs_extensions.rafale.rafale import Rafale_A_S, Rafale_B, Rafale_M
|
||||||
|
from pydcs_extensions.su57.su57 import Su_57
|
||||||
|
|
||||||
# All aircraft lists are in priority order. Aircraft higher in the list will be
|
# All aircraft lists are in priority order. Aircraft higher in the list will be
|
||||||
# preferred over those lower in the list.
|
# preferred over those lower in the list.
|
||||||
@ -155,8 +158,8 @@ CAP_CAPABLE = [
|
|||||||
# Used for CAS (Close air support) and BAI (Battlefield Interdiction)
|
# Used for CAS (Close air support) and BAI (Battlefield Interdiction)
|
||||||
CAS_CAPABLE = [
|
CAS_CAPABLE = [
|
||||||
A_10C_2,
|
A_10C_2,
|
||||||
A_10C,
|
|
||||||
B_1B,
|
B_1B,
|
||||||
|
A_10C,
|
||||||
F_14B,
|
F_14B,
|
||||||
F_14A_135_GR,
|
F_14A_135_GR,
|
||||||
Su_25TM,
|
Su_25TM,
|
||||||
@ -373,6 +376,13 @@ DRONES = [
|
|||||||
WingLoong_I
|
WingLoong_I
|
||||||
]
|
]
|
||||||
|
|
||||||
|
AEWC_CAPABLE = [
|
||||||
|
E_3A,
|
||||||
|
E_2C,
|
||||||
|
A_50,
|
||||||
|
KJ_2000,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def aircraft_for_task(task: FlightType) -> List[Type[FlyingType]]:
|
def aircraft_for_task(task: FlightType) -> List[Type[FlyingType]]:
|
||||||
cap_missions = (FlightType.BARCAP, FlightType.TARCAP)
|
cap_missions = (FlightType.BARCAP, FlightType.TARCAP)
|
||||||
@ -396,6 +406,8 @@ def aircraft_for_task(task: FlightType) -> List[Type[FlyingType]]:
|
|||||||
return STRIKE_CAPABLE
|
return STRIKE_CAPABLE
|
||||||
elif task == FlightType.ESCORT:
|
elif task == FlightType.ESCORT:
|
||||||
return CAP_CAPABLE
|
return CAP_CAPABLE
|
||||||
|
elif task == FlightType.AEWC:
|
||||||
|
return AEWC_CAPABLE
|
||||||
else:
|
else:
|
||||||
logging.error(f"Unplannable flight type: {task}")
|
logging.error(f"Unplannable flight type: {task}")
|
||||||
return []
|
return []
|
||||||
|
|||||||
@ -20,6 +20,14 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class FlightType(Enum):
|
class FlightType(Enum):
|
||||||
|
"""Enumeration of mission types.
|
||||||
|
|
||||||
|
The value of each enumeration is the name that will be shown in the UI.
|
||||||
|
|
||||||
|
These values are persisted to the save game as well since they are a part of
|
||||||
|
each flight and thus a part of the ATO, so changing these values will break
|
||||||
|
save compat.
|
||||||
|
"""
|
||||||
TARCAP = "TARCAP"
|
TARCAP = "TARCAP"
|
||||||
BARCAP = "BARCAP"
|
BARCAP = "BARCAP"
|
||||||
CAS = "CAS"
|
CAS = "CAS"
|
||||||
@ -33,6 +41,7 @@ class FlightType(Enum):
|
|||||||
SWEEP = "Fighter sweep"
|
SWEEP = "Fighter sweep"
|
||||||
OCA_RUNWAY = "OCA/Runway"
|
OCA_RUNWAY = "OCA/Runway"
|
||||||
OCA_AIRCRAFT = "OCA/Aircraft"
|
OCA_AIRCRAFT = "OCA/Aircraft"
|
||||||
|
AEWC = "AEW&C"
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.value
|
return self.value
|
||||||
|
|||||||
@ -15,6 +15,13 @@ from datetime import timedelta
|
|||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
from typing import Iterator, List, Optional, Set, TYPE_CHECKING, Tuple
|
from typing import Iterator, List, Optional, Set, TYPE_CHECKING, Tuple
|
||||||
|
|
||||||
|
from dcs.planes import (
|
||||||
|
E_3A,
|
||||||
|
E_2C,
|
||||||
|
A_50,
|
||||||
|
KJ_2000
|
||||||
|
)
|
||||||
|
|
||||||
from dcs.mapping import Point
|
from dcs.mapping import Point
|
||||||
from dcs.unit import Unit
|
from dcs.unit import Unit
|
||||||
from shapely.geometry import Point as ShapelyPoint
|
from shapely.geometry import Point as ShapelyPoint
|
||||||
@ -29,7 +36,7 @@ from game.theater import (
|
|||||||
TheaterGroundObject,
|
TheaterGroundObject,
|
||||||
)
|
)
|
||||||
from game.theater.theatergroundobject import EwrGroundObject
|
from game.theater.theatergroundobject import EwrGroundObject
|
||||||
from game.utils import Distance, Speed, meters, nautical_miles
|
from game.utils import Distance, Speed, feet, meters, nautical_miles
|
||||||
from .closestairfields import ObjectiveDistanceCache
|
from .closestairfields import ObjectiveDistanceCache
|
||||||
from .flight import Flight, FlightType, FlightWaypoint, FlightWaypointType
|
from .flight import Flight, FlightType, FlightWaypoint, FlightWaypointType
|
||||||
from .traveltime import GroundSpeed, TravelTime
|
from .traveltime import GroundSpeed, TravelTime
|
||||||
@ -280,11 +287,11 @@ class LoiterFlightPlan(FlightPlan):
|
|||||||
travel_time = super().travel_time_between_waypoints(a, b)
|
travel_time = super().travel_time_between_waypoints(a, b)
|
||||||
if a != self.hold:
|
if a != self.hold:
|
||||||
return travel_time
|
return travel_time
|
||||||
try:
|
|
||||||
return travel_time + self.hold_duration
|
return travel_time + self.hold_duration
|
||||||
except AttributeError:
|
|
||||||
# Save compat for 2.3.
|
@property
|
||||||
return travel_time + timedelta(minutes=5)
|
def mission_departure_time(self) -> timedelta:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@ -696,6 +703,41 @@ class SweepFlightPlan(LoiterFlightPlan):
|
|||||||
return self.sweep_end_time
|
return self.sweep_end_time
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class AwacsFlightPlan(LoiterFlightPlan):
|
||||||
|
takeoff: FlightWaypoint
|
||||||
|
nav_to: List[FlightWaypoint]
|
||||||
|
nav_from: List[FlightWaypoint]
|
||||||
|
land: FlightWaypoint
|
||||||
|
divert: Optional[FlightWaypoint]
|
||||||
|
|
||||||
|
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
||||||
|
yield self.takeoff
|
||||||
|
yield from self.nav_to
|
||||||
|
yield self.hold
|
||||||
|
yield from self.nav_from
|
||||||
|
yield self.land
|
||||||
|
if self.divert is not None:
|
||||||
|
yield self.divert
|
||||||
|
|
||||||
|
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||||
|
if waypoint == self.hold:
|
||||||
|
return self.package.time_over_target
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tot_waypoint(self) -> Optional[FlightWaypoint]:
|
||||||
|
return self.hold
|
||||||
|
|
||||||
|
@property
|
||||||
|
def push_time(self) -> timedelta:
|
||||||
|
return self.package.time_over_target + self.hold_duration
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mission_departure_time(self) -> timedelta:
|
||||||
|
return self.push_time
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class CustomFlightPlan(FlightPlan):
|
class CustomFlightPlan(FlightPlan):
|
||||||
custom_waypoints: List[FlightWaypoint]
|
custom_waypoints: List[FlightWaypoint]
|
||||||
@ -791,6 +833,8 @@ class FlightPlanBuilder:
|
|||||||
return self.generate_sweep(flight)
|
return self.generate_sweep(flight)
|
||||||
elif task == FlightType.TARCAP:
|
elif task == FlightType.TARCAP:
|
||||||
return self.generate_tarcap(flight)
|
return self.generate_tarcap(flight)
|
||||||
|
elif task == FlightType.AEWC:
|
||||||
|
return self.generate_aewc(flight)
|
||||||
raise PlanningError(
|
raise PlanningError(
|
||||||
f"{task} flight plan generation not implemented")
|
f"{task} flight plan generation not implemented")
|
||||||
|
|
||||||
@ -921,6 +965,45 @@ class FlightPlanBuilder:
|
|||||||
FlightWaypointType.INGRESS_STRIKE,
|
FlightWaypointType.INGRESS_STRIKE,
|
||||||
targets)
|
targets)
|
||||||
|
|
||||||
|
def generate_aewc(self, flight: Flight) -> AwacsFlightPlan:
|
||||||
|
"""Generate a AWACS flight at a given location.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
flight: The flight to generate the flight plan for.
|
||||||
|
"""
|
||||||
|
location = self.package.target
|
||||||
|
|
||||||
|
start = self.aewc_orbit(location)
|
||||||
|
|
||||||
|
# As high as possible to maximize detection and on-station time.
|
||||||
|
if flight.unit_type == E_2C:
|
||||||
|
patrol_alt = feet(30000)
|
||||||
|
elif flight.unit_type == E_3A:
|
||||||
|
patrol_alt = feet(35000)
|
||||||
|
elif flight.unit_type == A_50:
|
||||||
|
patrol_alt = feet(33000)
|
||||||
|
elif flight.unit_type == KJ_2000:
|
||||||
|
patrol_alt = feet(40000)
|
||||||
|
else:
|
||||||
|
patrol_alt = feet(25000)
|
||||||
|
|
||||||
|
builder = WaypointBuilder(flight, self.game, self.is_player)
|
||||||
|
start = builder.orbit(start, patrol_alt)
|
||||||
|
|
||||||
|
return AwacsFlightPlan(
|
||||||
|
package=self.package,
|
||||||
|
flight=flight,
|
||||||
|
takeoff=builder.takeoff(flight.departure),
|
||||||
|
nav_to=builder.nav_path(flight.departure.position, start.position,
|
||||||
|
patrol_alt),
|
||||||
|
nav_from=builder.nav_path(start.position, flight.arrival.position,
|
||||||
|
patrol_alt),
|
||||||
|
land=builder.land(flight.arrival),
|
||||||
|
divert=builder.divert(flight.divert),
|
||||||
|
hold=start,
|
||||||
|
hold_duration=timedelta(hours=4),
|
||||||
|
)
|
||||||
|
|
||||||
def generate_bai(self, flight: Flight) -> StrikeFlightPlan:
|
def generate_bai(self, flight: Flight) -> StrikeFlightPlan:
|
||||||
"""Generates a BAI flight plan.
|
"""Generates a BAI flight plan.
|
||||||
|
|
||||||
@ -1095,6 +1178,18 @@ class FlightPlanBuilder:
|
|||||||
start = end.point_from_heading(heading - 180, diameter)
|
start = end.point_from_heading(heading - 180, diameter)
|
||||||
return start, end
|
return start, end
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def aewc_orbit(location: MissionTarget) -> Point:
|
||||||
|
closest_airfield = location
|
||||||
|
# TODO: This is a heading to itself.
|
||||||
|
# Place this either over the target or as close as possible outside the
|
||||||
|
# threat zone: https://github.com/Khopa/dcs_liberation/issues/842.
|
||||||
|
heading = location.position.heading_between_point(closest_airfield.position)
|
||||||
|
return location.position.point_from_heading(
|
||||||
|
heading,
|
||||||
|
5000
|
||||||
|
)
|
||||||
|
|
||||||
def racetrack_for_frontline(self, origin: Point,
|
def racetrack_for_frontline(self, origin: Point,
|
||||||
front_line: FrontLine) -> Tuple[Point, Point]:
|
front_line: FrontLine) -> Tuple[Point, Point]:
|
||||||
ally_cp, enemy_cp = front_line.control_points
|
ally_cp, enemy_cp = front_line.control_points
|
||||||
|
|||||||
@ -366,6 +366,26 @@ class WaypointBuilder:
|
|||||||
return (self.race_track_start(start, altitude),
|
return (self.race_track_start(start, altitude),
|
||||||
self.race_track_end(end, altitude))
|
self.race_track_end(end, altitude))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def orbit(start: Point, altitude: Distance) -> FlightWaypoint:
|
||||||
|
"""Creates an circular orbit point.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
start: Position of the waypoint.
|
||||||
|
altitude: Altitude of the racetrack.
|
||||||
|
"""
|
||||||
|
|
||||||
|
waypoint = FlightWaypoint(
|
||||||
|
FlightWaypointType.LOITER,
|
||||||
|
start.x,
|
||||||
|
start.y,
|
||||||
|
altitude
|
||||||
|
)
|
||||||
|
waypoint.name = "ORBIT"
|
||||||
|
waypoint.description = "Anchor and hold at this point"
|
||||||
|
waypoint.pretty_name = "Orbit"
|
||||||
|
return waypoint
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def sweep_start(position: Point, altitude: Distance) -> FlightWaypoint:
|
def sweep_start(position: Point, altitude: Distance) -> FlightWaypoint:
|
||||||
"""Creates a sweep start waypoint.
|
"""Creates a sweep start waypoint.
|
||||||
|
|||||||
@ -47,6 +47,9 @@ class QAircraftTypeSelector(QComboBox):
|
|||||||
elif mission_type in [FlightType.OCA_RUNWAY]:
|
elif mission_type in [FlightType.OCA_RUNWAY]:
|
||||||
if aircraft in gen.flights.ai_flight_planner_db.RUNWAY_ATTACK_CAPABLE:
|
if aircraft in gen.flights.ai_flight_planner_db.RUNWAY_ATTACK_CAPABLE:
|
||||||
self.addItem(f"{db.unit_get_expanded_info(self.country, aircraft, 'name')}", userData=aircraft)
|
self.addItem(f"{db.unit_get_expanded_info(self.country, aircraft, 'name')}", userData=aircraft)
|
||||||
|
elif mission_type in [FlightType.AEWC]:
|
||||||
|
if aircraft in gen.flights.ai_flight_planner_db.AEWC_CAPABLE:
|
||||||
|
self.addItem(f"{db.unit_get_expanded_info(self.country, aircraft, 'name')}", userData=aircraft)
|
||||||
current_aircraft_index = self.findData(current_aircraft)
|
current_aircraft_index = self.findData(current_aircraft)
|
||||||
if current_aircraft_index != -1:
|
if current_aircraft_index != -1:
|
||||||
self.setCurrentIndex(current_aircraft_index)
|
self.setCurrentIndex(current_aircraft_index)
|
||||||
|
|||||||
@ -12,7 +12,7 @@ from PySide2.QtWidgets import (
|
|||||||
QVBoxLayout,
|
QVBoxLayout,
|
||||||
QWidget,
|
QWidget,
|
||||||
)
|
)
|
||||||
from dcs.task import CAP, CAS
|
from dcs.task import CAP, CAS, AWACS
|
||||||
from dcs.unittype import FlyingType, UnitType
|
from dcs.unittype import FlyingType, UnitType
|
||||||
|
|
||||||
from game import db
|
from game import db
|
||||||
@ -45,7 +45,7 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour):
|
|||||||
def init_ui(self):
|
def init_ui(self):
|
||||||
main_layout = QVBoxLayout()
|
main_layout = QVBoxLayout()
|
||||||
|
|
||||||
tasks = [CAP, CAS]
|
tasks = [CAP, CAS, AWACS]
|
||||||
|
|
||||||
scroll_content = QWidget()
|
scroll_content = QWidget()
|
||||||
task_box_layout = QGridLayout()
|
task_box_layout = QGridLayout()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user