mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
parent
270301958a
commit
789806637c
@ -12,6 +12,8 @@
|
|||||||
* **[Modding]** Updated support for Su-57 mod to build-04
|
* **[Modding]** Updated support for Su-57 mod to build-04
|
||||||
* **[Radios]** Added HF-FM band for AN/ARC-222
|
* **[Radios]** Added HF-FM band for AN/ARC-222
|
||||||
* **[Radios]** Ability to define preset channels for radios on squadron level (for human pilots only)
|
* **[Radios]** Ability to define preset channels for radios on squadron level (for human pilots only)
|
||||||
|
* **[Mission Planning]** Avoid helicopters being assigned as escort to planes and vice-versa.
|
||||||
|
* **[Mission Planning]** Allow attack helicopters to escort other helicopters
|
||||||
|
|
||||||
## Fixes
|
## Fixes
|
||||||
* **[Mission Generation]** Anti-ship strikes should use "group attack" in their attack-task
|
* **[Mission Generation]** Anti-ship strikes should use "group attack" in their attack-task
|
||||||
|
|||||||
@ -4,12 +4,15 @@ from dataclasses import dataclass
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import Iterator, TYPE_CHECKING, Type
|
from typing import Iterator, TYPE_CHECKING, Type
|
||||||
|
|
||||||
from game.ato.flightplans.standard import StandardFlightPlan, StandardLayout
|
|
||||||
from game.theater.controlpoint import ControlPointType
|
from game.theater.controlpoint import ControlPointType
|
||||||
from game.theater.missiontarget import MissionTarget
|
from game.theater.missiontarget import MissionTarget
|
||||||
from game.utils import Distance, feet, meters
|
from game.utils import Distance, feet, meters
|
||||||
from ._common_ctld import generate_random_ctld_point
|
from ._common_ctld import generate_random_ctld_point
|
||||||
from .ibuilder import IBuilder
|
from .formationattack import (
|
||||||
|
FormationAttackLayout,
|
||||||
|
FormationAttackBuilder,
|
||||||
|
FormationAttackFlightPlan,
|
||||||
|
)
|
||||||
from .planningerror import PlanningError
|
from .planningerror import PlanningError
|
||||||
from .uizonedisplay import UiZone, UiZoneDisplay
|
from .uizonedisplay import UiZone, UiZoneDisplay
|
||||||
from .waypointbuilder import WaypointBuilder
|
from .waypointbuilder import WaypointBuilder
|
||||||
@ -22,48 +25,58 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class AirAssaultLayout(StandardLayout):
|
class AirAssaultLayout(FormationAttackLayout):
|
||||||
# The pickup point is optional because we don't always need to load the cargo. When
|
# The pickup point is optional because we don't always need to load the cargo. When
|
||||||
# departing from a carrier, LHA, or off-map spawn, the cargo is pre-loaded.
|
# departing from a carrier, LHA, or off-map spawn, the cargo is pre-loaded.
|
||||||
pickup: FlightWaypoint | None
|
pickup: FlightWaypoint | None = None
|
||||||
nav_to_ingress: list[FlightWaypoint]
|
drop_off: FlightWaypoint | None = None
|
||||||
ingress: FlightWaypoint
|
|
||||||
drop_off: FlightWaypoint | None
|
|
||||||
# This is an implementation detail used by CTLD. The aircraft will not go to this
|
# This is an implementation detail used by CTLD. The aircraft will not go to this
|
||||||
# waypoint. It is used by CTLD as the destination for unloaded troops.
|
# waypoint. It is used by CTLD as the destination for unloaded troops.
|
||||||
target: FlightWaypoint
|
|
||||||
nav_to_home: list[FlightWaypoint]
|
|
||||||
|
|
||||||
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
||||||
yield self.departure
|
yield self.departure
|
||||||
if self.pickup is not None:
|
if self.pickup is not None:
|
||||||
yield self.pickup
|
yield self.pickup
|
||||||
yield from self.nav_to_ingress
|
yield from self.nav_to
|
||||||
yield self.ingress
|
yield self.ingress
|
||||||
if self.drop_off is not None:
|
if self.drop_off is not None:
|
||||||
yield self.drop_off
|
yield self.drop_off
|
||||||
yield self.target
|
yield self.targets[0]
|
||||||
yield from self.nav_to_home
|
yield from self.nav_from
|
||||||
yield self.arrival
|
yield self.arrival
|
||||||
if self.divert is not None:
|
if self.divert is not None:
|
||||||
yield self.divert
|
yield self.divert
|
||||||
yield self.bullseye
|
yield self.bullseye
|
||||||
|
|
||||||
|
|
||||||
class AirAssaultFlightPlan(StandardFlightPlan[AirAssaultLayout], UiZoneDisplay):
|
class AirAssaultFlightPlan(FormationAttackFlightPlan, UiZoneDisplay):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def builder_type() -> Type[Builder]:
|
def builder_type() -> Type[Builder]:
|
||||||
return Builder
|
return Builder
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_airassault(self) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def tot_waypoint(self) -> FlightWaypoint:
|
def tot_waypoint(self) -> FlightWaypoint:
|
||||||
if self.flight.is_helo and self.layout.drop_off is not None:
|
if self.flight.is_helo and self.layout.drop_off is not None:
|
||||||
return self.layout.drop_off
|
return self.layout.drop_off
|
||||||
return self.layout.target
|
return self.layout.target
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ingress_time(self) -> timedelta:
|
||||||
|
tot = self.tot
|
||||||
|
travel_time = self.travel_time_between_waypoints(
|
||||||
|
self.layout.ingress, self.layout.drop_off
|
||||||
|
)
|
||||||
|
return tot - travel_time
|
||||||
|
|
||||||
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
|
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
|
||||||
if waypoint == self.tot_waypoint:
|
if waypoint is self.tot_waypoint:
|
||||||
return self.tot
|
return self.tot
|
||||||
|
elif waypoint is self.layout.ingress:
|
||||||
|
return self.ingress_time
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
|
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
|
||||||
@ -84,7 +97,7 @@ class AirAssaultFlightPlan(StandardFlightPlan[AirAssaultLayout], UiZoneDisplay):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Builder(IBuilder[AirAssaultFlightPlan, AirAssaultLayout]):
|
class Builder(FormationAttackBuilder[AirAssaultFlightPlan, AirAssaultLayout]):
|
||||||
def layout(self) -> AirAssaultLayout:
|
def layout(self) -> AirAssaultLayout:
|
||||||
if not self.flight.is_helo and not self.flight.is_hercules:
|
if not self.flight.is_helo and not self.flight.is_hercules:
|
||||||
raise PlanningError(
|
raise PlanningError(
|
||||||
@ -130,7 +143,7 @@ class Builder(IBuilder[AirAssaultFlightPlan, AirAssaultLayout]):
|
|||||||
return AirAssaultLayout(
|
return AirAssaultLayout(
|
||||||
departure=builder.takeoff(self.flight.departure),
|
departure=builder.takeoff(self.flight.departure),
|
||||||
pickup=pickup,
|
pickup=pickup,
|
||||||
nav_to_ingress=builder.nav_path(
|
nav_to=builder.nav_path(
|
||||||
pickup_position,
|
pickup_position,
|
||||||
self.package.waypoints.ingress,
|
self.package.waypoints.ingress,
|
||||||
altitude,
|
altitude,
|
||||||
@ -142,8 +155,8 @@ class Builder(IBuilder[AirAssaultFlightPlan, AirAssaultLayout]):
|
|||||||
self.package.target,
|
self.package.target,
|
||||||
),
|
),
|
||||||
drop_off=dz,
|
drop_off=dz,
|
||||||
target=assault_area,
|
targets=[assault_area],
|
||||||
nav_to_home=builder.nav_path(
|
nav_from=builder.nav_path(
|
||||||
drop_off_zone.position,
|
drop_off_zone.position,
|
||||||
self.flight.arrival.position,
|
self.flight.arrival.position,
|
||||||
altitude,
|
altitude,
|
||||||
@ -152,6 +165,10 @@ class Builder(IBuilder[AirAssaultFlightPlan, AirAssaultLayout]):
|
|||||||
arrival=builder.land(self.flight.arrival),
|
arrival=builder.land(self.flight.arrival),
|
||||||
divert=builder.divert(self.flight.divert),
|
divert=builder.divert(self.flight.divert),
|
||||||
bullseye=builder.bullseye(),
|
bullseye=builder.bullseye(),
|
||||||
|
hold=None,
|
||||||
|
join=builder.join(pickup_position),
|
||||||
|
split=builder.split(self.package.waypoints.split),
|
||||||
|
refuel=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
def build(self) -> AirAssaultFlightPlan:
|
def build(self) -> AirAssaultFlightPlan:
|
||||||
|
|||||||
@ -1,16 +1,35 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
|
from .airassault import AirAssaultLayout
|
||||||
|
from .airlift import AirliftLayout
|
||||||
from .formationattack import (
|
from .formationattack import (
|
||||||
FormationAttackBuilder,
|
FormationAttackBuilder,
|
||||||
FormationAttackFlightPlan,
|
FormationAttackFlightPlan,
|
||||||
FormationAttackLayout,
|
FormationAttackLayout,
|
||||||
)
|
)
|
||||||
from .waypointbuilder import WaypointBuilder
|
from .waypointbuilder import WaypointBuilder
|
||||||
|
from .. import FlightType
|
||||||
|
from ..traveltime import TravelTime, GroundSpeed
|
||||||
|
from ...utils import feet
|
||||||
|
|
||||||
|
|
||||||
class EscortFlightPlan(FormationAttackFlightPlan):
|
class EscortFlightPlan(FormationAttackFlightPlan):
|
||||||
|
@property
|
||||||
|
def push_time(self) -> timedelta:
|
||||||
|
hold2join_time = (
|
||||||
|
TravelTime.between_points(
|
||||||
|
self.layout.hold.position,
|
||||||
|
self.layout.join.position,
|
||||||
|
GroundSpeed.for_flight(self.flight, self.layout.hold.alt),
|
||||||
|
)
|
||||||
|
if self.layout.hold is not None
|
||||||
|
else timedelta(0)
|
||||||
|
)
|
||||||
|
return self.join_time - hold2join_time
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def builder_type() -> Type[Builder]:
|
def builder_type() -> Type[Builder]:
|
||||||
return Builder
|
return Builder
|
||||||
@ -26,31 +45,84 @@ class Builder(FormationAttackBuilder[EscortFlightPlan, FormationAttackLayout]):
|
|||||||
)
|
)
|
||||||
ingress.only_for_player = True
|
ingress.only_for_player = True
|
||||||
target.only_for_player = True
|
target.only_for_player = True
|
||||||
|
if not self.primary_flight_is_air_assault:
|
||||||
hold = builder.hold(self._hold_point())
|
hold = builder.hold(self._hold_point())
|
||||||
|
elif self.package.primary_flight is not None:
|
||||||
|
fp = self.package.primary_flight.flight_plan
|
||||||
|
assert isinstance(fp.layout, AirAssaultLayout)
|
||||||
|
assert fp.layout.pickup is not None
|
||||||
|
hold = builder.hold(fp.layout.pickup.position)
|
||||||
|
|
||||||
join = builder.join(self.package.waypoints.join)
|
join = builder.join(self.package.waypoints.join)
|
||||||
split = builder.split(self.package.waypoints.split)
|
split = builder.split(self.package.waypoints.split)
|
||||||
refuel = builder.refuel(self.package.waypoints.refuel)
|
|
||||||
|
ingress_alt = self.doctrine.ingress_altitude
|
||||||
initial = builder.escort_hold(
|
initial = builder.escort_hold(
|
||||||
self.package.waypoints.initial, self.doctrine.ingress_altitude
|
target.position
|
||||||
|
if builder.flight.is_helo
|
||||||
|
else self.package.waypoints.initial,
|
||||||
|
min(feet(500), ingress_alt) if builder.flight.is_helo else ingress_alt,
|
||||||
|
)
|
||||||
|
|
||||||
|
pf = self.package.primary_flight
|
||||||
|
if pf and pf.flight_type in [FlightType.AIR_ASSAULT, FlightType.TRANSPORT]:
|
||||||
|
layout = pf.flight_plan.layout
|
||||||
|
assert isinstance(layout, AirAssaultLayout) or isinstance(
|
||||||
|
layout, AirliftLayout
|
||||||
|
)
|
||||||
|
if isinstance(layout, AirliftLayout):
|
||||||
|
join = builder.join(layout.departure.position)
|
||||||
|
else:
|
||||||
|
join = builder.join(layout.ingress.position)
|
||||||
|
if layout.pickup:
|
||||||
|
join = builder.join(layout.pickup.position)
|
||||||
|
split = builder.split(layout.arrival.position)
|
||||||
|
if layout.drop_off:
|
||||||
|
initial = builder.escort_hold(
|
||||||
|
layout.drop_off.position,
|
||||||
|
min(feet(200), ingress_alt)
|
||||||
|
if builder.flight.is_helo
|
||||||
|
else ingress_alt,
|
||||||
|
)
|
||||||
|
|
||||||
|
refuel = None
|
||||||
|
if not self.flight.is_helo:
|
||||||
|
refuel = builder.refuel(self.package.waypoints.refuel)
|
||||||
|
|
||||||
|
departure = builder.takeoff(self.flight.departure)
|
||||||
|
if hold:
|
||||||
|
nav_to = builder.nav_path(
|
||||||
|
hold.position, join.position, self.doctrine.ingress_altitude
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
nav_to = builder.nav_path(
|
||||||
|
departure.position, join.position, self.doctrine.ingress_altitude
|
||||||
|
)
|
||||||
|
|
||||||
|
if refuel:
|
||||||
|
nav_from = builder.nav_path(
|
||||||
|
refuel.position,
|
||||||
|
self.flight.arrival.position,
|
||||||
|
self.doctrine.ingress_altitude,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
nav_from = builder.nav_path(
|
||||||
|
split.position,
|
||||||
|
self.flight.arrival.position,
|
||||||
|
self.doctrine.ingress_altitude,
|
||||||
)
|
)
|
||||||
|
|
||||||
return FormationAttackLayout(
|
return FormationAttackLayout(
|
||||||
departure=builder.takeoff(self.flight.departure),
|
departure=departure,
|
||||||
hold=hold,
|
hold=hold,
|
||||||
nav_to=builder.nav_path(
|
nav_to=nav_to,
|
||||||
hold.position, join.position, self.doctrine.ingress_altitude
|
|
||||||
),
|
|
||||||
join=join,
|
join=join,
|
||||||
ingress=ingress,
|
ingress=ingress,
|
||||||
initial=initial,
|
initial=initial,
|
||||||
targets=[target],
|
targets=[target],
|
||||||
split=split,
|
split=split,
|
||||||
refuel=refuel,
|
refuel=refuel,
|
||||||
nav_from=builder.nav_path(
|
nav_from=nav_from,
|
||||||
refuel.position,
|
|
||||||
self.flight.arrival.position,
|
|
||||||
self.doctrine.ingress_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),
|
||||||
bullseye=builder.bullseye(),
|
bullseye=builder.bullseye(),
|
||||||
|
|||||||
@ -308,11 +308,15 @@ class FlightPlan(ABC, Generic[LayoutT]):
|
|||||||
def estimate_ground_ops(self) -> timedelta:
|
def estimate_ground_ops(self) -> timedelta:
|
||||||
if self.flight.start_type in {StartType.RUNWAY, StartType.IN_FLIGHT}:
|
if self.flight.start_type in {StartType.RUNWAY, StartType.IN_FLIGHT}:
|
||||||
return timedelta()
|
return timedelta()
|
||||||
if self.flight.from_cp.is_fleet:
|
if self.flight.from_cp.is_fleet or self.flight.from_cp.is_fob:
|
||||||
return timedelta(minutes=2)
|
return timedelta(minutes=2)
|
||||||
else:
|
else:
|
||||||
return timedelta(minutes=8)
|
return timedelta(minutes=8)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_airassault(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mission_departure_time(self) -> timedelta:
|
def mission_departure_time(self) -> timedelta:
|
||||||
"""The time that the mission is complete and the flight RTBs."""
|
"""The time that the mission is complete and the flight RTBs."""
|
||||||
|
|||||||
@ -187,13 +187,7 @@ class FormationAttackBuilder(IBuilder[FlightPlanT, LayoutT], ABC):
|
|||||||
|
|
||||||
hold = None
|
hold = None
|
||||||
join = None
|
join = None
|
||||||
if (
|
if self.primary_flight_is_air_assault:
|
||||||
self.flight is self.package.primary_flight
|
|
||||||
or self.package.primary_flight
|
|
||||||
and isinstance(
|
|
||||||
self.package.primary_flight.flight_plan, FormationAttackFlightPlan
|
|
||||||
)
|
|
||||||
):
|
|
||||||
hold = builder.hold(self._hold_point())
|
hold = builder.hold(self._hold_point())
|
||||||
join = builder.join(self.package.waypoints.join)
|
join = builder.join(self.package.waypoints.join)
|
||||||
split = builder.split(self.package.waypoints.split)
|
split = builder.split(self.package.waypoints.split)
|
||||||
@ -240,6 +234,17 @@ class FormationAttackBuilder(IBuilder[FlightPlanT, LayoutT], ABC):
|
|||||||
bullseye=builder.bullseye(),
|
bullseye=builder.bullseye(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def primary_flight_is_air_assault(self) -> bool:
|
||||||
|
if self.flight is self.package.primary_flight:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
assert self.package.primary_flight is not None
|
||||||
|
fp = self.package.primary_flight.flight_plan
|
||||||
|
if fp.is_airassault:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def target_waypoint(
|
def target_waypoint(
|
||||||
flight: Flight, builder: WaypointBuilder, target: StrikeTarget
|
flight: Flight, builder: WaypointBuilder, target: StrikeTarget
|
||||||
|
|||||||
@ -10,6 +10,7 @@ from typing import (
|
|||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
Tuple,
|
Tuple,
|
||||||
Union,
|
Union,
|
||||||
|
Literal,
|
||||||
)
|
)
|
||||||
|
|
||||||
from dcs.mapping import Point, Vector2
|
from dcs.mapping import Point, Vector2
|
||||||
@ -169,7 +170,7 @@ class WaypointBuilder:
|
|||||||
"HOLD",
|
"HOLD",
|
||||||
FlightWaypointType.LOITER,
|
FlightWaypointType.LOITER,
|
||||||
position,
|
position,
|
||||||
meters(500) if self.is_helo else self.doctrine.rendezvous_altitude,
|
feet(1000) 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",
|
||||||
@ -471,20 +472,23 @@ class WaypointBuilder:
|
|||||||
)
|
)
|
||||||
return hold
|
return hold
|
||||||
|
|
||||||
@staticmethod
|
def escort_hold(self, start: Point, altitude: Distance) -> FlightWaypoint:
|
||||||
def escort_hold(start: Point, altitude: Distance) -> FlightWaypoint:
|
|
||||||
"""Creates custom waypoint for escort flights that need to hold.
|
"""Creates custom waypoint for escort flights that need to hold.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
start: Position of the waypoint.
|
start: Position of the waypoint.
|
||||||
altitude: Altitude of the holding pattern.
|
altitude: Altitude of the holding pattern.
|
||||||
"""
|
"""
|
||||||
|
alt_type: Literal["BARO", "RADIO"] = "BARO"
|
||||||
|
if self.is_helo:
|
||||||
|
alt_type = "RADIO"
|
||||||
|
|
||||||
return FlightWaypoint(
|
return FlightWaypoint(
|
||||||
"ESCORT HOLD",
|
"ESCORT HOLD",
|
||||||
FlightWaypointType.CUSTOM,
|
FlightWaypointType.CUSTOM,
|
||||||
start,
|
start,
|
||||||
altitude,
|
altitude,
|
||||||
|
alt_type=alt_type,
|
||||||
description="Anchor and hold at this point",
|
description="Anchor and hold at this point",
|
||||||
pretty_name="Escort Hold",
|
pretty_name="Escort Hold",
|
||||||
)
|
)
|
||||||
|
|||||||
@ -39,8 +39,8 @@ class GroundSpeed:
|
|||||||
# as it can at sea level. This probably isn't great assumption, but
|
# as it can at sea level. This probably isn't great assumption, but
|
||||||
# might. be sufficient given the wiggle room. We can come up with
|
# might. be sufficient given the wiggle room. We can come up with
|
||||||
# another heuristic if needed.
|
# another heuristic if needed.
|
||||||
cruise_mach = max_speed.mach() * (0.65 if flight.is_helo else 0.85)
|
cruise_mach = max_speed.mach() * (0.60 if flight.is_helo else 0.85)
|
||||||
return mach(cruise_mach, altitude)
|
return mach(cruise_mach, altitude if not flight.is_helo else meters(0))
|
||||||
|
|
||||||
|
|
||||||
class TravelTime:
|
class TravelTime:
|
||||||
|
|||||||
@ -43,8 +43,10 @@ class PackageBuilder:
|
|||||||
caller should return any previously planned flights to the inventory
|
caller should return any previously planned flights to the inventory
|
||||||
using release_planned_aircraft.
|
using release_planned_aircraft.
|
||||||
"""
|
"""
|
||||||
|
pf = self.package.primary_flight
|
||||||
|
heli = pf.is_helo if pf else False
|
||||||
squadron = self.air_wing.best_squadron_for(
|
squadron = self.air_wing.best_squadron_for(
|
||||||
self.package.target, plan.task, plan.num_aircraft, this_turn=True
|
self.package.target, plan.task, plan.num_aircraft, heli, this_turn=True
|
||||||
)
|
)
|
||||||
if squadron is None:
|
if squadron is None:
|
||||||
return False
|
return False
|
||||||
|
|||||||
@ -82,11 +82,14 @@ class PackageFulfiller:
|
|||||||
purchase_multiplier: int,
|
purchase_multiplier: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
if not builder.plan_flight(flight):
|
if not builder.plan_flight(flight):
|
||||||
|
pf = builder.package.primary_flight
|
||||||
|
heli = pf.is_helo if pf else False
|
||||||
missing_types.add(flight.task)
|
missing_types.add(flight.task)
|
||||||
purchase_order = AircraftProcurementRequest(
|
purchase_order = AircraftProcurementRequest(
|
||||||
near=mission.location,
|
near=mission.location,
|
||||||
task_capability=flight.task,
|
task_capability=flight.task,
|
||||||
number=flight.num_aircraft * purchase_multiplier,
|
number=flight.num_aircraft * purchase_multiplier,
|
||||||
|
heli=heli,
|
||||||
)
|
)
|
||||||
# Reserves are planned for critical missions, so prioritize those orders
|
# Reserves are planned for critical missions, so prioritize those orders
|
||||||
# over aircraft needed for non-critical missions.
|
# over aircraft needed for non-critical missions.
|
||||||
|
|||||||
@ -4,14 +4,22 @@ from dcs.point import MovingPoint
|
|||||||
from dcs.task import ControlledTask, OptFormation, OrbitAction
|
from dcs.task import ControlledTask, OptFormation, OrbitAction
|
||||||
|
|
||||||
from game.ato.flightplans.loiter import LoiterFlightPlan
|
from game.ato.flightplans.loiter import LoiterFlightPlan
|
||||||
|
from game.utils import meters
|
||||||
from ._helper import create_stop_orbit_trigger
|
from ._helper import create_stop_orbit_trigger
|
||||||
from .pydcswaypointbuilder import PydcsWaypointBuilder
|
from .pydcswaypointbuilder import PydcsWaypointBuilder
|
||||||
|
|
||||||
|
|
||||||
class HoldPointBuilder(PydcsWaypointBuilder):
|
class HoldPointBuilder(PydcsWaypointBuilder):
|
||||||
def add_tasks(self, waypoint: MovingPoint) -> None:
|
def add_tasks(self, waypoint: MovingPoint) -> None:
|
||||||
|
speed = self.flight.squadron.aircraft.preferred_patrol_speed(
|
||||||
|
meters(waypoint.alt)
|
||||||
|
)
|
||||||
loiter = ControlledTask(
|
loiter = ControlledTask(
|
||||||
OrbitAction(altitude=waypoint.alt, pattern=OrbitAction.OrbitPattern.Circle)
|
OrbitAction(
|
||||||
|
altitude=waypoint.alt,
|
||||||
|
speed=speed.meters_per_second,
|
||||||
|
pattern=OrbitAction.OrbitPattern.Circle,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
if not isinstance(self.flight.flight_plan, LoiterFlightPlan):
|
if not isinstance(self.flight.flight_plan, LoiterFlightPlan):
|
||||||
flight_plan_type = self.flight.flight_plan.__class__.__name__
|
flight_plan_type = self.flight.flight_plan.__class__.__name__
|
||||||
@ -29,4 +37,7 @@ class HoldPointBuilder(PydcsWaypointBuilder):
|
|||||||
create_stop_orbit_trigger(loiter, self.package, self.mission, elapsed)
|
create_stop_orbit_trigger(loiter, self.package, self.mission, elapsed)
|
||||||
# end of hotfix
|
# end of hotfix
|
||||||
waypoint.add_task(loiter)
|
waypoint.add_task(loiter)
|
||||||
|
if self.flight.is_helo:
|
||||||
|
waypoint.add_task(OptFormation.rotary_column())
|
||||||
|
else:
|
||||||
waypoint.add_task(OptFormation.finger_four_close())
|
waypoint.add_task(OptFormation.finger_four_close())
|
||||||
|
|||||||
@ -8,6 +8,7 @@ from dcs.task import (
|
|||||||
OptECMUsing,
|
OptECMUsing,
|
||||||
OptFormation,
|
OptFormation,
|
||||||
Targets,
|
Targets,
|
||||||
|
OptROE,
|
||||||
)
|
)
|
||||||
|
|
||||||
from game.ato import FlightType
|
from game.ato import FlightType
|
||||||
@ -18,17 +19,29 @@ from .pydcswaypointbuilder import PydcsWaypointBuilder
|
|||||||
|
|
||||||
class JoinPointBuilder(PydcsWaypointBuilder):
|
class JoinPointBuilder(PydcsWaypointBuilder):
|
||||||
def add_tasks(self, waypoint: MovingPoint) -> None:
|
def add_tasks(self, waypoint: MovingPoint) -> None:
|
||||||
|
if self.flight.is_helo:
|
||||||
|
waypoint.tasks.append(OptFormation.rotary_wedge())
|
||||||
|
else:
|
||||||
waypoint.tasks.append(OptFormation.finger_four_open())
|
waypoint.tasks.append(OptFormation.finger_four_open())
|
||||||
|
|
||||||
doctrine = self.flight.coalition.doctrine
|
doctrine = self.flight.coalition.doctrine
|
||||||
|
|
||||||
if self.flight.flight_type == FlightType.ESCORT:
|
if self.flight.flight_type == FlightType.ESCORT:
|
||||||
self.configure_escort_tasks(
|
targets = [
|
||||||
waypoint,
|
|
||||||
[
|
|
||||||
Targets.All.Air.Planes.Fighters.id,
|
Targets.All.Air.Planes.Fighters.id,
|
||||||
Targets.All.Air.Planes.MultiroleFighters.id,
|
Targets.All.Air.Planes.MultiroleFighters.id,
|
||||||
],
|
]
|
||||||
|
if self.flight.is_helo:
|
||||||
|
targets = [
|
||||||
|
Targets.All.Air.Helicopters.id,
|
||||||
|
Targets.All.GroundUnits.AirDefence.id,
|
||||||
|
Targets.All.GroundUnits.GroundVehicles.UnarmedVehicles.id,
|
||||||
|
Targets.All.GroundUnits.GroundVehicles.ArmoredVehicles.id,
|
||||||
|
Targets.All.Naval.Ships.ArmedShips.LightArmedShips.id,
|
||||||
|
]
|
||||||
|
self.configure_escort_tasks(
|
||||||
|
waypoint,
|
||||||
|
targets,
|
||||||
max_dist=doctrine.escort_engagement_range.nautical_miles,
|
max_dist=doctrine.escort_engagement_range.nautical_miles,
|
||||||
vertical_spacing=doctrine.escort_spacing.feet,
|
vertical_spacing=doctrine.escort_spacing.feet,
|
||||||
)
|
)
|
||||||
@ -71,10 +84,18 @@ class JoinPointBuilder(PydcsWaypointBuilder):
|
|||||||
max_dist: float = 30.0,
|
max_dist: float = 30.0,
|
||||||
vertical_spacing: float = 2000.0,
|
vertical_spacing: float = 2000.0,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
waypoint.tasks.append(OptROE(value=OptROE.Values.OpenFireWeaponFree))
|
||||||
|
|
||||||
rx = (random.random() + 0.1) * 333
|
rx = (random.random() + 0.1) * 333
|
||||||
ry = feet(vertical_spacing).meters
|
ry = feet(vertical_spacing).meters
|
||||||
rz = (random.random() + 0.1) * 166 * random.choice([-1, 1])
|
rz = (random.random() + 0.1) * 166 * random.choice([-1, 1])
|
||||||
pos = {"x": rx, "y": ry, "z": rz}
|
pos = {"x": rx, "y": ry, "z": rz}
|
||||||
|
engage_dist = int(nautical_miles(max_dist).meters)
|
||||||
|
|
||||||
|
if self.flight.is_helo:
|
||||||
|
for key in pos:
|
||||||
|
pos[key] *= 0.25
|
||||||
|
engage_dist = int(engage_dist * 0.25)
|
||||||
|
|
||||||
group_id = None
|
group_id = None
|
||||||
if self.package.primary_flight is not None:
|
if self.package.primary_flight is not None:
|
||||||
@ -83,7 +104,7 @@ class JoinPointBuilder(PydcsWaypointBuilder):
|
|||||||
escort = ControlledTask(
|
escort = ControlledTask(
|
||||||
EscortTaskAction(
|
EscortTaskAction(
|
||||||
group_id=group_id,
|
group_id=group_id,
|
||||||
engagement_max_dist=int(nautical_miles(max_dist).meters),
|
engagement_max_dist=engage_dist,
|
||||||
targets=target_types,
|
targets=target_types,
|
||||||
position=pos,
|
position=pos,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
from dcs.point import MovingPoint
|
from dcs.point import MovingPoint
|
||||||
from dcs.task import Land
|
from dcs.task import Land, RunScript
|
||||||
|
|
||||||
|
|
||||||
from .pydcswaypointbuilder import PydcsWaypointBuilder
|
from .pydcswaypointbuilder import PydcsWaypointBuilder
|
||||||
|
|
||||||
@ -14,4 +13,9 @@ class LandingZoneBuilder(PydcsWaypointBuilder):
|
|||||||
landing_point = waypoint.position.random_point_within(15, 5)
|
landing_point = waypoint.position.random_point_within(15, 5)
|
||||||
# Use Land Task with 30s duration for helos
|
# Use Land Task with 30s duration for helos
|
||||||
waypoint.add_task(Land(landing_point, duration=30))
|
waypoint.add_task(Land(landing_point, duration=30))
|
||||||
|
if waypoint.name == "DROPOFFZONE":
|
||||||
|
script = RunScript(
|
||||||
|
f'trigger.action.setUserFlag("split-{id(self.package)}", true)'
|
||||||
|
)
|
||||||
|
waypoint.tasks.append(script)
|
||||||
return waypoint
|
return waypoint
|
||||||
|
|||||||
@ -15,6 +15,9 @@ class SplitPointBuilder(PydcsWaypointBuilder):
|
|||||||
ecm_option = OptECMUsing(value=OptECMUsing.Values.UseIfOnlyLockByRadar)
|
ecm_option = OptECMUsing(value=OptECMUsing.Values.UseIfOnlyLockByRadar)
|
||||||
waypoint.tasks.append(ecm_option)
|
waypoint.tasks.append(ecm_option)
|
||||||
|
|
||||||
|
if self.flight.is_helo:
|
||||||
|
waypoint.tasks.append(OptFormation.rotary_wedge())
|
||||||
|
else:
|
||||||
waypoint.tasks.append(OptFormation.finger_four_close())
|
waypoint.tasks.append(OptFormation.finger_four_close())
|
||||||
waypoint.speed_locked = True
|
waypoint.speed_locked = True
|
||||||
waypoint.speed = self.flight.coalition.doctrine.rtb_speed.meters_per_second
|
waypoint.speed = self.flight.coalition.doctrine.rtb_speed.meters_per_second
|
||||||
|
|||||||
@ -45,7 +45,7 @@ class LogisticsGenerator:
|
|||||||
# Create the Waypoint Zone used by CTLD
|
# Create the Waypoint Zone used by CTLD
|
||||||
target_zone = f"{self.group.name} TARGET_ZONE"
|
target_zone = f"{self.group.name} TARGET_ZONE"
|
||||||
self.mission.triggers.add_triggerzone(
|
self.mission.triggers.add_triggerzone(
|
||||||
self.flight.flight_plan.layout.target.position,
|
self.flight.flight_plan.layout.targets[0].position,
|
||||||
self.flight.flight_plan.ctld_target_zone_radius.meters,
|
self.flight.flight_plan.ctld_target_zone_radius.meters,
|
||||||
False,
|
False,
|
||||||
target_zone,
|
target_zone,
|
||||||
|
|||||||
@ -22,11 +22,12 @@ class AircraftProcurementRequest:
|
|||||||
near: MissionTarget
|
near: MissionTarget
|
||||||
task_capability: FlightType
|
task_capability: FlightType
|
||||||
number: int
|
number: int
|
||||||
|
heli: bool = False
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
task = self.task_capability.value
|
task = self.task_capability.value
|
||||||
target = self.near.name
|
target = self.near.name
|
||||||
return f"{self.number} ship {task} near {target}"
|
return f"{self.number} ship {task} near {target} (heli={self.heli})"
|
||||||
|
|
||||||
|
|
||||||
class ProcurementAi:
|
class ProcurementAi:
|
||||||
@ -234,7 +235,11 @@ class ProcurementAi:
|
|||||||
) -> Iterator[Squadron]:
|
) -> Iterator[Squadron]:
|
||||||
threatened = []
|
threatened = []
|
||||||
for squadron in self.air_wing.best_squadrons_for(
|
for squadron in self.air_wing.best_squadrons_for(
|
||||||
request.near, request.task_capability, request.number, this_turn=False
|
request.near,
|
||||||
|
request.task_capability,
|
||||||
|
request.number,
|
||||||
|
request.heli,
|
||||||
|
this_turn=False,
|
||||||
):
|
):
|
||||||
parking_type = ParkingType().from_squadron(squadron)
|
parking_type = ParkingType().from_squadron(squadron)
|
||||||
|
|
||||||
|
|||||||
@ -45,7 +45,12 @@ class AirWing:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def best_squadrons_for(
|
def best_squadrons_for(
|
||||||
self, location: MissionTarget, task: FlightType, size: int, this_turn: bool
|
self,
|
||||||
|
location: MissionTarget,
|
||||||
|
task: FlightType,
|
||||||
|
size: int,
|
||||||
|
heli: bool,
|
||||||
|
this_turn: bool,
|
||||||
) -> list[Squadron]:
|
) -> list[Squadron]:
|
||||||
airfield_cache = ObjectiveDistanceCache.get_closest_airfields(location)
|
airfield_cache = ObjectiveDistanceCache.get_closest_airfields(location)
|
||||||
best_aircraft = AircraftType.priority_list_for_task(task)
|
best_aircraft = AircraftType.priority_list_for_task(task)
|
||||||
@ -55,7 +60,9 @@ class AirWing:
|
|||||||
continue
|
continue
|
||||||
capable_at_base = []
|
capable_at_base = []
|
||||||
for squadron in control_point.squadrons:
|
for squadron in control_point.squadrons:
|
||||||
if squadron.can_auto_assign_mission(location, task, size, this_turn):
|
if squadron.can_auto_assign_mission(
|
||||||
|
location, task, size, heli, this_turn
|
||||||
|
):
|
||||||
capable_at_base.append(squadron)
|
capable_at_base.append(squadron)
|
||||||
if squadron.aircraft not in best_aircraft:
|
if squadron.aircraft not in best_aircraft:
|
||||||
# If it is not already in the list it should be the last one
|
# If it is not already in the list it should be the last one
|
||||||
@ -79,9 +86,14 @@ class AirWing:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def best_squadron_for(
|
def best_squadron_for(
|
||||||
self, location: MissionTarget, task: FlightType, size: int, this_turn: bool
|
self,
|
||||||
|
location: MissionTarget,
|
||||||
|
task: FlightType,
|
||||||
|
size: int,
|
||||||
|
heli: bool,
|
||||||
|
this_turn: bool,
|
||||||
) -> Optional[Squadron]:
|
) -> Optional[Squadron]:
|
||||||
for squadron in self.best_squadrons_for(location, task, size, this_turn):
|
for squadron in self.best_squadrons_for(location, task, size, heli, this_turn):
|
||||||
return squadron
|
return squadron
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@ -273,7 +273,12 @@ class Squadron:
|
|||||||
return task in self.auto_assignable_mission_types
|
return task in self.auto_assignable_mission_types
|
||||||
|
|
||||||
def can_auto_assign_mission(
|
def can_auto_assign_mission(
|
||||||
self, location: MissionTarget, task: FlightType, size: int, this_turn: bool
|
self,
|
||||||
|
location: MissionTarget,
|
||||||
|
task: FlightType,
|
||||||
|
size: int,
|
||||||
|
heli: bool,
|
||||||
|
this_turn: bool,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
if (
|
if (
|
||||||
self.location.cptype.name in ["FOB", "FARP"]
|
self.location.cptype.name in ["FOB", "FARP"]
|
||||||
@ -288,6 +293,15 @@ class Squadron:
|
|||||||
if this_turn and not self.can_fulfill_flight(size):
|
if this_turn and not self.can_fulfill_flight(size):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if task in [FlightType.ESCORT, FlightType.SEAD_ESCORT]:
|
||||||
|
if heli and not self.aircraft.helicopter and not self.aircraft.lha_capable:
|
||||||
|
return False
|
||||||
|
if not heli and self.aircraft.helicopter:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if heli and task == FlightType.REFUELING:
|
||||||
|
return False
|
||||||
|
|
||||||
distance_to_target = meters(location.distance_to(self.location))
|
distance_to_target = meters(location.distance_to(self.location))
|
||||||
return distance_to_target <= self.aircraft.max_mission_range
|
return distance_to_target <= self.aircraft.max_mission_range
|
||||||
|
|
||||||
|
|||||||
@ -626,6 +626,13 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
|
|||||||
"""
|
"""
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_fob(self) -> bool:
|
||||||
|
"""
|
||||||
|
:return: Whether this control point is a FOB
|
||||||
|
"""
|
||||||
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def moveable(self) -> bool:
|
def moveable(self) -> bool:
|
||||||
"""
|
"""
|
||||||
@ -1605,6 +1612,13 @@ class Fob(ControlPoint, RadioFrequencyContainer, CTLD):
|
|||||||
def income_per_turn(self) -> int:
|
def income_per_turn(self) -> int:
|
||||||
return 10
|
return 10
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_fob(self) -> bool:
|
||||||
|
"""
|
||||||
|
:return: Whether this control point is a FOB
|
||||||
|
"""
|
||||||
|
return True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def category(self) -> str:
|
def category(self) -> str:
|
||||||
return "fob"
|
return "fob"
|
||||||
|
|||||||
@ -63,6 +63,24 @@ local unitPayloads = {
|
|||||||
[3] = 32,
|
[3] = 32,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
[4] = {
|
||||||
|
["name"] = "Retribution Escort",
|
||||||
|
["pylons"] = {
|
||||||
|
[1] = {
|
||||||
|
["CLSID"] = "{88D18A5E-99C8-4B04-B40B-1C02F2018B6E}",
|
||||||
|
["num"] = 1,
|
||||||
|
},
|
||||||
|
[2] = {
|
||||||
|
["CLSID"] = "{88D18A5E-99C8-4B04-B40B-1C02F2018B6E}",
|
||||||
|
["num"] = 4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
["tasks"] = {
|
||||||
|
[1] = 18,
|
||||||
|
[2] = 31,
|
||||||
|
[3] = 32,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
["unitType"] = "AH-1W",
|
["unitType"] = "AH-1W",
|
||||||
}
|
}
|
||||||
|
|||||||
@ -74,6 +74,31 @@ local unitPayloads = {
|
|||||||
[1] = 31,
|
[1] = 31,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
[4] = {
|
||||||
|
["displayName"] = "Retribution Escort",
|
||||||
|
["name"] = "Retribution Escort",
|
||||||
|
["pylons"] = {
|
||||||
|
[1] = {
|
||||||
|
["CLSID"] = "{M299_4xAGM_114L}",
|
||||||
|
["num"] = 3,
|
||||||
|
},
|
||||||
|
[2] = {
|
||||||
|
["CLSID"] = "{M299_4xAGM_114L}",
|
||||||
|
["num"] = 4,
|
||||||
|
},
|
||||||
|
[3] = {
|
||||||
|
["CLSID"] = "{M299_4xAGM_114L}",
|
||||||
|
["num"] = 2,
|
||||||
|
},
|
||||||
|
[4] = {
|
||||||
|
["CLSID"] = "{M299_4xAGM_114L}",
|
||||||
|
["num"] = 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
["tasks"] = {
|
||||||
|
[1] = 31,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
["unitType"] = "AH-64D_BLK_II",
|
["unitType"] = "AH-64D_BLK_II",
|
||||||
}
|
}
|
||||||
|
|||||||
@ -67,6 +67,38 @@ local unitPayloads = {
|
|||||||
[2] = 32,
|
[2] = 32,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
[3] = {
|
||||||
|
["name"] = "Retribution Escort",
|
||||||
|
["pylons"] = {
|
||||||
|
[1] = {
|
||||||
|
["CLSID"] = "{9S846_2xIGLA}",
|
||||||
|
["num"] = 6,
|
||||||
|
},
|
||||||
|
[2] = {
|
||||||
|
["CLSID"] = "{9S846_2xIGLA}",
|
||||||
|
["num"] = 5,
|
||||||
|
},
|
||||||
|
[3] = {
|
||||||
|
["CLSID"] = "{A6FD14D3-6D30-4C85-88A7-8D17BEE120E2}",
|
||||||
|
["num"] = 4,
|
||||||
|
},
|
||||||
|
[4] = {
|
||||||
|
["CLSID"] = "{A6FD14D3-6D30-4C85-88A7-8D17BEE120E2}",
|
||||||
|
["num"] = 1,
|
||||||
|
},
|
||||||
|
[5] = {
|
||||||
|
["CLSID"] = "B_8V20A_OFP2",
|
||||||
|
["num"] = 3,
|
||||||
|
},
|
||||||
|
[6] = {
|
||||||
|
["CLSID"] = "B_8V20A_OFP2",
|
||||||
|
["num"] = 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
["tasks"] = {
|
||||||
|
[1] = 31,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
["tasks"] = {
|
["tasks"] = {
|
||||||
},
|
},
|
||||||
|
|||||||
@ -6,11 +6,11 @@ local unitPayloads = {
|
|||||||
["name"] = "Retribution CAS",
|
["name"] = "Retribution CAS",
|
||||||
["pylons"] = {
|
["pylons"] = {
|
||||||
[1] = {
|
[1] = {
|
||||||
["CLSID"] = "{B919B0F4-7C25-455E-9A02-CEA51DB895E3}",
|
["CLSID"] = "{2x9M120_Ataka_V}",
|
||||||
["num"] = 6,
|
["num"] = 6,
|
||||||
},
|
},
|
||||||
[2] = {
|
[2] = {
|
||||||
["CLSID"] = "{B919B0F4-7C25-455E-9A02-CEA51DB895E3}",
|
["CLSID"] = "{2x9M120_Ataka_V}",
|
||||||
["num"] = 1,
|
["num"] = 1,
|
||||||
},
|
},
|
||||||
[3] = {
|
[3] = {
|
||||||
@ -22,11 +22,11 @@ local unitPayloads = {
|
|||||||
["num"] = 3,
|
["num"] = 3,
|
||||||
},
|
},
|
||||||
[5] = {
|
[5] = {
|
||||||
["CLSID"] = "{B919B0F4-7C25-455E-9A02-CEA51DB895E3}",
|
["CLSID"] = "{2x9M120_Ataka_V}",
|
||||||
["num"] = 5,
|
["num"] = 5,
|
||||||
},
|
},
|
||||||
[6] = {
|
[6] = {
|
||||||
["CLSID"] = "{B919B0F4-7C25-455E-9A02-CEA51DB895E3}",
|
["CLSID"] = "{2x9M120_Ataka_V}",
|
||||||
["num"] = 2,
|
["num"] = 2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -41,11 +41,11 @@ local unitPayloads = {
|
|||||||
["name"] = "Retribution BAI",
|
["name"] = "Retribution BAI",
|
||||||
["pylons"] = {
|
["pylons"] = {
|
||||||
[1] = {
|
[1] = {
|
||||||
["CLSID"] = "{B919B0F4-7C25-455E-9A02-CEA51DB895E3}",
|
["CLSID"] = "{2x9M120_Ataka_V}",
|
||||||
["num"] = 6,
|
["num"] = 6,
|
||||||
},
|
},
|
||||||
[2] = {
|
[2] = {
|
||||||
["CLSID"] = "{B919B0F4-7C25-455E-9A02-CEA51DB895E3}",
|
["CLSID"] = "{2x9M120_Ataka_V}",
|
||||||
["num"] = 1,
|
["num"] = 1,
|
||||||
},
|
},
|
||||||
[3] = {
|
[3] = {
|
||||||
@ -57,11 +57,11 @@ local unitPayloads = {
|
|||||||
["num"] = 3,
|
["num"] = 3,
|
||||||
},
|
},
|
||||||
[5] = {
|
[5] = {
|
||||||
["CLSID"] = "{B919B0F4-7C25-455E-9A02-CEA51DB895E3}",
|
["CLSID"] = "{2x9M120_Ataka_V}",
|
||||||
["num"] = 5,
|
["num"] = 5,
|
||||||
},
|
},
|
||||||
[6] = {
|
[6] = {
|
||||||
["CLSID"] = "{B919B0F4-7C25-455E-9A02-CEA51DB895E3}",
|
["CLSID"] = "{2x9M120_Ataka_V}",
|
||||||
["num"] = 2,
|
["num"] = 2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -92,11 +92,11 @@ local unitPayloads = {
|
|||||||
["num"] = 5,
|
["num"] = 5,
|
||||||
},
|
},
|
||||||
[5] = {
|
[5] = {
|
||||||
["CLSID"] = "{B919B0F4-7C25-455E-9A02-CEA51DB895E3}",
|
["CLSID"] = "{2x9M120_Ataka_V}",
|
||||||
["num"] = 1,
|
["num"] = 1,
|
||||||
},
|
},
|
||||||
[6] = {
|
[6] = {
|
||||||
["CLSID"] = "{B919B0F4-7C25-455E-9A02-CEA51DB895E3}",
|
["CLSID"] = "{2x9M120_Ataka_V}",
|
||||||
["num"] = 6,
|
["num"] = 6,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -109,11 +109,11 @@ local unitPayloads = {
|
|||||||
["name"] = "Retribution Antiship",
|
["name"] = "Retribution Antiship",
|
||||||
["pylons"] = {
|
["pylons"] = {
|
||||||
[1] = {
|
[1] = {
|
||||||
["CLSID"] = "{B919B0F4-7C25-455E-9A02-CEA51DB895E3}",
|
["CLSID"] = "{2x9M120_Ataka_V}",
|
||||||
["num"] = 6,
|
["num"] = 6,
|
||||||
},
|
},
|
||||||
[2] = {
|
[2] = {
|
||||||
["CLSID"] = "{B919B0F4-7C25-455E-9A02-CEA51DB895E3}",
|
["CLSID"] = "{2x9M120_Ataka_V}",
|
||||||
["num"] = 1,
|
["num"] = 1,
|
||||||
},
|
},
|
||||||
[3] = {
|
[3] = {
|
||||||
@ -125,11 +125,11 @@ local unitPayloads = {
|
|||||||
["num"] = 3,
|
["num"] = 3,
|
||||||
},
|
},
|
||||||
[5] = {
|
[5] = {
|
||||||
["CLSID"] = "{B919B0F4-7C25-455E-9A02-CEA51DB895E3}",
|
["CLSID"] = "{2x9M120_Ataka_V}",
|
||||||
["num"] = 5,
|
["num"] = 5,
|
||||||
},
|
},
|
||||||
[6] = {
|
[6] = {
|
||||||
["CLSID"] = "{B919B0F4-7C25-455E-9A02-CEA51DB895E3}",
|
["CLSID"] = "{2x9M120_Ataka_V}",
|
||||||
["num"] = 2,
|
["num"] = 2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -144,11 +144,11 @@ local unitPayloads = {
|
|||||||
["name"] = "Retribution SEAD",
|
["name"] = "Retribution SEAD",
|
||||||
["pylons"] = {
|
["pylons"] = {
|
||||||
[1] = {
|
[1] = {
|
||||||
["CLSID"] = "{B919B0F4-7C25-455E-9A02-CEA51DB895E3}",
|
["CLSID"] = "{2x9M120_Ataka_V}",
|
||||||
["num"] = 6,
|
["num"] = 6,
|
||||||
},
|
},
|
||||||
[2] = {
|
[2] = {
|
||||||
["CLSID"] = "{B919B0F4-7C25-455E-9A02-CEA51DB895E3}",
|
["CLSID"] = "{2x9M120_Ataka_V}",
|
||||||
["num"] = 1,
|
["num"] = 1,
|
||||||
},
|
},
|
||||||
[3] = {
|
[3] = {
|
||||||
@ -160,11 +160,11 @@ local unitPayloads = {
|
|||||||
["num"] = 3,
|
["num"] = 3,
|
||||||
},
|
},
|
||||||
[5] = {
|
[5] = {
|
||||||
["CLSID"] = "{B919B0F4-7C25-455E-9A02-CEA51DB895E3}",
|
["CLSID"] = "{2x9M120_Ataka_V}",
|
||||||
["num"] = 5,
|
["num"] = 5,
|
||||||
},
|
},
|
||||||
[6] = {
|
[6] = {
|
||||||
["CLSID"] = "{B919B0F4-7C25-455E-9A02-CEA51DB895E3}",
|
["CLSID"] = "{2x9M120_Ataka_V}",
|
||||||
["num"] = 2,
|
["num"] = 2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -179,11 +179,11 @@ local unitPayloads = {
|
|||||||
["name"] = "Retribution DEAD",
|
["name"] = "Retribution DEAD",
|
||||||
["pylons"] = {
|
["pylons"] = {
|
||||||
[1] = {
|
[1] = {
|
||||||
["CLSID"] = "{B919B0F4-7C25-455E-9A02-CEA51DB895E3}",
|
["CLSID"] = "{2x9M120_Ataka_V}",
|
||||||
["num"] = 6,
|
["num"] = 6,
|
||||||
},
|
},
|
||||||
[2] = {
|
[2] = {
|
||||||
["CLSID"] = "{B919B0F4-7C25-455E-9A02-CEA51DB895E3}",
|
["CLSID"] = "{2x9M120_Ataka_V}",
|
||||||
["num"] = 1,
|
["num"] = 1,
|
||||||
},
|
},
|
||||||
[3] = {
|
[3] = {
|
||||||
@ -195,11 +195,11 @@ local unitPayloads = {
|
|||||||
["num"] = 3,
|
["num"] = 3,
|
||||||
},
|
},
|
||||||
[5] = {
|
[5] = {
|
||||||
["CLSID"] = "{B919B0F4-7C25-455E-9A02-CEA51DB895E3}",
|
["CLSID"] = "{2x9M120_Ataka_V}",
|
||||||
["num"] = 5,
|
["num"] = 5,
|
||||||
},
|
},
|
||||||
[6] = {
|
[6] = {
|
||||||
["CLSID"] = "{B919B0F4-7C25-455E-9A02-CEA51DB895E3}",
|
["CLSID"] = "{2x9M120_Ataka_V}",
|
||||||
["num"] = 2,
|
["num"] = 2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -209,6 +209,31 @@ local unitPayloads = {
|
|||||||
[3] = 32,
|
[3] = 32,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
[7] = {
|
||||||
|
["displayName"] = "Retribution Escort",
|
||||||
|
["name"] = "Retribution Escort",
|
||||||
|
["pylons"] = {
|
||||||
|
[1] = {
|
||||||
|
["CLSID"] = "{2x9M220_Ataka_V}",
|
||||||
|
["num"] = 6,
|
||||||
|
},
|
||||||
|
[2] = {
|
||||||
|
["CLSID"] = "{2x9M220_Ataka_V}",
|
||||||
|
["num"] = 1,
|
||||||
|
},
|
||||||
|
[3] = {
|
||||||
|
["CLSID"] = "{2x9M120_Ataka_V}",
|
||||||
|
["num"] = 5,
|
||||||
|
},
|
||||||
|
[4] = {
|
||||||
|
["CLSID"] = "{2x9M120_Ataka_V}",
|
||||||
|
["num"] = 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
["tasks"] = {
|
||||||
|
[1] = 31,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
["tasks"] = {
|
["tasks"] = {
|
||||||
},
|
},
|
||||||
|
|||||||
@ -142,6 +142,32 @@ local unitPayloads = {
|
|||||||
[3] = 18,
|
[3] = 18,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
[6] = {
|
||||||
|
["name"] = "Retribution Escort",
|
||||||
|
["pylons"] = {
|
||||||
|
[1] = {
|
||||||
|
["CLSID"] = "{B919B0F4-7C25-455E-9A02-CEA51DB895E3}",
|
||||||
|
["num"] = 1,
|
||||||
|
},
|
||||||
|
[2] = {
|
||||||
|
["CLSID"] = "{B919B0F4-7C25-455E-9A02-CEA51DB895E3}",
|
||||||
|
["num"] = 2,
|
||||||
|
},
|
||||||
|
[3] = {
|
||||||
|
["CLSID"] = "{B919B0F4-7C25-455E-9A02-CEA51DB895E3}",
|
||||||
|
["num"] = 5,
|
||||||
|
},
|
||||||
|
[4] = {
|
||||||
|
["CLSID"] = "{B919B0F4-7C25-455E-9A02-CEA51DB895E3}",
|
||||||
|
["num"] = 6,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
["tasks"] = {
|
||||||
|
[1] = 31,
|
||||||
|
[2] = 32,
|
||||||
|
[3] = 18,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
["unitType"] = "Mi-24V",
|
["unitType"] = "Mi-24V",
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,4 +22,5 @@ variants:
|
|||||||
tasks:
|
tasks:
|
||||||
BAI: 480
|
BAI: 480
|
||||||
CAS: 480
|
CAS: 480
|
||||||
|
Escort: 100
|
||||||
OCA/Aircraft: 480
|
OCA/Aircraft: 480
|
||||||
|
|||||||
@ -23,4 +23,5 @@ variants:
|
|||||||
tasks:
|
tasks:
|
||||||
BAI: 490
|
BAI: 490
|
||||||
CAS: 490
|
CAS: 490
|
||||||
|
Escort: 80
|
||||||
OCA/Aircraft: 490
|
OCA/Aircraft: 490
|
||||||
|
|||||||
@ -24,4 +24,5 @@ variants:
|
|||||||
tasks:
|
tasks:
|
||||||
BAI: 500
|
BAI: 500
|
||||||
CAS: 500
|
CAS: 500
|
||||||
|
Escort: 90
|
||||||
OCA/Aircraft: 500
|
OCA/Aircraft: 500
|
||||||
|
|||||||
@ -37,4 +37,5 @@ radios:
|
|||||||
tasks:
|
tasks:
|
||||||
BAI: 510
|
BAI: 510
|
||||||
CAS: 510
|
CAS: 510
|
||||||
|
Escort: 100
|
||||||
OCA/Aircraft: 510
|
OCA/Aircraft: 510
|
||||||
|
|||||||
@ -27,4 +27,5 @@ kneeboard_units: "metric"
|
|||||||
tasks:
|
tasks:
|
||||||
BAI: 430
|
BAI: 430
|
||||||
CAS: 430
|
CAS: 430
|
||||||
|
Escort: 90
|
||||||
OCA/Aircraft: 430
|
OCA/Aircraft: 430
|
||||||
|
|||||||
@ -28,4 +28,5 @@ kneeboard_units: "metric"
|
|||||||
tasks:
|
tasks:
|
||||||
BAI: 440
|
BAI: 440
|
||||||
CAS: 440
|
CAS: 440
|
||||||
|
Escort: 100
|
||||||
OCA/Aircraft: 440
|
OCA/Aircraft: 440
|
||||||
|
|||||||
@ -39,4 +39,5 @@ tasks:
|
|||||||
Air Assault: 20
|
Air Assault: 20
|
||||||
BAI: 410
|
BAI: 410
|
||||||
CAS: 410
|
CAS: 410
|
||||||
|
Escort: 100
|
||||||
OCA/Aircraft: 410
|
OCA/Aircraft: 410
|
||||||
|
|||||||
@ -29,4 +29,5 @@ tasks:
|
|||||||
Air Assault: 10
|
Air Assault: 10
|
||||||
BAI: 400
|
BAI: 400
|
||||||
CAS: 400
|
CAS: 400
|
||||||
|
Escort: 100
|
||||||
OCA/Aircraft: 400
|
OCA/Aircraft: 400
|
||||||
|
|||||||
@ -18,4 +18,5 @@ variants:
|
|||||||
tasks:
|
tasks:
|
||||||
BAI: 420
|
BAI: 420
|
||||||
CAS: 420
|
CAS: 420
|
||||||
|
Escort: 100
|
||||||
OCA/Aircraft: 420
|
OCA/Aircraft: 420
|
||||||
|
|||||||
@ -23,4 +23,5 @@ kneeboard_units: "metric"
|
|||||||
tasks:
|
tasks:
|
||||||
BAI: 450
|
BAI: 450
|
||||||
CAS: 450
|
CAS: 450
|
||||||
|
Escort: 100
|
||||||
OCA/Aircraft: 450
|
OCA/Aircraft: 450
|
||||||
|
|||||||
@ -26,4 +26,5 @@ kneeboard_units: "metric"
|
|||||||
tasks:
|
tasks:
|
||||||
BAI: 460
|
BAI: 460
|
||||||
CAS: 460
|
CAS: 460
|
||||||
|
Escort: 90
|
||||||
OCA/Aircraft: 460
|
OCA/Aircraft: 460
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user