mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Add AirAssault and Airlift mission types with CTLD support
- Add the new airassault mission type and special flightplans for it - Add the mission type to airbase and FOB - Add Layout for the UH-1H - Add mission type to capable squadrons - Allow the auto planner to task air assault missions when preconditions are met - Improve Airlift mission type and improve the flightplan (Stopover and Helo landing) - Allow Slingload and spawnable crates for airlift - Rework airsupport to a general missiondata class - Added Carrier Information to mission data - Allow to define CTLD specific capabilities in the unit yaml - Allow inflight preload and fixed wing support for air assault
This commit is contained in:
parent
de148dbb61
commit
aa77cfe4b9
@ -14,13 +14,15 @@ Saves from 5.x are not compatible with 6.0.
|
|||||||
* **[Flight Planning]** Added the ability to plan tankers for recovery on package flights. AI does not plan.
|
* **[Flight Planning]** Added the ability to plan tankers for recovery on package flights. AI does not plan.
|
||||||
* **[Flight Planning]** Air to Ground flights now have ECM enabled on lock at the join point, and SEAD/DEAD also have ECM enabled on detection and lock at ingress.
|
* **[Flight Planning]** Air to Ground flights now have ECM enabled on lock at the join point, and SEAD/DEAD also have ECM enabled on detection and lock at ingress.
|
||||||
* **[Flight Planning]** AWACS flightplan changed from orbit to a racetrack to reduce data link disconnects which were caused by blind spots as a result of the bank angle.
|
* **[Flight Planning]** AWACS flightplan changed from orbit to a racetrack to reduce data link disconnects which were caused by blind spots as a result of the bank angle.
|
||||||
|
* **[Flight Planning]** Added a new helo mission type: AirAssault which can be used to load and transport infantry troops from a pickup zone or a carrier to an enemy CP to capture it.
|
||||||
|
* **[Flight Planning]** Improved the Airlift mission type so that it now can be enforced within the unit transfer dialog and implemented CTLD support. This allows user to spawn sling loadable crates at the pickup location and fly transport flights.
|
||||||
* **[Modding]** Updated UH-60L mod version support to 1.3.1
|
* **[Modding]** Updated UH-60L mod version support to 1.3.1
|
||||||
* **[Modding]** Updated the High Digit SAMs implementation and added the HQ-2 as well as the upgraded SA-2 and SA-3 Launchers from the mod. Threat range circles will now also be displayed correctly.
|
* **[Modding]** Updated the High Digit SAMs implementation and added the HQ-2 as well as the upgraded SA-2 and SA-3 Launchers from the mod. Threat range circles will now also be displayed correctly.
|
||||||
* **[UI]** Added options to the loadout editor for setting properties such as HMD choice.
|
* **[UI]** Added options to the loadout editor for setting properties such as HMD choice.
|
||||||
* **[UI]** Added separate images for the different carrier types.
|
* **[UI]** Added separate images for the different carrier types.
|
||||||
* **[Campaign]** Allow campaign designers to define default values for the economy settings (starting budget and multiplier).
|
* **[Campaign]** Allow campaign designers to define default values for the economy settings (starting budget and multiplier).
|
||||||
* **[Plugins]** Allow full support of the SkynetIADS plugin with all advanced features (connection nodes, power sources, command centers) if campaign supports it.
|
* **[Plugins]** Allow full support of the SkynetIADS plugin with all advanced features (connection nodes, power sources, command centers) if campaign supports it.
|
||||||
* **[Plugins]** Added support for the CTLD script by ciribob and updated the JTAC Autolase
|
* **[Plugins]** Added support for the CTLD script by ciribob with many possible customization options and updated the JTAC Autolase to the CTLD included script.
|
||||||
|
|
||||||
## Fixes
|
## Fixes
|
||||||
|
|
||||||
|
|||||||
@ -483,6 +483,20 @@ TRANSPORT_CAPABLE = [
|
|||||||
Mi_26,
|
Mi_26,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
AIR_ASSAULT_CAPABLE = [
|
||||||
|
CH_53E,
|
||||||
|
CH_47D,
|
||||||
|
UH_60L,
|
||||||
|
SH_60B,
|
||||||
|
UH_60A,
|
||||||
|
UH_1H,
|
||||||
|
Mi_8MT,
|
||||||
|
Mi_26,
|
||||||
|
Mi_24P,
|
||||||
|
Mi_24V,
|
||||||
|
Hercules,
|
||||||
|
]
|
||||||
|
|
||||||
DRONES = [MQ_9_Reaper, RQ_1A_Predator, WingLoong_I]
|
DRONES = [MQ_9_Reaper, RQ_1A_Predator, WingLoong_I]
|
||||||
|
|
||||||
AEWC_CAPABLE = [
|
AEWC_CAPABLE = [
|
||||||
@ -538,6 +552,8 @@ def dcs_types_for_task(task: FlightType) -> Sequence[Type[FlyingType]]:
|
|||||||
return REFUELING_CAPABALE
|
return REFUELING_CAPABALE
|
||||||
elif task == FlightType.TRANSPORT:
|
elif task == FlightType.TRANSPORT:
|
||||||
return TRANSPORT_CAPABLE
|
return TRANSPORT_CAPABLE
|
||||||
|
elif task == FlightType.AIR_ASSAULT:
|
||||||
|
return AIR_ASSAULT_CAPABLE
|
||||||
else:
|
else:
|
||||||
logging.error(f"Unplannable flight type: {task}")
|
logging.error(f"Unplannable flight type: {task}")
|
||||||
return []
|
return []
|
||||||
|
|||||||
@ -139,6 +139,10 @@ class Flight(SidcDescribable):
|
|||||||
def unit_type(self) -> AircraftType:
|
def unit_type(self) -> AircraftType:
|
||||||
return self.squadron.aircraft
|
return self.squadron.aircraft
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_helo(self) -> bool:
|
||||||
|
return self.unit_type.dcs_unit_type.helicopter
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def from_cp(self) -> ControlPoint:
|
def from_cp(self) -> ControlPoint:
|
||||||
return self.departure
|
return self.departure
|
||||||
|
|||||||
128
game/ato/flightplans/airassault.py
Normal file
128
game/ato/flightplans/airassault.py
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from datetime import timedelta
|
||||||
|
from typing import TYPE_CHECKING, Iterator, Type
|
||||||
|
from game.ato.flightplans.airlift import AirliftLayout
|
||||||
|
from game.ato.flightplans.standard import StandardFlightPlan
|
||||||
|
from game.theater.controlpoint import ControlPointType
|
||||||
|
from game.theater.missiontarget import MissionTarget
|
||||||
|
from game.utils import Distance, feet, meters
|
||||||
|
from .ibuilder import IBuilder
|
||||||
|
from .waypointbuilder import WaypointBuilder
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..flight import Flight
|
||||||
|
from ..flightwaypoint import FlightWaypoint
|
||||||
|
|
||||||
|
|
||||||
|
class Builder(IBuilder):
|
||||||
|
def build(self) -> AirAssaultLayout:
|
||||||
|
|
||||||
|
altitude = feet(1500) if self.flight.is_helo else self.doctrine.ingress_altitude
|
||||||
|
altitude_is_agl = self.flight.is_helo
|
||||||
|
|
||||||
|
builder = WaypointBuilder(self.flight, self.coalition)
|
||||||
|
|
||||||
|
if not self.flight.is_helo or self.flight.departure.cptype in [
|
||||||
|
ControlPointType.AIRCRAFT_CARRIER_GROUP,
|
||||||
|
ControlPointType.LHA_GROUP,
|
||||||
|
ControlPointType.OFF_MAP,
|
||||||
|
]:
|
||||||
|
# Non-Helo flights or Off_Map will be preloaded
|
||||||
|
# Carrier operations load the logistics directly from the carrier
|
||||||
|
pickup = None
|
||||||
|
pickup_position = self.flight.departure.position
|
||||||
|
else:
|
||||||
|
# Create a special pickup zone for Helos from Airbase / FOB
|
||||||
|
pickup = builder.pickup(
|
||||||
|
MissionTarget(
|
||||||
|
"Pickup Zone",
|
||||||
|
self.flight.departure.position.random_point_within(1200, 600),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
pickup_position = pickup.position
|
||||||
|
assault_area = builder.assault_area(self.package.target)
|
||||||
|
heading = self.package.target.position.heading_between_point(pickup_position)
|
||||||
|
drop_off_zone = MissionTarget(
|
||||||
|
"Dropoff zone",
|
||||||
|
self.package.target.position.point_from_heading(heading, 1200),
|
||||||
|
)
|
||||||
|
|
||||||
|
return AirAssaultLayout(
|
||||||
|
departure=builder.takeoff(self.flight.departure),
|
||||||
|
nav_to_pickup=builder.nav_path(
|
||||||
|
self.flight.departure.position,
|
||||||
|
pickup_position,
|
||||||
|
altitude,
|
||||||
|
altitude_is_agl,
|
||||||
|
),
|
||||||
|
pickup=pickup,
|
||||||
|
nav_to_drop_off=builder.nav_path(
|
||||||
|
pickup_position,
|
||||||
|
drop_off_zone.position,
|
||||||
|
altitude,
|
||||||
|
altitude_is_agl,
|
||||||
|
),
|
||||||
|
drop_off=builder.drop_off(drop_off_zone),
|
||||||
|
stopover=None,
|
||||||
|
target=assault_area,
|
||||||
|
nav_to_home=builder.nav_path(
|
||||||
|
drop_off_zone.position,
|
||||||
|
self.flight.arrival.position,
|
||||||
|
altitude,
|
||||||
|
altitude_is_agl,
|
||||||
|
),
|
||||||
|
arrival=builder.land(self.flight.arrival),
|
||||||
|
divert=builder.divert(self.flight.divert),
|
||||||
|
bullseye=builder.bullseye(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class AirAssaultLayout(AirliftLayout):
|
||||||
|
target: FlightWaypoint
|
||||||
|
|
||||||
|
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
||||||
|
yield self.departure
|
||||||
|
yield from self.nav_to_pickup
|
||||||
|
if self.pickup:
|
||||||
|
yield self.pickup
|
||||||
|
yield from self.nav_to_drop_off
|
||||||
|
yield self.drop_off
|
||||||
|
yield self.target
|
||||||
|
yield from self.nav_to_home
|
||||||
|
yield self.arrival
|
||||||
|
if self.divert is not None:
|
||||||
|
yield self.divert
|
||||||
|
yield self.bullseye
|
||||||
|
|
||||||
|
|
||||||
|
class AirAssaultFlightPlan(StandardFlightPlan[AirAssaultLayout]):
|
||||||
|
def __init__(self, flight: Flight, layout: AirAssaultLayout) -> None:
|
||||||
|
super().__init__(flight, layout)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def builder_type() -> Type[Builder]:
|
||||||
|
return Builder
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tot_waypoint(self) -> FlightWaypoint | None:
|
||||||
|
return self.layout.drop_off
|
||||||
|
|
||||||
|
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
|
||||||
|
if waypoint == self.tot_waypoint:
|
||||||
|
return self.tot
|
||||||
|
return None
|
||||||
|
|
||||||
|
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def engagement_distance(self) -> Distance:
|
||||||
|
# The radius of the WaypointZone created at the target location
|
||||||
|
return meters(2500)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mission_departure_time(self) -> timedelta:
|
||||||
|
return self.package.time_over_target
|
||||||
@ -4,6 +4,7 @@ from collections.abc import Iterator
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import TYPE_CHECKING, Type
|
from typing import TYPE_CHECKING, Type
|
||||||
|
from game.theater.missiontarget import MissionTarget
|
||||||
|
|
||||||
from game.utils import feet
|
from game.utils import feet
|
||||||
from .ibuilder import IBuilder
|
from .ibuilder import IBuilder
|
||||||
@ -30,9 +31,32 @@ class Builder(IBuilder):
|
|||||||
builder = WaypointBuilder(self.flight, self.coalition)
|
builder = WaypointBuilder(self.flight, self.coalition)
|
||||||
|
|
||||||
pickup = None
|
pickup = None
|
||||||
nav_to_pickup = []
|
stopover = None
|
||||||
|
if self.flight.is_helo:
|
||||||
|
# Create a pickupzone where the cargo will be spawned
|
||||||
|
pickup_zone = MissionTarget(
|
||||||
|
"Pickup Zone", cargo.origin.position.random_point_within(1000, 200)
|
||||||
|
)
|
||||||
|
pickup = builder.pickup(pickup_zone)
|
||||||
|
# If The cargo is at the departure controlpoint, the pickup waypoint should
|
||||||
|
# only be created for client flights
|
||||||
|
pickup.only_for_player = cargo.origin == self.flight.departure
|
||||||
|
|
||||||
|
# Create a dropoff zone where the cargo should be dropped
|
||||||
|
drop_off_zone = MissionTarget(
|
||||||
|
"Dropoff zone",
|
||||||
|
cargo.next_stop.position.random_point_within(1000, 200),
|
||||||
|
)
|
||||||
|
drop_off = builder.drop_off(drop_off_zone)
|
||||||
|
|
||||||
|
# Add an additional stopover point so that the flight can refuel
|
||||||
|
stopover = builder.stopover(cargo.next_stop)
|
||||||
|
else:
|
||||||
|
# Fixed Wing will get stopover points for pickup and dropoff
|
||||||
if cargo.origin != self.flight.departure:
|
if cargo.origin != self.flight.departure:
|
||||||
pickup = builder.pickup(cargo.origin)
|
pickup = builder.stopover(cargo.origin, "PICKUP")
|
||||||
|
drop_off = builder.stopover(cargo.next_stop, "DROP OFF")
|
||||||
|
|
||||||
nav_to_pickup = builder.nav_path(
|
nav_to_pickup = builder.nav_path(
|
||||||
self.flight.departure.position,
|
self.flight.departure.position,
|
||||||
cargo.origin.position,
|
cargo.origin.position,
|
||||||
@ -40,6 +64,16 @@ class Builder(IBuilder):
|
|||||||
altitude_is_agl,
|
altitude_is_agl,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self.flight.client_count > 0:
|
||||||
|
# Normal Landing Waypoint
|
||||||
|
arrival = builder.land(self.flight.arrival)
|
||||||
|
else:
|
||||||
|
# The AI Needs another Stopover point to actually fly back to the original
|
||||||
|
# base. Otherwise the Cargo drop will be the new Landing Waypoint and the
|
||||||
|
# AI will end its mission there instead of flying back.
|
||||||
|
# https://forum.dcs.world/topic/211775-landing-to-refuel-and-rearm-the-landingrefuar-waypoint/
|
||||||
|
arrival = builder.stopover(self.flight.arrival, "LANDING")
|
||||||
|
|
||||||
return AirliftLayout(
|
return AirliftLayout(
|
||||||
departure=builder.takeoff(self.flight.departure),
|
departure=builder.takeoff(self.flight.departure),
|
||||||
nav_to_pickup=nav_to_pickup,
|
nav_to_pickup=nav_to_pickup,
|
||||||
@ -50,14 +84,15 @@ class Builder(IBuilder):
|
|||||||
altitude,
|
altitude,
|
||||||
altitude_is_agl,
|
altitude_is_agl,
|
||||||
),
|
),
|
||||||
drop_off=builder.drop_off(cargo.next_stop),
|
drop_off=drop_off,
|
||||||
|
stopover=stopover,
|
||||||
nav_to_home=builder.nav_path(
|
nav_to_home=builder.nav_path(
|
||||||
cargo.origin.position,
|
cargo.origin.position,
|
||||||
self.flight.arrival.position,
|
self.flight.arrival.position,
|
||||||
altitude,
|
altitude,
|
||||||
altitude_is_agl,
|
altitude_is_agl,
|
||||||
),
|
),
|
||||||
arrival=builder.land(self.flight.arrival),
|
arrival=arrival,
|
||||||
divert=builder.divert(self.flight.divert),
|
divert=builder.divert(self.flight.divert),
|
||||||
bullseye=builder.bullseye(),
|
bullseye=builder.bullseye(),
|
||||||
)
|
)
|
||||||
@ -69,15 +104,18 @@ class AirliftLayout(StandardLayout):
|
|||||||
pickup: FlightWaypoint | None
|
pickup: FlightWaypoint | None
|
||||||
nav_to_drop_off: list[FlightWaypoint]
|
nav_to_drop_off: list[FlightWaypoint]
|
||||||
drop_off: FlightWaypoint
|
drop_off: FlightWaypoint
|
||||||
|
stopover: FlightWaypoint | None
|
||||||
nav_to_home: list[FlightWaypoint]
|
nav_to_home: list[FlightWaypoint]
|
||||||
|
|
||||||
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
||||||
yield self.departure
|
yield self.departure
|
||||||
yield from self.nav_to_pickup
|
yield from self.nav_to_pickup
|
||||||
if self.pickup:
|
if self.pickup is not None:
|
||||||
yield self.pickup
|
yield self.pickup
|
||||||
yield from self.nav_to_drop_off
|
yield from self.nav_to_drop_off
|
||||||
yield self.drop_off
|
yield self.drop_off
|
||||||
|
if self.stopover is not None:
|
||||||
|
yield self.stopover
|
||||||
yield from self.nav_to_home
|
yield from self.nav_to_home
|
||||||
yield self.arrival
|
yield self.arrival
|
||||||
if self.divert is not None:
|
if self.divert is not None:
|
||||||
|
|||||||
@ -8,6 +8,7 @@ from game.data.doctrine import Doctrine
|
|||||||
from game.flightplan import IpZoneGeometry, JoinZoneGeometry
|
from game.flightplan import IpZoneGeometry, JoinZoneGeometry
|
||||||
from game.flightplan.refuelzonegeometry import RefuelZoneGeometry
|
from game.flightplan.refuelzonegeometry import RefuelZoneGeometry
|
||||||
from .aewc import AewcFlightPlan
|
from .aewc import AewcFlightPlan
|
||||||
|
from .airassault import AirAssaultFlightPlan
|
||||||
from .airlift import AirliftFlightPlan
|
from .airlift import AirliftFlightPlan
|
||||||
from .antiship import AntiShipFlightPlan
|
from .antiship import AntiShipFlightPlan
|
||||||
from .bai import BaiFlightPlan
|
from .bai import BaiFlightPlan
|
||||||
@ -108,6 +109,7 @@ class FlightPlanBuilder:
|
|||||||
FlightType.AEWC: AewcFlightPlan,
|
FlightType.AEWC: AewcFlightPlan,
|
||||||
FlightType.TRANSPORT: AirliftFlightPlan,
|
FlightType.TRANSPORT: AirliftFlightPlan,
|
||||||
FlightType.FERRY: FerryFlightPlan,
|
FlightType.FERRY: FerryFlightPlan,
|
||||||
|
FlightType.AIR_ASSAULT: AirAssaultFlightPlan,
|
||||||
}
|
}
|
||||||
return plan_dict.get(task)
|
return plan_dict.get(task)
|
||||||
|
|
||||||
|
|||||||
@ -54,7 +54,7 @@ class WaypointBuilder:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def is_helo(self) -> bool:
|
def is_helo(self) -> bool:
|
||||||
return self.flight.unit_type.dcs_unit_type.helicopter
|
return self.flight.is_helo
|
||||||
|
|
||||||
def takeoff(self, departure: ControlPoint) -> FlightWaypoint:
|
def takeoff(self, departure: ControlPoint) -> FlightWaypoint:
|
||||||
"""Create takeoff waypoint for the given arrival airfield or carrier.
|
"""Create takeoff waypoint for the given arrival airfield or carrier.
|
||||||
@ -303,6 +303,9 @@ class WaypointBuilder:
|
|||||||
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)
|
||||||
|
|
||||||
|
def assault_area(self, target: MissionTarget) -> FlightWaypoint:
|
||||||
|
return self._target_area(f"ASSAULT {target.name}", target)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _target_area(
|
def _target_area(
|
||||||
name: str,
|
name: str,
|
||||||
@ -491,36 +494,57 @@ class WaypointBuilder:
|
|||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def pickup(control_point: ControlPoint) -> FlightWaypoint:
|
def stopover(stopover: ControlPoint, name: str = "STOPOVER") -> FlightWaypoint:
|
||||||
"""Creates a cargo pickup waypoint.
|
"""Creates a stopover waypoint.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
control_point: Pick up location.
|
control_point: Pick up location.
|
||||||
"""
|
"""
|
||||||
return FlightWaypoint(
|
return FlightWaypoint(
|
||||||
"PICKUP",
|
name,
|
||||||
FlightWaypointType.PICKUP,
|
FlightWaypointType.STOPOVER,
|
||||||
control_point.position,
|
stopover.position,
|
||||||
meters(0),
|
meters(0),
|
||||||
"RADIO",
|
"RADIO",
|
||||||
description=f"Pick up cargo from {control_point}",
|
description=f"Stopover at {stopover}",
|
||||||
pretty_name="Pick up location",
|
pretty_name="Stopover location",
|
||||||
|
control_point=stopover,
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def drop_off(control_point: ControlPoint) -> FlightWaypoint:
|
def pickup(pick_up: MissionTarget) -> FlightWaypoint:
|
||||||
|
"""Creates a cargo pickup waypoint.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
control_point: Pick up location.
|
||||||
|
"""
|
||||||
|
control_point = pick_up if isinstance(pick_up, ControlPoint) else None
|
||||||
|
return FlightWaypoint(
|
||||||
|
"PICKUP",
|
||||||
|
FlightWaypointType.PICKUP,
|
||||||
|
pick_up.position,
|
||||||
|
meters(0),
|
||||||
|
"RADIO",
|
||||||
|
description=f"Pick up cargo from {pick_up.name}",
|
||||||
|
pretty_name="Pick up location",
|
||||||
|
control_point=control_point,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def drop_off(drop_off: MissionTarget) -> FlightWaypoint:
|
||||||
"""Creates a cargo drop-off waypoint.
|
"""Creates a cargo drop-off waypoint.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
control_point: Drop-off location.
|
control_point: Drop-off location.
|
||||||
"""
|
"""
|
||||||
|
control_point = drop_off if isinstance(drop_off, ControlPoint) else None
|
||||||
return FlightWaypoint(
|
return FlightWaypoint(
|
||||||
"DROP OFF",
|
"DROP OFF",
|
||||||
FlightWaypointType.PICKUP,
|
FlightWaypointType.DROP_OFF,
|
||||||
control_point.position,
|
drop_off.position,
|
||||||
meters(0),
|
meters(0),
|
||||||
"RADIO",
|
"RADIO",
|
||||||
description=f"Drop off cargo at {control_point}",
|
description=f"Drop off cargo at {drop_off.name}",
|
||||||
pretty_name="Drop off location",
|
pretty_name="Drop off location",
|
||||||
control_point=control_point,
|
control_point=control_point,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -56,6 +56,7 @@ class FlightType(Enum):
|
|||||||
SEAD_ESCORT = "SEAD Escort"
|
SEAD_ESCORT = "SEAD Escort"
|
||||||
REFUELING = "Refueling"
|
REFUELING = "Refueling"
|
||||||
FERRY = "Ferry"
|
FERRY = "Ferry"
|
||||||
|
AIR_ASSAULT = "Air Assault"
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.value
|
return self.value
|
||||||
@ -89,6 +90,7 @@ class FlightType(Enum):
|
|||||||
FlightType.OCA_RUNWAY,
|
FlightType.OCA_RUNWAY,
|
||||||
FlightType.OCA_AIRCRAFT,
|
FlightType.OCA_AIRCRAFT,
|
||||||
FlightType.SEAD_ESCORT,
|
FlightType.SEAD_ESCORT,
|
||||||
|
FlightType.AIR_ASSAULT,
|
||||||
}
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -112,4 +114,5 @@ class FlightType(Enum):
|
|||||||
FlightType.SWEEP: AirEntity.FIGHTER,
|
FlightType.SWEEP: AirEntity.FIGHTER,
|
||||||
FlightType.TARCAP: AirEntity.FIGHTER,
|
FlightType.TARCAP: AirEntity.FIGHTER,
|
||||||
FlightType.TRANSPORT: AirEntity.UTILITY,
|
FlightType.TRANSPORT: AirEntity.UTILITY,
|
||||||
|
FlightType.AIR_ASSAULT: AirEntity.ROTARY_WING,
|
||||||
}.get(self, AirEntity.UNSPECIFIED)
|
}.get(self, AirEntity.UNSPECIFIED)
|
||||||
|
|||||||
@ -47,3 +47,4 @@ class FlightWaypointType(IntEnum):
|
|||||||
DROP_OFF = 27
|
DROP_OFF = 27
|
||||||
BULLSEYE = 28
|
BULLSEYE = 28
|
||||||
REFUEL = 29 # Should look for nearby tanker to refuel from.
|
REFUEL = 29 # Should look for nearby tanker to refuel from.
|
||||||
|
STOPOVER = 30 # Stopover landing point using the LandingReFuAr waypoint type
|
||||||
|
|||||||
@ -161,6 +161,7 @@ class Package:
|
|||||||
FlightType.BAI,
|
FlightType.BAI,
|
||||||
FlightType.DEAD,
|
FlightType.DEAD,
|
||||||
FlightType.TRANSPORT,
|
FlightType.TRANSPORT,
|
||||||
|
FlightType.AIR_ASSAULT,
|
||||||
FlightType.SEAD,
|
FlightType.SEAD,
|
||||||
FlightType.TARCAP,
|
FlightType.TARCAP,
|
||||||
FlightType.BARCAP,
|
FlightType.BARCAP,
|
||||||
|
|||||||
@ -139,6 +139,14 @@ class ObjectiveFinder:
|
|||||||
"""Iterates over all active front lines in the theater."""
|
"""Iterates over all active front lines in the theater."""
|
||||||
yield from self.game.theater.conflicts()
|
yield from self.game.theater.conflicts()
|
||||||
|
|
||||||
|
def air_assault_targets(self) -> Iterator[ControlPoint]:
|
||||||
|
"""Iterates over all capturable controlpoints for all active front lines"""
|
||||||
|
if not self.game.settings.plugin_option("ctld"):
|
||||||
|
# Air Assault should only be tasked with CTLD enabled
|
||||||
|
return
|
||||||
|
for front_line in self.front_lines():
|
||||||
|
yield front_line.control_point_hostile_to(self.is_player)
|
||||||
|
|
||||||
def vulnerable_control_points(self) -> Iterator[ControlPoint]:
|
def vulnerable_control_points(self) -> Iterator[ControlPoint]:
|
||||||
"""Iterates over friendly CPs that are vulnerable to enemy CPs.
|
"""Iterates over friendly CPs that are vulnerable to enemy CPs.
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ from game.commander.tasks.compound.destroyenemygroundunits import (
|
|||||||
from game.commander.tasks.compound.reduceenemyfrontlinecapacity import (
|
from game.commander.tasks.compound.reduceenemyfrontlinecapacity import (
|
||||||
ReduceEnemyFrontLineCapacity,
|
ReduceEnemyFrontLineCapacity,
|
||||||
)
|
)
|
||||||
|
from game.commander.tasks.primitive.airassault import PlanAirAssault
|
||||||
from game.commander.tasks.primitive.breakthroughattack import BreakthroughAttack
|
from game.commander.tasks.primitive.breakthroughattack import BreakthroughAttack
|
||||||
from game.commander.theaterstate import TheaterState
|
from game.commander.theaterstate import TheaterState
|
||||||
from game.htn import CompoundTask, Method
|
from game.htn import CompoundTask, Method
|
||||||
@ -18,6 +19,7 @@ class CaptureBase(CompoundTask[TheaterState]):
|
|||||||
front_line: FrontLine
|
front_line: FrontLine
|
||||||
|
|
||||||
def each_valid_method(self, state: TheaterState) -> Iterator[Method[TheaterState]]:
|
def each_valid_method(self, state: TheaterState) -> Iterator[Method[TheaterState]]:
|
||||||
|
yield [PlanAirAssault(self.enemy_cp(state))]
|
||||||
yield [BreakthroughAttack(self.front_line, state.context.coalition.player)]
|
yield [BreakthroughAttack(self.front_line, state.context.coalition.player)]
|
||||||
yield [DestroyEnemyGroundUnits(self.front_line)]
|
yield [DestroyEnemyGroundUnits(self.front_line)]
|
||||||
if self.worth_destroying_ammo_depots(state):
|
if self.worth_destroying_ammo_depots(state):
|
||||||
|
|||||||
34
game/commander/tasks/primitive/airassault.py
Normal file
34
game/commander/tasks/primitive/airassault.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from game.commander.tasks.packageplanningtask import PackagePlanningTask
|
||||||
|
from game.commander.theaterstate import TheaterState
|
||||||
|
from game.theater import ControlPoint
|
||||||
|
from game.ato.flighttype import FlightType
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PlanAirAssault(PackagePlanningTask[ControlPoint]):
|
||||||
|
def preconditions_met(self, state: TheaterState) -> bool:
|
||||||
|
if self.target not in state.air_assault_targets:
|
||||||
|
return False
|
||||||
|
if self.capture_blocked(state):
|
||||||
|
# Do not task if there are enemy garrisons blocking the capture
|
||||||
|
return False
|
||||||
|
if not self.target_area_preconditions_met(state):
|
||||||
|
# Do not task if air defense is present in the target area
|
||||||
|
return False
|
||||||
|
return super().preconditions_met(state)
|
||||||
|
|
||||||
|
def capture_blocked(self, state: TheaterState) -> bool:
|
||||||
|
garrisons = state.enemy_garrisons[self.target]
|
||||||
|
return len(garrisons.blocking_capture) > 0
|
||||||
|
|
||||||
|
def apply_effects(self, state: TheaterState) -> None:
|
||||||
|
state.air_assault_targets.remove(self.target)
|
||||||
|
|
||||||
|
def propose_flights(self) -> None:
|
||||||
|
self.propose_flight(FlightType.AIR_ASSAULT, 2)
|
||||||
|
# TODO Validate this.. / is Heli escort possible?
|
||||||
|
self.propose_flight(FlightType.TARCAP, 2)
|
||||||
@ -45,6 +45,7 @@ class TheaterState(WorldState["TheaterState"]):
|
|||||||
context: PersistentContext
|
context: PersistentContext
|
||||||
barcaps_needed: dict[ControlPoint, int]
|
barcaps_needed: dict[ControlPoint, int]
|
||||||
active_front_lines: list[FrontLine]
|
active_front_lines: list[FrontLine]
|
||||||
|
air_assault_targets: list[ControlPoint]
|
||||||
front_line_stances: dict[FrontLine, Optional[CombatStance]]
|
front_line_stances: dict[FrontLine, Optional[CombatStance]]
|
||||||
vulnerable_front_lines: list[FrontLine]
|
vulnerable_front_lines: list[FrontLine]
|
||||||
aewc_targets: list[MissionTarget]
|
aewc_targets: list[MissionTarget]
|
||||||
@ -109,6 +110,7 @@ class TheaterState(WorldState["TheaterState"]):
|
|||||||
context=self.context,
|
context=self.context,
|
||||||
barcaps_needed=dict(self.barcaps_needed),
|
barcaps_needed=dict(self.barcaps_needed),
|
||||||
active_front_lines=list(self.active_front_lines),
|
active_front_lines=list(self.active_front_lines),
|
||||||
|
air_assault_targets=list(self.air_assault_targets),
|
||||||
front_line_stances=dict(self.front_line_stances),
|
front_line_stances=dict(self.front_line_stances),
|
||||||
vulnerable_front_lines=list(self.vulnerable_front_lines),
|
vulnerable_front_lines=list(self.vulnerable_front_lines),
|
||||||
aewc_targets=list(self.aewc_targets),
|
aewc_targets=list(self.aewc_targets),
|
||||||
@ -158,6 +160,7 @@ class TheaterState(WorldState["TheaterState"]):
|
|||||||
cp: barcap_rounds for cp in finder.vulnerable_control_points()
|
cp: barcap_rounds for cp in finder.vulnerable_control_points()
|
||||||
},
|
},
|
||||||
active_front_lines=list(finder.front_lines()),
|
active_front_lines=list(finder.front_lines()),
|
||||||
|
air_assault_targets=list(finder.air_assault_targets()),
|
||||||
front_line_stances={f: None for f in finder.front_lines()},
|
front_line_stances={f: None for f in finder.front_lines()},
|
||||||
vulnerable_front_lines=list(finder.front_lines()),
|
vulnerable_front_lines=list(finder.front_lines()),
|
||||||
aewc_targets=[finder.farthest_friendly_control_point()],
|
aewc_targets=[finder.farthest_friendly_control_point()],
|
||||||
|
|||||||
@ -18,6 +18,7 @@ class UnitClass(Enum):
|
|||||||
EARLY_WARNING_RADAR = "EarlyWarningRadar"
|
EARLY_WARNING_RADAR = "EarlyWarningRadar"
|
||||||
FORTIFICATION = "Fortification"
|
FORTIFICATION = "Fortification"
|
||||||
FRIGATE = "Frigate"
|
FRIGATE = "Frigate"
|
||||||
|
HELICOPTER = "Helicopter"
|
||||||
HELICOPTER_CARRIER = "HelicopterCarrier"
|
HELICOPTER_CARRIER = "HelicopterCarrier"
|
||||||
IFV = "IFV"
|
IFV = "IFV"
|
||||||
INFANTRY = "Infantry"
|
INFANTRY = "Infantry"
|
||||||
|
|||||||
@ -47,7 +47,7 @@ from game.utils import (
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game.missiongenerator.aircraft.flightdata import FlightData
|
from game.missiongenerator.aircraft.flightdata import FlightData
|
||||||
from game.missiongenerator.airsupport import AirSupport
|
from game.missiongenerator.missiondata import MissionData
|
||||||
from game.radio.radios import Radio, RadioFrequency, RadioRegistry
|
from game.radio.radios import Radio, RadioFrequency, RadioRegistry
|
||||||
|
|
||||||
|
|
||||||
@ -182,6 +182,14 @@ class AircraftType(UnitType[Type[FlyingType]]):
|
|||||||
channel_allocator: Optional[RadioChannelAllocator]
|
channel_allocator: Optional[RadioChannelAllocator]
|
||||||
channel_namer: Type[ChannelNamer]
|
channel_namer: Type[ChannelNamer]
|
||||||
|
|
||||||
|
# Logisitcs info
|
||||||
|
# cabin_size defines how many troops can be loaded. 0 means the aircraft can not
|
||||||
|
# transport any troops. Default for helos is 10, non helos will have 0.
|
||||||
|
cabin_size: int
|
||||||
|
# If the aircraft can carry crates can_carry_crates should be set to true which
|
||||||
|
# will be set to true for helos by default
|
||||||
|
can_carry_crates: bool
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def flyable(self) -> bool:
|
def flyable(self) -> bool:
|
||||||
return self.dcs_unit_type.flyable
|
return self.dcs_unit_type.flyable
|
||||||
@ -281,10 +289,10 @@ class AircraftType(UnitType[Type[FlyingType]]):
|
|||||||
return freq
|
return freq
|
||||||
|
|
||||||
def assign_channels_for_flight(
|
def assign_channels_for_flight(
|
||||||
self, flight: FlightData, air_support: AirSupport
|
self, flight: FlightData, mission_data: MissionData
|
||||||
) -> None:
|
) -> None:
|
||||||
if self.channel_allocator is not None:
|
if self.channel_allocator is not None:
|
||||||
self.channel_allocator.assign_channels_for_flight(flight, air_support)
|
self.channel_allocator.assign_channels_for_flight(flight, mission_data)
|
||||||
|
|
||||||
def channel_name(self, radio_id: int, channel_id: int) -> str:
|
def channel_name(self, radio_id: int, channel_id: int) -> str:
|
||||||
return self.channel_namer.channel_name(radio_id, channel_id)
|
return self.channel_namer.channel_name(radio_id, channel_id)
|
||||||
@ -387,6 +395,9 @@ class AircraftType(UnitType[Type[FlyingType]]):
|
|||||||
if units_data == "metric":
|
if units_data == "metric":
|
||||||
units = MetricUnits()
|
units = MetricUnits()
|
||||||
|
|
||||||
|
class_name = data.get("class")
|
||||||
|
unit_class = UnitClass.PLANE if class_name is None else UnitClass(class_name)
|
||||||
|
|
||||||
prop_overrides = data.get("default_overrides")
|
prop_overrides = data.get("default_overrides")
|
||||||
if prop_overrides is not None:
|
if prop_overrides is not None:
|
||||||
cls._set_props_overrides(prop_overrides, aircraft, data_path)
|
cls._set_props_overrides(prop_overrides, aircraft, data_path)
|
||||||
@ -419,5 +430,7 @@ class AircraftType(UnitType[Type[FlyingType]]):
|
|||||||
channel_namer=radio_config.channel_namer,
|
channel_namer=radio_config.channel_namer,
|
||||||
kneeboard_units=units,
|
kneeboard_units=units,
|
||||||
utc_kneeboard=data.get("utc_kneeboard", False),
|
utc_kneeboard=data.get("utc_kneeboard", False),
|
||||||
unit_class=UnitClass.PLANE,
|
unit_class=unit_class,
|
||||||
|
cabin_size=data.get("cabin_size", 10 if aircraft.helicopter else 0),
|
||||||
|
can_carry_crates=data.get("can_carry_crates", aircraft.helicopter),
|
||||||
)
|
)
|
||||||
|
|||||||
@ -62,7 +62,10 @@ class AircraftBehavior:
|
|||||||
self.configure_runway_attack(group, flight)
|
self.configure_runway_attack(group, flight)
|
||||||
elif self.task == FlightType.OCA_AIRCRAFT:
|
elif self.task == FlightType.OCA_AIRCRAFT:
|
||||||
self.configure_oca_strike(group, flight)
|
self.configure_oca_strike(group, flight)
|
||||||
elif self.task == FlightType.TRANSPORT:
|
elif self.task in [
|
||||||
|
FlightType.TRANSPORT,
|
||||||
|
FlightType.AIR_ASSAULT,
|
||||||
|
]:
|
||||||
self.configure_transport(group, flight)
|
self.configure_transport(group, flight)
|
||||||
elif self.task == FlightType.FERRY:
|
elif self.task == FlightType.FERRY:
|
||||||
self.configure_ferry(group, flight)
|
self.configure_ferry(group, flight)
|
||||||
|
|||||||
@ -17,7 +17,7 @@ from game.ato.flighttype import FlightType
|
|||||||
from game.ato.package import Package
|
from game.ato.package import Package
|
||||||
from game.ato.starttype import StartType
|
from game.ato.starttype import StartType
|
||||||
from game.factions.faction import Faction
|
from game.factions.faction import Faction
|
||||||
from game.missiongenerator.airsupport import AirSupport
|
from game.missiongenerator.missiondata import MissionData
|
||||||
from game.missiongenerator.lasercoderegistry import LaserCodeRegistry
|
from game.missiongenerator.lasercoderegistry import LaserCodeRegistry
|
||||||
from game.radio.radios import RadioRegistry
|
from game.radio.radios import RadioRegistry
|
||||||
from game.radio.tacan import TacanRegistry
|
from game.radio.tacan import TacanRegistry
|
||||||
@ -49,7 +49,7 @@ class AircraftGenerator:
|
|||||||
tacan_registry: TacanRegistry,
|
tacan_registry: TacanRegistry,
|
||||||
laser_code_registry: LaserCodeRegistry,
|
laser_code_registry: LaserCodeRegistry,
|
||||||
unit_map: UnitMap,
|
unit_map: UnitMap,
|
||||||
air_support: AirSupport,
|
mission_data: MissionData,
|
||||||
helipads: dict[ControlPoint, StaticGroup],
|
helipads: dict[ControlPoint, StaticGroup],
|
||||||
) -> None:
|
) -> None:
|
||||||
self.mission = mission
|
self.mission = mission
|
||||||
@ -61,7 +61,7 @@ class AircraftGenerator:
|
|||||||
self.laser_code_registry = laser_code_registry
|
self.laser_code_registry = laser_code_registry
|
||||||
self.unit_map = unit_map
|
self.unit_map = unit_map
|
||||||
self.flights: List[FlightData] = []
|
self.flights: List[FlightData] = []
|
||||||
self.air_support = air_support
|
self.mission_data = mission_data
|
||||||
self.helipads = helipads
|
self.helipads = helipads
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
@ -174,7 +174,7 @@ class AircraftGenerator:
|
|||||||
self.radio_registry,
|
self.radio_registry,
|
||||||
self.tacan_registy,
|
self.tacan_registy,
|
||||||
self.laser_code_registry,
|
self.laser_code_registry,
|
||||||
self.air_support,
|
self.mission_data,
|
||||||
dynamic_runways,
|
dynamic_runways,
|
||||||
self.use_client,
|
self.use_client,
|
||||||
).configure()
|
).configure()
|
||||||
|
|||||||
@ -12,8 +12,9 @@ from dcs.unitgroup import FlyingGroup
|
|||||||
from game.ato import Flight, FlightType
|
from game.ato import Flight, FlightType
|
||||||
from game.callsigns import callsign_for_support_unit
|
from game.callsigns import callsign_for_support_unit
|
||||||
from game.data.weapons import Pylon, WeaponType as WeaponTypeEnum
|
from game.data.weapons import Pylon, WeaponType as WeaponTypeEnum
|
||||||
from game.missiongenerator.airsupport import AirSupport, AwacsInfo, TankerInfo
|
from game.missiongenerator.missiondata import MissionData, AwacsInfo, TankerInfo
|
||||||
from game.missiongenerator.lasercoderegistry import LaserCodeRegistry
|
from game.missiongenerator.lasercoderegistry import LaserCodeRegistry
|
||||||
|
from game.missiongenerator.logisticsgenerator import LogisticsGenerator
|
||||||
from game.radio.radios import RadioFrequency, RadioRegistry
|
from game.radio.radios import RadioFrequency, RadioRegistry
|
||||||
from game.radio.tacan import TacanBand, TacanRegistry, TacanUsage
|
from game.radio.tacan import TacanBand, TacanRegistry, TacanUsage
|
||||||
from game.runways import RunwayData
|
from game.runways import RunwayData
|
||||||
@ -40,7 +41,7 @@ class FlightGroupConfigurator:
|
|||||||
radio_registry: RadioRegistry,
|
radio_registry: RadioRegistry,
|
||||||
tacan_registry: TacanRegistry,
|
tacan_registry: TacanRegistry,
|
||||||
laser_code_registry: LaserCodeRegistry,
|
laser_code_registry: LaserCodeRegistry,
|
||||||
air_support: AirSupport,
|
mission_data: MissionData,
|
||||||
dynamic_runways: dict[str, RunwayData],
|
dynamic_runways: dict[str, RunwayData],
|
||||||
use_client: bool,
|
use_client: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -52,7 +53,7 @@ class FlightGroupConfigurator:
|
|||||||
self.radio_registry = radio_registry
|
self.radio_registry = radio_registry
|
||||||
self.tacan_registry = tacan_registry
|
self.tacan_registry = tacan_registry
|
||||||
self.laser_code_registry = laser_code_registry
|
self.laser_code_registry = laser_code_registry
|
||||||
self.air_support = air_support
|
self.mission_data = mission_data
|
||||||
self.dynamic_runways = dynamic_runways
|
self.dynamic_runways = dynamic_runways
|
||||||
self.use_client = use_client
|
self.use_client = use_client
|
||||||
|
|
||||||
@ -74,6 +75,20 @@ class FlightGroupConfigurator:
|
|||||||
self.game.theater, self.game.conditions, self.dynamic_runways
|
self.game.theater, self.game.conditions, self.dynamic_runways
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self.flight.flight_type in [
|
||||||
|
FlightType.TRANSPORT,
|
||||||
|
FlightType.AIR_ASSAULT,
|
||||||
|
] and self.game.settings.plugin_option("ctld"):
|
||||||
|
transfer = None
|
||||||
|
if self.flight.flight_type == FlightType.TRANSPORT:
|
||||||
|
coalition = self.game.coalition_for(player=self.flight.blue)
|
||||||
|
transfer = coalition.transfers.transfer_for_flight(self.flight)
|
||||||
|
self.mission_data.logistics.append(
|
||||||
|
LogisticsGenerator(
|
||||||
|
self.flight, self.group, self.mission, self.game.settings, transfer
|
||||||
|
).generate_logistics()
|
||||||
|
)
|
||||||
|
|
||||||
mission_start_time, waypoints = WaypointGenerator(
|
mission_start_time, waypoints = WaypointGenerator(
|
||||||
self.flight,
|
self.flight,
|
||||||
self.group,
|
self.group,
|
||||||
@ -81,7 +96,7 @@ class FlightGroupConfigurator:
|
|||||||
self.game.conditions.start_time,
|
self.game.conditions.start_time,
|
||||||
self.time,
|
self.time,
|
||||||
self.game.settings,
|
self.game.settings,
|
||||||
self.air_support,
|
self.mission_data,
|
||||||
).create_waypoints()
|
).create_waypoints()
|
||||||
|
|
||||||
return FlightData(
|
return FlightData(
|
||||||
@ -130,7 +145,7 @@ class FlightGroupConfigurator:
|
|||||||
def register_air_support(self, channel: RadioFrequency) -> None:
|
def register_air_support(self, channel: RadioFrequency) -> None:
|
||||||
callsign = callsign_for_support_unit(self.group)
|
callsign = callsign_for_support_unit(self.group)
|
||||||
if isinstance(self.flight.flight_plan, AewcFlightPlan):
|
if isinstance(self.flight.flight_plan, AewcFlightPlan):
|
||||||
self.air_support.awacs.append(
|
self.mission_data.awacs.append(
|
||||||
AwacsInfo(
|
AwacsInfo(
|
||||||
group_name=str(self.group.name),
|
group_name=str(self.group.name),
|
||||||
callsign=callsign,
|
callsign=callsign,
|
||||||
@ -143,7 +158,7 @@ class FlightGroupConfigurator:
|
|||||||
)
|
)
|
||||||
elif isinstance(self.flight.flight_plan, TheaterRefuelingFlightPlan):
|
elif isinstance(self.flight.flight_plan, TheaterRefuelingFlightPlan):
|
||||||
tacan = self.tacan_registry.alloc_for_band(TacanBand.Y, TacanUsage.AirToAir)
|
tacan = self.tacan_registry.alloc_for_band(TacanBand.Y, TacanUsage.AirToAir)
|
||||||
self.air_support.tankers.append(
|
self.mission_data.tankers.append(
|
||||||
TankerInfo(
|
TankerInfo(
|
||||||
group_name=str(self.group.name),
|
group_name=str(self.group.name),
|
||||||
callsign=callsign,
|
callsign=callsign,
|
||||||
|
|||||||
@ -1,4 +1,9 @@
|
|||||||
from dcs.point import MovingPoint, PointAction
|
from dcs.point import MovingPoint
|
||||||
|
from dcs.task import Land
|
||||||
|
|
||||||
|
from game.utils import feet
|
||||||
|
from dcs.point import PointAction
|
||||||
|
|
||||||
|
|
||||||
from .pydcswaypointbuilder import PydcsWaypointBuilder
|
from .pydcswaypointbuilder import PydcsWaypointBuilder
|
||||||
|
|
||||||
@ -6,9 +11,16 @@ from .pydcswaypointbuilder import PydcsWaypointBuilder
|
|||||||
class CargoStopBuilder(PydcsWaypointBuilder):
|
class CargoStopBuilder(PydcsWaypointBuilder):
|
||||||
def build(self) -> MovingPoint:
|
def build(self) -> MovingPoint:
|
||||||
waypoint = super().build()
|
waypoint = super().build()
|
||||||
waypoint.type = "LandingReFuAr"
|
# Create a landing task, currently only for Helos!
|
||||||
waypoint.action = PointAction.LandingReFuAr
|
if self.flight.is_helo:
|
||||||
waypoint.landing_refuel_rearm_time = 2 # Minutes.
|
# Calculate a landing point with a small buffer to prevent AI from landing
|
||||||
if (control_point := self.waypoint.control_point) is not None:
|
# directly at the static ammo depot and exploding
|
||||||
waypoint.airdrome_id = control_point.airdrome_id_for_landing
|
landing_point = waypoint.position.random_point_within(15, 5)
|
||||||
|
# Use Land Task with 30s duration for helos
|
||||||
|
waypoint.add_task(Land(landing_point, duration=30))
|
||||||
|
else:
|
||||||
|
# Fixed wing will drop the cargo at the waypoint so we set a lower altitude
|
||||||
|
waypoint.alt = int(feet(10000).meters)
|
||||||
|
waypoint.alt_type = "BARO"
|
||||||
|
waypoint.action = PointAction.FlyOverPoint
|
||||||
return waypoint
|
return waypoint
|
||||||
|
|||||||
@ -10,7 +10,7 @@ from dcs.unitgroup import FlyingGroup
|
|||||||
|
|
||||||
from game.ato import Flight, FlightWaypoint
|
from game.ato import Flight, FlightWaypoint
|
||||||
from game.ato.flightwaypointtype import FlightWaypointType
|
from game.ato.flightwaypointtype import FlightWaypointType
|
||||||
from game.missiongenerator.airsupport import AirSupport
|
from game.missiongenerator.missiondata import MissionData
|
||||||
from game.theater import MissionTarget, TheaterUnit
|
from game.theater import MissionTarget, TheaterUnit
|
||||||
|
|
||||||
TARGET_WAYPOINTS = (
|
TARGET_WAYPOINTS = (
|
||||||
@ -28,7 +28,7 @@ class PydcsWaypointBuilder:
|
|||||||
flight: Flight,
|
flight: Flight,
|
||||||
mission: Mission,
|
mission: Mission,
|
||||||
elapsed_mission_time: timedelta,
|
elapsed_mission_time: timedelta,
|
||||||
air_support: AirSupport,
|
mission_data: MissionData,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.waypoint = waypoint
|
self.waypoint = waypoint
|
||||||
self.group = group
|
self.group = group
|
||||||
@ -36,7 +36,7 @@ class PydcsWaypointBuilder:
|
|||||||
self.flight = flight
|
self.flight = flight
|
||||||
self.mission = mission
|
self.mission = mission
|
||||||
self.elapsed_mission_time = elapsed_mission_time
|
self.elapsed_mission_time = elapsed_mission_time
|
||||||
self.air_support = air_support
|
self.mission_data = mission_data
|
||||||
|
|
||||||
def build(self) -> MovingPoint:
|
def build(self) -> MovingPoint:
|
||||||
waypoint = self.group.add_waypoint(
|
waypoint = self.group.add_waypoint(
|
||||||
|
|||||||
@ -64,7 +64,7 @@ class RaceTrackBuilder(PydcsWaypointBuilder):
|
|||||||
waypoint.add_task(Tanker())
|
waypoint.add_task(Tanker())
|
||||||
|
|
||||||
if self.flight.unit_type.dcs_unit_type.tacan:
|
if self.flight.unit_type.dcs_unit_type.tacan:
|
||||||
tanker_info = self.air_support.tankers[-1]
|
tanker_info = self.mission_data.tankers[-1]
|
||||||
tacan = tanker_info.tacan
|
tacan = tanker_info.tacan
|
||||||
tacan_callsign = {
|
tacan_callsign = {
|
||||||
"Texaco": "TEX",
|
"Texaco": "TEX",
|
||||||
|
|||||||
15
game/missiongenerator/aircraft/waypoints/stopover.py
Normal file
15
game/missiongenerator/aircraft/waypoints/stopover.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
from dcs.point import MovingPoint, PointAction
|
||||||
|
from dcs.task import Land
|
||||||
|
|
||||||
|
from .pydcswaypointbuilder import PydcsWaypointBuilder
|
||||||
|
|
||||||
|
|
||||||
|
class StopoverBuilder(PydcsWaypointBuilder):
|
||||||
|
def build(self) -> MovingPoint:
|
||||||
|
waypoint = super().build()
|
||||||
|
waypoint.type = "LandingReFuAr"
|
||||||
|
waypoint.action = PointAction.LandingReFuAr
|
||||||
|
waypoint.landing_refuel_rearm_time = 2 # Minutes.
|
||||||
|
if (control_point := self.waypoint.control_point) is not None:
|
||||||
|
waypoint.airdrome_id = control_point.airdrome_id_for_landing
|
||||||
|
return waypoint
|
||||||
@ -16,7 +16,8 @@ from game.ato import Flight, FlightWaypoint
|
|||||||
from game.ato.flightstate import InFlight, WaitingForStart
|
from game.ato.flightstate import InFlight, WaitingForStart
|
||||||
from game.ato.flightwaypointtype import FlightWaypointType
|
from game.ato.flightwaypointtype import FlightWaypointType
|
||||||
from game.ato.starttype import StartType
|
from game.ato.starttype import StartType
|
||||||
from game.missiongenerator.airsupport import AirSupport
|
from game.missiongenerator.aircraft.waypoints.stopover import StopoverBuilder
|
||||||
|
from game.missiongenerator.missiondata import MissionData
|
||||||
from game.settings import Settings
|
from game.settings import Settings
|
||||||
from game.utils import pairwise
|
from game.utils import pairwise
|
||||||
from .baiingress import BaiIngressBuilder
|
from .baiingress import BaiIngressBuilder
|
||||||
@ -48,7 +49,7 @@ class WaypointGenerator:
|
|||||||
turn_start_time: datetime,
|
turn_start_time: datetime,
|
||||||
time: datetime,
|
time: datetime,
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
air_support: AirSupport,
|
mission_data: MissionData,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.flight = flight
|
self.flight = flight
|
||||||
self.group = group
|
self.group = group
|
||||||
@ -56,7 +57,7 @@ class WaypointGenerator:
|
|||||||
self.elapsed_mission_time = time - turn_start_time
|
self.elapsed_mission_time = time - turn_start_time
|
||||||
self.time = time
|
self.time = time
|
||||||
self.settings = settings
|
self.settings = settings
|
||||||
self.air_support = air_support
|
self.mission_data = mission_data
|
||||||
|
|
||||||
def create_waypoints(self) -> tuple[timedelta, list[FlightWaypoint]]:
|
def create_waypoints(self) -> tuple[timedelta, list[FlightWaypoint]]:
|
||||||
for waypoint in self.flight.points:
|
for waypoint in self.flight.points:
|
||||||
@ -134,6 +135,7 @@ class WaypointGenerator:
|
|||||||
FlightWaypointType.PATROL_TRACK: RaceTrackBuilder,
|
FlightWaypointType.PATROL_TRACK: RaceTrackBuilder,
|
||||||
FlightWaypointType.PICKUP: CargoStopBuilder,
|
FlightWaypointType.PICKUP: CargoStopBuilder,
|
||||||
FlightWaypointType.REFUEL: RefuelPointBuilder,
|
FlightWaypointType.REFUEL: RefuelPointBuilder,
|
||||||
|
FlightWaypointType.STOPOVER: StopoverBuilder,
|
||||||
}
|
}
|
||||||
builder = builders.get(waypoint.waypoint_type, DefaultWaypointBuilder)
|
builder = builders.get(waypoint.waypoint_type, DefaultWaypointBuilder)
|
||||||
return builder(
|
return builder(
|
||||||
@ -142,7 +144,7 @@ class WaypointGenerator:
|
|||||||
self.flight,
|
self.flight,
|
||||||
self.mission,
|
self.mission,
|
||||||
self.elapsed_mission_time,
|
self.elapsed_mission_time,
|
||||||
self.air_support,
|
self.mission_data,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _estimate_min_fuel_for(self, waypoints: list[FlightWaypoint]) -> None:
|
def _estimate_min_fuel_for(self, waypoints: list[FlightWaypoint]) -> None:
|
||||||
|
|||||||
@ -1,56 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
|
||||||
from datetime import timedelta
|
|
||||||
from typing import Optional, TYPE_CHECKING
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from game.radio.radios import RadioFrequency
|
|
||||||
from game.radio.tacan import TacanChannel
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class AwacsInfo:
|
|
||||||
"""AWACS information for the kneeboard."""
|
|
||||||
|
|
||||||
group_name: str
|
|
||||||
callsign: str
|
|
||||||
freq: RadioFrequency
|
|
||||||
depature_location: Optional[str]
|
|
||||||
start_time: Optional[timedelta]
|
|
||||||
end_time: Optional[timedelta]
|
|
||||||
blue: bool
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class TankerInfo:
|
|
||||||
"""Tanker information for the kneeboard."""
|
|
||||||
|
|
||||||
group_name: str
|
|
||||||
callsign: str
|
|
||||||
variant: str
|
|
||||||
freq: RadioFrequency
|
|
||||||
tacan: TacanChannel
|
|
||||||
start_time: Optional[timedelta]
|
|
||||||
end_time: Optional[timedelta]
|
|
||||||
blue: bool
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class JtacInfo:
|
|
||||||
"""JTAC information."""
|
|
||||||
|
|
||||||
group_name: str
|
|
||||||
unit_name: str
|
|
||||||
callsign: str
|
|
||||||
region: str
|
|
||||||
code: str
|
|
||||||
blue: bool
|
|
||||||
freq: RadioFrequency
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class AirSupport:
|
|
||||||
awacs: list[AwacsInfo] = field(default_factory=list)
|
|
||||||
tankers: list[TankerInfo] = field(default_factory=list)
|
|
||||||
jtacs: list[JtacInfo] = field(default_factory=list)
|
|
||||||
@ -21,7 +21,7 @@ from game.radio.radios import RadioRegistry
|
|||||||
from game.radio.tacan import TacanBand, TacanRegistry, TacanUsage
|
from game.radio.tacan import TacanBand, TacanRegistry, TacanUsage
|
||||||
from game.utils import Heading
|
from game.utils import Heading
|
||||||
from game.ato.ai_flight_planner_db import AEWC_CAPABLE
|
from game.ato.ai_flight_planner_db import AEWC_CAPABLE
|
||||||
from .airsupport import AirSupport, AwacsInfo, TankerInfo
|
from .missiondata import MissionData, AwacsInfo, TankerInfo
|
||||||
from .frontlineconflictdescription import FrontLineConflictDescription
|
from .frontlineconflictdescription import FrontLineConflictDescription
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -43,14 +43,14 @@ class AirSupportGenerator:
|
|||||||
game: Game,
|
game: Game,
|
||||||
radio_registry: RadioRegistry,
|
radio_registry: RadioRegistry,
|
||||||
tacan_registry: TacanRegistry,
|
tacan_registry: TacanRegistry,
|
||||||
air_support: AirSupport,
|
mission_data: MissionData,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.mission = mission
|
self.mission = mission
|
||||||
self.conflict = conflict
|
self.conflict = conflict
|
||||||
self.game = game
|
self.game = game
|
||||||
self.radio_registry = radio_registry
|
self.radio_registry = radio_registry
|
||||||
self.tacan_registry = tacan_registry
|
self.tacan_registry = tacan_registry
|
||||||
self.air_support = air_support
|
self.mission_data = mission_data
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def support_tasks(cls) -> List[Type[MainTask]]:
|
def support_tasks(cls) -> List[Type[MainTask]]:
|
||||||
@ -148,13 +148,13 @@ class AirSupportGenerator:
|
|||||||
tanker_group.points[0].tasks.append(SetInvisibleCommand(True))
|
tanker_group.points[0].tasks.append(SetInvisibleCommand(True))
|
||||||
tanker_group.points[0].tasks.append(SetImmortalCommand(True))
|
tanker_group.points[0].tasks.append(SetImmortalCommand(True))
|
||||||
|
|
||||||
self.air_support.tankers.append(
|
self.mission_data.tankers.append(
|
||||||
TankerInfo(
|
TankerInfo(
|
||||||
str(tanker_group.name),
|
group_name=str(tanker_group.name),
|
||||||
callsign,
|
callsign=callsign,
|
||||||
tanker_unit_type.name,
|
variant=tanker_unit_type.name,
|
||||||
freq,
|
freq=freq,
|
||||||
tacan,
|
tacan=tacan,
|
||||||
start_time=None,
|
start_time=None,
|
||||||
end_time=None,
|
end_time=None,
|
||||||
blue=True,
|
blue=True,
|
||||||
@ -197,7 +197,7 @@ class AirSupportGenerator:
|
|||||||
awacs_flight.points[0].tasks.append(SetInvisibleCommand(True))
|
awacs_flight.points[0].tasks.append(SetInvisibleCommand(True))
|
||||||
awacs_flight.points[0].tasks.append(SetImmortalCommand(True))
|
awacs_flight.points[0].tasks.append(SetImmortalCommand(True))
|
||||||
|
|
||||||
self.air_support.awacs.append(
|
self.mission_data.awacs.append(
|
||||||
AwacsInfo(
|
AwacsInfo(
|
||||||
group_name=str(awacs_flight.name),
|
group_name=str(awacs_flight.name),
|
||||||
callsign=callsign_for_support_unit(awacs_flight),
|
callsign=callsign_for_support_unit(awacs_flight),
|
||||||
|
|||||||
@ -43,7 +43,7 @@ from game.radio.radios import RadioRegistry
|
|||||||
from game.theater.controlpoint import ControlPoint
|
from game.theater.controlpoint import ControlPoint
|
||||||
from game.unitmap import UnitMap
|
from game.unitmap import UnitMap
|
||||||
from game.utils import Heading
|
from game.utils import Heading
|
||||||
from .airsupport import AirSupport, JtacInfo
|
from .missiondata import MissionData, JtacInfo
|
||||||
from .frontlineconflictdescription import FrontLineConflictDescription
|
from .frontlineconflictdescription import FrontLineConflictDescription
|
||||||
from .lasercoderegistry import LaserCodeRegistry
|
from .lasercoderegistry import LaserCodeRegistry
|
||||||
|
|
||||||
@ -80,7 +80,7 @@ class FlotGenerator:
|
|||||||
enemy_stance: CombatStance,
|
enemy_stance: CombatStance,
|
||||||
unit_map: UnitMap,
|
unit_map: UnitMap,
|
||||||
radio_registry: RadioRegistry,
|
radio_registry: RadioRegistry,
|
||||||
air_support: AirSupport,
|
mission_data: MissionData,
|
||||||
laser_code_registry: LaserCodeRegistry,
|
laser_code_registry: LaserCodeRegistry,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.mission = mission
|
self.mission = mission
|
||||||
@ -92,7 +92,7 @@ class FlotGenerator:
|
|||||||
self.game = game
|
self.game = game
|
||||||
self.unit_map = unit_map
|
self.unit_map = unit_map
|
||||||
self.radio_registry = radio_registry
|
self.radio_registry = radio_registry
|
||||||
self.air_support = air_support
|
self.mission_data = mission_data
|
||||||
self.laser_code_registry = laser_code_registry
|
self.laser_code_registry = laser_code_registry
|
||||||
|
|
||||||
def generate(self) -> None:
|
def generate(self) -> None:
|
||||||
@ -166,7 +166,7 @@ class FlotGenerator:
|
|||||||
)
|
)
|
||||||
jtac.points[0].tasks.append(
|
jtac.points[0].tasks.append(
|
||||||
FAC(
|
FAC(
|
||||||
callsign=len(self.air_support.jtacs) + 1,
|
callsign=len(self.mission_data.jtacs) + 1,
|
||||||
frequency=int(freq.mhz),
|
frequency=int(freq.mhz),
|
||||||
modulation=freq.modulation,
|
modulation=freq.modulation,
|
||||||
)
|
)
|
||||||
@ -181,13 +181,13 @@ class FlotGenerator:
|
|||||||
)
|
)
|
||||||
# Note: Will need to change if we ever add ground based JTAC.
|
# Note: Will need to change if we ever add ground based JTAC.
|
||||||
callsign = callsign_for_support_unit(jtac)
|
callsign = callsign_for_support_unit(jtac)
|
||||||
self.air_support.jtacs.append(
|
self.mission_data.jtacs.append(
|
||||||
JtacInfo(
|
JtacInfo(
|
||||||
jtac.name,
|
group_name=jtac.name,
|
||||||
jtac.name,
|
unit_name=jtac.units[0].name,
|
||||||
callsign,
|
callsign=callsign,
|
||||||
frontline,
|
region=frontline,
|
||||||
str(code),
|
code=str(code),
|
||||||
blue=True,
|
blue=True,
|
||||||
freq=freq,
|
freq=freq,
|
||||||
)
|
)
|
||||||
|
|||||||
104
game/missiongenerator/logisticsgenerator.py
Normal file
104
game/missiongenerator/logisticsgenerator.py
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
from typing import Any, Optional
|
||||||
|
from dcs import Mission
|
||||||
|
from dcs.unitgroup import FlyingGroup
|
||||||
|
from dcs.statics import Fortification
|
||||||
|
from game.ato import Flight
|
||||||
|
from game.ato.flightplans.airassault import AirAssaultFlightPlan
|
||||||
|
from game.ato.flightwaypointtype import FlightWaypointType
|
||||||
|
from game.missiongenerator.missiondata import CargoInfo, LogisticsInfo
|
||||||
|
from game.settings.settings import Settings
|
||||||
|
from game.transfers import TransferOrder
|
||||||
|
|
||||||
|
|
||||||
|
ZONE_RADIUS = 300
|
||||||
|
CRATE_ZONE_RADIUS = 50
|
||||||
|
|
||||||
|
|
||||||
|
class LogisticsGenerator:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
flight: Flight,
|
||||||
|
group: FlyingGroup[Any],
|
||||||
|
mission: Mission,
|
||||||
|
settings: Settings,
|
||||||
|
transfer: Optional[TransferOrder] = None,
|
||||||
|
) -> None:
|
||||||
|
self.flight = flight
|
||||||
|
self.group = group
|
||||||
|
self.transfer = transfer
|
||||||
|
self.mission = mission
|
||||||
|
self.settings = settings
|
||||||
|
|
||||||
|
def generate_logistics(self) -> LogisticsInfo:
|
||||||
|
# Add Logisitcs info for the flight
|
||||||
|
logistics_info = LogisticsInfo(
|
||||||
|
pilot_names=[u.name for u in self.group.units],
|
||||||
|
transport=self.flight.squadron.aircraft,
|
||||||
|
blue=self.flight.blue,
|
||||||
|
preload=self.flight.state.in_flight,
|
||||||
|
)
|
||||||
|
|
||||||
|
if isinstance(self.flight.flight_plan, AirAssaultFlightPlan):
|
||||||
|
# Preload fixed wing as they do not have a pickup zone
|
||||||
|
logistics_info.preload = logistics_info.preload or not self.flight.is_helo
|
||||||
|
# Create the Waypoint Zone used by CTLD
|
||||||
|
target_zone = f"{self.group.name}TARGET_ZONE"
|
||||||
|
self.mission.triggers.add_triggerzone(
|
||||||
|
self.flight.flight_plan.layout.target.position,
|
||||||
|
self.flight.flight_plan.engagement_distance.meters,
|
||||||
|
False,
|
||||||
|
target_zone,
|
||||||
|
)
|
||||||
|
logistics_info.target_zone = target_zone
|
||||||
|
|
||||||
|
pickup_point = None
|
||||||
|
for waypoint in self.flight.points:
|
||||||
|
if (
|
||||||
|
waypoint.waypoint_type
|
||||||
|
not in [
|
||||||
|
FlightWaypointType.PICKUP,
|
||||||
|
FlightWaypointType.DROP_OFF,
|
||||||
|
]
|
||||||
|
or waypoint.only_for_player
|
||||||
|
and not self.flight.client_count
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
# Create Pickup and DropOff zone
|
||||||
|
zone_name = f"{self.group.name}{waypoint.waypoint_type.name}"
|
||||||
|
self.mission.triggers.add_triggerzone(
|
||||||
|
waypoint.position, ZONE_RADIUS, False, zone_name
|
||||||
|
)
|
||||||
|
if waypoint.waypoint_type == FlightWaypointType.PICKUP:
|
||||||
|
pickup_point = waypoint.position
|
||||||
|
logistics_info.pickup_zone = zone_name
|
||||||
|
else:
|
||||||
|
logistics_info.drop_off_zone = zone_name
|
||||||
|
|
||||||
|
if self.transfer and self.flight.client_count > 0 and pickup_point is not None:
|
||||||
|
# Add spawnable crates for client airlifts
|
||||||
|
crate_location = pickup_point.random_point_within(
|
||||||
|
ZONE_RADIUS - CRATE_ZONE_RADIUS, CRATE_ZONE_RADIUS
|
||||||
|
)
|
||||||
|
crate_zone = f"{self.group.name}crate_spawn"
|
||||||
|
self.mission.triggers.add_triggerzone(
|
||||||
|
crate_location, CRATE_ZONE_RADIUS, False, crate_zone
|
||||||
|
)
|
||||||
|
logistics_info.cargo = [
|
||||||
|
CargoInfo(cargo_unit_type.dcs_id, crate_zone, amount)
|
||||||
|
for cargo_unit_type, amount in self.transfer.units.items()
|
||||||
|
]
|
||||||
|
|
||||||
|
if pickup_point is not None and self.settings.plugin_option(
|
||||||
|
"ctld.logisticunit"
|
||||||
|
):
|
||||||
|
# Spawn logisticsunit at pickup zones
|
||||||
|
country = self.mission.country(self.flight.country)
|
||||||
|
logistic_unit = self.mission.static_group(
|
||||||
|
country,
|
||||||
|
f"{self.group.name}logistic",
|
||||||
|
Fortification.FARP_Ammo_Dump_Coating,
|
||||||
|
pickup_point,
|
||||||
|
)
|
||||||
|
logistics_info.logistic_unit = logistic_unit.units[0].name
|
||||||
|
|
||||||
|
return logistics_info
|
||||||
@ -12,13 +12,13 @@ from dcs.translation import String
|
|||||||
from dcs.triggers import TriggerStart
|
from dcs.triggers import TriggerStart
|
||||||
|
|
||||||
from game.ato import FlightType
|
from game.ato import FlightType
|
||||||
|
from game.dcs.aircrafttype import AircraftType
|
||||||
from game.plugins import LuaPluginManager
|
from game.plugins import LuaPluginManager
|
||||||
from game.theater import TheaterGroundObject
|
from game.theater import TheaterGroundObject
|
||||||
from game.theater.iadsnetwork.iadsrole import IadsRole
|
from game.theater.iadsnetwork.iadsrole import IadsRole
|
||||||
from game.utils import escape_string_for_lua
|
from game.utils import escape_string_for_lua
|
||||||
|
|
||||||
from .aircraft.flightdata import FlightData
|
from .missiondata import MissionData
|
||||||
from .airsupport import AirSupport
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game import Game
|
from game import Game
|
||||||
@ -29,13 +29,11 @@ class LuaGenerator:
|
|||||||
self,
|
self,
|
||||||
game: Game,
|
game: Game,
|
||||||
mission: Mission,
|
mission: Mission,
|
||||||
air_support: AirSupport,
|
mission_data: MissionData,
|
||||||
flights: list[FlightData],
|
|
||||||
) -> None:
|
) -> None:
|
||||||
self.game = game
|
self.game = game
|
||||||
self.mission = mission
|
self.mission = mission
|
||||||
self.air_support = air_support
|
self.mission_data = mission_data
|
||||||
self.flights = flights
|
|
||||||
self.plugin_scripts: list[str] = []
|
self.plugin_scripts: list[str] = []
|
||||||
|
|
||||||
def generate(self) -> None:
|
def generate(self) -> None:
|
||||||
@ -49,10 +47,20 @@ class LuaGenerator:
|
|||||||
install_path.set_value(os.path.abspath("."))
|
install_path.set_value(os.path.abspath("."))
|
||||||
|
|
||||||
lua_data.add_item("Airbases")
|
lua_data.add_item("Airbases")
|
||||||
lua_data.add_item("Carriers")
|
carriers_object = lua_data.add_item("Carriers")
|
||||||
|
|
||||||
|
for carrier in self.mission_data.carriers:
|
||||||
|
carrier_item = carriers_object.add_item()
|
||||||
|
carrier_item.add_key_value("dcsGroupName", carrier.group_name)
|
||||||
|
carrier_item.add_key_value("unit_name", carrier.unit_name)
|
||||||
|
carrier_item.add_key_value("callsign", carrier.callsign)
|
||||||
|
carrier_item.add_key_value("radio", str(carrier.freq.mhz))
|
||||||
|
carrier_item.add_key_value(
|
||||||
|
"tacan", str(carrier.tacan.number) + carrier.tacan.band.name
|
||||||
|
)
|
||||||
|
|
||||||
tankers_object = lua_data.add_item("Tankers")
|
tankers_object = lua_data.add_item("Tankers")
|
||||||
for tanker in self.air_support.tankers:
|
for tanker in self.mission_data.tankers:
|
||||||
tanker_item = tankers_object.add_item()
|
tanker_item = tankers_object.add_item()
|
||||||
tanker_item.add_key_value("dcsGroupName", tanker.group_name)
|
tanker_item.add_key_value("dcsGroupName", tanker.group_name)
|
||||||
tanker_item.add_key_value("callsign", tanker.callsign)
|
tanker_item.add_key_value("callsign", tanker.callsign)
|
||||||
@ -63,14 +71,14 @@ class LuaGenerator:
|
|||||||
)
|
)
|
||||||
|
|
||||||
awacs_object = lua_data.add_item("AWACs")
|
awacs_object = lua_data.add_item("AWACs")
|
||||||
for awacs in self.air_support.awacs:
|
for awacs in self.mission_data.awacs:
|
||||||
awacs_item = awacs_object.add_item()
|
awacs_item = awacs_object.add_item()
|
||||||
awacs_item.add_key_value("dcsGroupName", awacs.group_name)
|
awacs_item.add_key_value("dcsGroupName", awacs.group_name)
|
||||||
awacs_item.add_key_value("callsign", awacs.callsign)
|
awacs_item.add_key_value("callsign", awacs.callsign)
|
||||||
awacs_item.add_key_value("radio", str(awacs.freq.mhz))
|
awacs_item.add_key_value("radio", str(awacs.freq.mhz))
|
||||||
|
|
||||||
jtacs_object = lua_data.add_item("JTACs")
|
jtacs_object = lua_data.add_item("JTACs")
|
||||||
for jtac in self.air_support.jtacs:
|
for jtac in self.mission_data.jtacs:
|
||||||
jtac_item = jtacs_object.add_item()
|
jtac_item = jtacs_object.add_item()
|
||||||
jtac_item.add_key_value("dcsGroupName", jtac.group_name)
|
jtac_item.add_key_value("dcsGroupName", jtac.group_name)
|
||||||
jtac_item.add_key_value("callsign", jtac.callsign)
|
jtac_item.add_key_value("callsign", jtac.callsign)
|
||||||
@ -81,16 +89,55 @@ class LuaGenerator:
|
|||||||
jtac_item.add_key_value("modulation", jtac.freq.modulation.name)
|
jtac_item.add_key_value("modulation", jtac.freq.modulation.name)
|
||||||
|
|
||||||
logistics_object = lua_data.add_item("Logistics")
|
logistics_object = lua_data.add_item("Logistics")
|
||||||
for logistic_info in self.air_support.logistics.values():
|
logistics_flights = logistics_object.add_item("flights")
|
||||||
logistics_item = logistics_object.add_item()
|
crates_object = logistics_object.add_item("crates")
|
||||||
|
spawnable_crates: dict[str, str] = {}
|
||||||
|
transports: list[AircraftType] = []
|
||||||
|
for logistic_info in self.mission_data.logistics:
|
||||||
|
if logistic_info.transport not in transports:
|
||||||
|
transports.append(logistic_info.transport)
|
||||||
|
coalition_color = "blue" if logistic_info.blue else "red"
|
||||||
|
logistics_item = logistics_flights.add_item()
|
||||||
logistics_item.add_data_array("pilot_names", logistic_info.pilot_names)
|
logistics_item.add_data_array("pilot_names", logistic_info.pilot_names)
|
||||||
logistics_item.add_key_value("pickup_zone", logistic_info.pickup_zone)
|
logistics_item.add_key_value("pickup_zone", logistic_info.pickup_zone)
|
||||||
logistics_item.add_key_value("drop_off_zone", logistic_info.drop_off_zone)
|
logistics_item.add_key_value("drop_off_zone", logistic_info.drop_off_zone)
|
||||||
logistics_item.add_key_value("target_zone", logistic_info.target_zone)
|
logistics_item.add_key_value("target_zone", logistic_info.target_zone)
|
||||||
logistics_item.add_key_value("side", str(2 if logistic_info.blue else 1))
|
logistics_item.add_key_value("side", str(2 if logistic_info.blue else 1))
|
||||||
|
logistics_item.add_key_value("logistic_unit", logistic_info.logistic_unit)
|
||||||
|
logistics_item.add_key_value(
|
||||||
|
"aircraft_type", logistic_info.transport.dcs_id
|
||||||
|
)
|
||||||
|
logistics_item.add_key_value(
|
||||||
|
"preload", "true" if logistic_info.preload else "false"
|
||||||
|
)
|
||||||
|
for cargo in logistic_info.cargo:
|
||||||
|
if cargo.unit_type not in spawnable_crates:
|
||||||
|
spawnable_crates[cargo.unit_type] = str(200 + len(spawnable_crates))
|
||||||
|
crate_weight = spawnable_crates[cargo.unit_type]
|
||||||
|
for i in range(cargo.amount):
|
||||||
|
cargo_item = crates_object.add_item()
|
||||||
|
cargo_item.add_key_value("weight", crate_weight)
|
||||||
|
cargo_item.add_key_value("coalition", coalition_color)
|
||||||
|
cargo_item.add_key_value("zone", cargo.spawn_zone)
|
||||||
|
transport_object = logistics_object.add_item("transports")
|
||||||
|
for transport in transports:
|
||||||
|
transport_item = transport_object.add_item()
|
||||||
|
transport_item.add_key_value("aircraft_type", transport.dcs_id)
|
||||||
|
transport_item.add_key_value("cabin_size", str(transport.cabin_size))
|
||||||
|
transport_item.add_key_value(
|
||||||
|
"troops", "true" if transport.cabin_size > 0 else "false"
|
||||||
|
)
|
||||||
|
transport_item.add_key_value(
|
||||||
|
"crates", "true" if transport.can_carry_crates else "false"
|
||||||
|
)
|
||||||
|
spawnable_crates_object = logistics_object.add_item("spawnable_crates")
|
||||||
|
for unit, weight in spawnable_crates.items():
|
||||||
|
crate_item = spawnable_crates_object.add_item()
|
||||||
|
crate_item.add_key_value("unit", unit)
|
||||||
|
crate_item.add_key_value("weight", weight)
|
||||||
|
|
||||||
target_points = lua_data.add_item("TargetPoints")
|
target_points = lua_data.add_item("TargetPoints")
|
||||||
for flight in self.flights:
|
for flight in self.mission_data.flights:
|
||||||
if flight.friendly and flight.flight_type in [
|
if flight.friendly and flight.flight_type in [
|
||||||
FlightType.ANTISHIP,
|
FlightType.ANTISHIP,
|
||||||
FlightType.DEAD,
|
FlightType.DEAD,
|
||||||
@ -225,6 +272,9 @@ class LuaItem(ABC):
|
|||||||
def set_value(self, value: str) -> None:
|
def set_value(self, value: str) -> None:
|
||||||
self.value = LuaValue(None, value)
|
self.value = LuaValue(None, value)
|
||||||
|
|
||||||
|
def set_data_array(self, values: list[str]) -> None:
|
||||||
|
self.value = LuaValue(None, values)
|
||||||
|
|
||||||
def add_data_array(self, key: str, values: list[str]) -> None:
|
def add_data_array(self, key: str, values: list[str]) -> None:
|
||||||
self._add_value(LuaValue(key, values))
|
self._add_value(LuaValue(key, values))
|
||||||
|
|
||||||
|
|||||||
96
game/missiongenerator/missiondata.py
Normal file
96
game/missiongenerator/missiondata.py
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from datetime import timedelta
|
||||||
|
from typing import Optional, TYPE_CHECKING
|
||||||
|
from game.dcs.aircrafttype import AircraftType
|
||||||
|
from game.missiongenerator.aircraft.flightdata import FlightData
|
||||||
|
|
||||||
|
from game.runways import RunwayData
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from game.radio.radios import RadioFrequency
|
||||||
|
from game.radio.tacan import TacanChannel
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class GroupInfo:
|
||||||
|
group_name: str
|
||||||
|
callsign: str
|
||||||
|
freq: RadioFrequency
|
||||||
|
blue: bool
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class UnitInfo(GroupInfo):
|
||||||
|
unit_name: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AwacsInfo(GroupInfo):
|
||||||
|
"""AWACS information for the kneeboard."""
|
||||||
|
|
||||||
|
depature_location: Optional[str]
|
||||||
|
start_time: Optional[timedelta]
|
||||||
|
end_time: Optional[timedelta]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TankerInfo(GroupInfo):
|
||||||
|
"""Tanker information for the kneeboard."""
|
||||||
|
|
||||||
|
variant: str
|
||||||
|
tacan: TacanChannel
|
||||||
|
start_time: Optional[timedelta]
|
||||||
|
end_time: Optional[timedelta]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class CarrierInfo(UnitInfo):
|
||||||
|
"""Carrier information."""
|
||||||
|
|
||||||
|
tacan: TacanChannel
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class JtacInfo(UnitInfo):
|
||||||
|
"""JTAC information."""
|
||||||
|
|
||||||
|
region: str
|
||||||
|
code: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class CargoInfo:
|
||||||
|
"""Cargo information."""
|
||||||
|
|
||||||
|
unit_type: str = field(default_factory=str)
|
||||||
|
spawn_zone: str = field(default_factory=str)
|
||||||
|
amount: int = field(default=1)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class LogisticsInfo:
|
||||||
|
"""Logistics information."""
|
||||||
|
|
||||||
|
pilot_names: list[str]
|
||||||
|
transport: AircraftType
|
||||||
|
blue: bool
|
||||||
|
|
||||||
|
logistic_unit: str = field(default_factory=str)
|
||||||
|
pickup_zone: str = field(default_factory=str)
|
||||||
|
drop_off_zone: str = field(default_factory=str)
|
||||||
|
target_zone: str = field(default_factory=str)
|
||||||
|
cargo: list[CargoInfo] = field(default_factory=list)
|
||||||
|
preload: bool = field(default=False)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MissionData:
|
||||||
|
awacs: list[AwacsInfo] = field(default_factory=list)
|
||||||
|
runways: list[RunwayData] = field(default_factory=list)
|
||||||
|
carriers: list[CarrierInfo] = field(default_factory=list)
|
||||||
|
flights: list[FlightData] = field(default_factory=list)
|
||||||
|
tankers: list[TankerInfo] = field(default_factory=list)
|
||||||
|
jtacs: list[JtacInfo] = field(default_factory=list)
|
||||||
|
logistics: list[LogisticsInfo] = field(default_factory=list)
|
||||||
@ -22,7 +22,7 @@ from game.theater import Airfield, FrontLine
|
|||||||
from game.theater.bullseye import Bullseye
|
from game.theater.bullseye import Bullseye
|
||||||
from game.unitmap import UnitMap
|
from game.unitmap import UnitMap
|
||||||
from .aircraft.flightdata import FlightData
|
from .aircraft.flightdata import FlightData
|
||||||
from .airsupport import AirSupport
|
from .missiondata import MissionData
|
||||||
from .airsupportgenerator import AirSupportGenerator
|
from .airsupportgenerator import AirSupportGenerator
|
||||||
from .beacons import load_beacons_for_terrain
|
from .beacons import load_beacons_for_terrain
|
||||||
from .briefinggenerator import BriefingGenerator, MissionInfoGenerator
|
from .briefinggenerator import BriefingGenerator, MissionInfoGenerator
|
||||||
@ -61,7 +61,7 @@ class MissionGenerator:
|
|||||||
self.mission = Mission(game.theater.terrain)
|
self.mission = Mission(game.theater.terrain)
|
||||||
self.unit_map = UnitMap()
|
self.unit_map = UnitMap()
|
||||||
|
|
||||||
self.air_support = AirSupport()
|
self.mission_data = MissionData()
|
||||||
|
|
||||||
self.laser_code_registry = LaserCodeRegistry()
|
self.laser_code_registry = LaserCodeRegistry()
|
||||||
self.radio_registry = RadioRegistry()
|
self.radio_registry = RadioRegistry()
|
||||||
@ -92,6 +92,7 @@ class MissionGenerator:
|
|||||||
self.radio_registry,
|
self.radio_registry,
|
||||||
self.tacan_registry,
|
self.tacan_registry,
|
||||||
self.unit_map,
|
self.unit_map,
|
||||||
|
self.mission_data,
|
||||||
)
|
)
|
||||||
tgo_generator.generate()
|
tgo_generator.generate()
|
||||||
|
|
||||||
@ -103,17 +104,17 @@ class MissionGenerator:
|
|||||||
# Generate ground conflicts first so the JTACs get the first laser code (1688)
|
# Generate ground conflicts first so the JTACs get the first laser code (1688)
|
||||||
# rather than the first player flight with a TGP.
|
# rather than the first player flight with a TGP.
|
||||||
self.generate_ground_conflicts()
|
self.generate_ground_conflicts()
|
||||||
air_support, flights = self.generate_air_units(tgo_generator)
|
self.generate_air_units(tgo_generator)
|
||||||
|
|
||||||
TriggerGenerator(self.mission, self.game).generate()
|
TriggerGenerator(self.mission, self.game).generate()
|
||||||
ForcedOptionsGenerator(self.mission, self.game).generate()
|
ForcedOptionsGenerator(self.mission, self.game).generate()
|
||||||
VisualsGenerator(self.mission, self.game).generate()
|
VisualsGenerator(self.mission, self.game).generate()
|
||||||
LuaGenerator(self.game, self.mission, air_support, flights).generate()
|
LuaGenerator(self.game, self.mission, self.mission_data).generate()
|
||||||
DrawingsGenerator(self.mission, self.game).generate()
|
DrawingsGenerator(self.mission, self.game).generate()
|
||||||
|
|
||||||
self.setup_combined_arms()
|
self.setup_combined_arms()
|
||||||
|
|
||||||
self.notify_info_generators(tgo_generator, air_support, flights)
|
self.notify_info_generators()
|
||||||
|
|
||||||
# TODO: Shouldn't this be first?
|
# TODO: Shouldn't this be first?
|
||||||
namegen.reset_numbers()
|
namegen.reset_numbers()
|
||||||
@ -217,14 +218,12 @@ class MissionGenerator:
|
|||||||
enemy_cp.stances[player_cp.id],
|
enemy_cp.stances[player_cp.id],
|
||||||
self.unit_map,
|
self.unit_map,
|
||||||
self.radio_registry,
|
self.radio_registry,
|
||||||
self.air_support,
|
self.mission_data,
|
||||||
self.laser_code_registry,
|
self.laser_code_registry,
|
||||||
)
|
)
|
||||||
ground_conflict_gen.generate()
|
ground_conflict_gen.generate()
|
||||||
|
|
||||||
def generate_air_units(
|
def generate_air_units(self, tgo_generator: TgoGenerator) -> None:
|
||||||
self, tgo_generator: TgoGenerator
|
|
||||||
) -> tuple[AirSupport, list[FlightData]]:
|
|
||||||
"""Generate the air units for the Operation"""
|
"""Generate the air units for the Operation"""
|
||||||
|
|
||||||
# Air Support (Tanker & Awacs)
|
# Air Support (Tanker & Awacs)
|
||||||
@ -234,7 +233,7 @@ class MissionGenerator:
|
|||||||
self.game,
|
self.game,
|
||||||
self.radio_registry,
|
self.radio_registry,
|
||||||
self.tacan_registry,
|
self.tacan_registry,
|
||||||
self.air_support,
|
self.mission_data,
|
||||||
)
|
)
|
||||||
air_support_generator.generate()
|
air_support_generator.generate()
|
||||||
|
|
||||||
@ -248,7 +247,7 @@ class MissionGenerator:
|
|||||||
self.tacan_registry,
|
self.tacan_registry,
|
||||||
self.laser_code_registry,
|
self.laser_code_registry,
|
||||||
self.unit_map,
|
self.unit_map,
|
||||||
air_support=air_support_generator.air_support,
|
mission_data=air_support_generator.mission_data,
|
||||||
helipads=tgo_generator.helipads,
|
helipads=tgo_generator.helipads,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -273,10 +272,10 @@ class MissionGenerator:
|
|||||||
if not flight.client_units:
|
if not flight.client_units:
|
||||||
continue
|
continue
|
||||||
flight.aircraft_type.assign_channels_for_flight(
|
flight.aircraft_type.assign_channels_for_flight(
|
||||||
flight, air_support_generator.air_support
|
flight, air_support_generator.mission_data
|
||||||
)
|
)
|
||||||
|
|
||||||
return air_support_generator.air_support, aircraft_generator.flights
|
self.mission_data.flights = aircraft_generator.flights
|
||||||
|
|
||||||
def generate_destroyed_units(self) -> None:
|
def generate_destroyed_units(self) -> None:
|
||||||
"""Add destroyed units to the Mission"""
|
"""Add destroyed units to the Mission"""
|
||||||
@ -325,33 +324,30 @@ class MissionGenerator:
|
|||||||
|
|
||||||
def notify_info_generators(
|
def notify_info_generators(
|
||||||
self,
|
self,
|
||||||
tgo_generator: TgoGenerator,
|
|
||||||
air_support: AirSupport,
|
|
||||||
flights: list[FlightData],
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Generates subscribed MissionInfoGenerator objects."""
|
"""Generates subscribed MissionInfoGenerator objects."""
|
||||||
|
mission_data = self.mission_data
|
||||||
gens: list[MissionInfoGenerator] = [
|
gens: list[MissionInfoGenerator] = [
|
||||||
KneeboardGenerator(self.mission, self.game),
|
KneeboardGenerator(self.mission, self.game),
|
||||||
BriefingGenerator(self.mission, self.game),
|
BriefingGenerator(self.mission, self.game),
|
||||||
]
|
]
|
||||||
for gen in gens:
|
for gen in gens:
|
||||||
for dynamic_runway in tgo_generator.runways.values():
|
for dynamic_runway in mission_data.runways:
|
||||||
gen.add_dynamic_runway(dynamic_runway)
|
gen.add_dynamic_runway(dynamic_runway)
|
||||||
|
|
||||||
for tanker in air_support.tankers:
|
for tanker in mission_data.tankers:
|
||||||
if tanker.blue:
|
if tanker.blue:
|
||||||
gen.add_tanker(tanker)
|
gen.add_tanker(tanker)
|
||||||
|
|
||||||
for aewc in air_support.awacs:
|
for aewc in mission_data.awacs:
|
||||||
if aewc.blue:
|
if aewc.blue:
|
||||||
gen.add_awacs(aewc)
|
gen.add_awacs(aewc)
|
||||||
|
|
||||||
for jtac in air_support.jtacs:
|
for jtac in mission_data.jtacs:
|
||||||
if jtac.blue:
|
if jtac.blue:
|
||||||
gen.add_jtac(jtac)
|
gen.add_jtac(jtac)
|
||||||
|
|
||||||
for flight in flights:
|
for flight in mission_data.flights:
|
||||||
gen.add_flight(flight)
|
gen.add_flight(flight)
|
||||||
gen.generate()
|
gen.generate()
|
||||||
|
|
||||||
|
|||||||
@ -41,6 +41,7 @@ from dcs.unit import Unit, InvisibleFARP
|
|||||||
from dcs.unitgroup import MovingGroup, ShipGroup, StaticGroup, VehicleGroup
|
from dcs.unitgroup import MovingGroup, ShipGroup, StaticGroup, VehicleGroup
|
||||||
from dcs.unittype import ShipType, VehicleType
|
from dcs.unittype import ShipType, VehicleType
|
||||||
from dcs.vehicles import vehicle_map
|
from dcs.vehicles import vehicle_map
|
||||||
|
from game.missiongenerator.missiondata import CarrierInfo, MissionData
|
||||||
|
|
||||||
from game.radio.radios import RadioFrequency, RadioRegistry
|
from game.radio.radios import RadioFrequency, RadioRegistry
|
||||||
from game.radio.tacan import TacanBand, TacanChannel, TacanRegistry, TacanUsage
|
from game.radio.tacan import TacanBand, TacanChannel, TacanRegistry, TacanUsage
|
||||||
@ -351,6 +352,7 @@ class GenericCarrierGenerator(GroundObjectGenerator):
|
|||||||
icls_alloc: Iterator[int],
|
icls_alloc: Iterator[int],
|
||||||
runways: Dict[str, RunwayData],
|
runways: Dict[str, RunwayData],
|
||||||
unit_map: UnitMap,
|
unit_map: UnitMap,
|
||||||
|
mission_data: MissionData,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(ground_object, country, game, mission, unit_map)
|
super().__init__(ground_object, country, game, mission, unit_map)
|
||||||
self.ground_object = ground_object
|
self.ground_object = ground_object
|
||||||
@ -359,6 +361,7 @@ class GenericCarrierGenerator(GroundObjectGenerator):
|
|||||||
self.tacan_registry = tacan_registry
|
self.tacan_registry = tacan_registry
|
||||||
self.icls_alloc = icls_alloc
|
self.icls_alloc = icls_alloc
|
||||||
self.runways = runways
|
self.runways = runways
|
||||||
|
self.mission_data = mission_data
|
||||||
|
|
||||||
def generate(self) -> None:
|
def generate(self) -> None:
|
||||||
|
|
||||||
@ -400,6 +403,16 @@ class GenericCarrierGenerator(GroundObjectGenerator):
|
|||||||
self.add_runway_data(
|
self.add_runway_data(
|
||||||
brc or Heading.from_degrees(0), atc, tacan, tacan_callsign, icls
|
brc or Heading.from_degrees(0), atc, tacan, tacan_callsign, icls
|
||||||
)
|
)
|
||||||
|
self.mission_data.carriers.append(
|
||||||
|
CarrierInfo(
|
||||||
|
group_name=ship_group.name,
|
||||||
|
unit_name=ship_group.units[0].name,
|
||||||
|
callsign=tacan_callsign,
|
||||||
|
freq=atc,
|
||||||
|
tacan=tacan,
|
||||||
|
blue=self.control_point.captured,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def carrier_type(self) -> Optional[Type[ShipType]]:
|
def carrier_type(self) -> Optional[Type[ShipType]]:
|
||||||
@ -604,6 +617,7 @@ class TgoGenerator:
|
|||||||
radio_registry: RadioRegistry,
|
radio_registry: RadioRegistry,
|
||||||
tacan_registry: TacanRegistry,
|
tacan_registry: TacanRegistry,
|
||||||
unit_map: UnitMap,
|
unit_map: UnitMap,
|
||||||
|
mission_data: MissionData,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.m = mission
|
self.m = mission
|
||||||
self.game = game
|
self.game = game
|
||||||
@ -613,6 +627,7 @@ class TgoGenerator:
|
|||||||
self.icls_alloc = iter(range(1, 21))
|
self.icls_alloc = iter(range(1, 21))
|
||||||
self.runways: Dict[str, RunwayData] = {}
|
self.runways: Dict[str, RunwayData] = {}
|
||||||
self.helipads: dict[ControlPoint, StaticGroup] = {}
|
self.helipads: dict[ControlPoint, StaticGroup] = {}
|
||||||
|
self.mission_data = mission_data
|
||||||
|
|
||||||
def generate(self) -> None:
|
def generate(self) -> None:
|
||||||
for cp in self.game.theater.controlpoints:
|
for cp in self.game.theater.controlpoints:
|
||||||
@ -640,6 +655,7 @@ class TgoGenerator:
|
|||||||
self.icls_alloc,
|
self.icls_alloc,
|
||||||
self.runways,
|
self.runways,
|
||||||
self.unit_map,
|
self.unit_map,
|
||||||
|
self.mission_data,
|
||||||
)
|
)
|
||||||
elif isinstance(ground_object, LhaGroundObject):
|
elif isinstance(ground_object, LhaGroundObject):
|
||||||
generator = LhaGenerator(
|
generator = LhaGenerator(
|
||||||
@ -653,6 +669,7 @@ class TgoGenerator:
|
|||||||
self.icls_alloc,
|
self.icls_alloc,
|
||||||
self.runways,
|
self.runways,
|
||||||
self.unit_map,
|
self.unit_map,
|
||||||
|
self.mission_data,
|
||||||
)
|
)
|
||||||
elif isinstance(ground_object, MissileSiteGroundObject):
|
elif isinstance(ground_object, MissileSiteGroundObject):
|
||||||
generator = MissileSiteGenerator(
|
generator = MissileSiteGenerator(
|
||||||
@ -663,3 +680,4 @@ class TgoGenerator:
|
|||||||
ground_object, country, self.game, self.m, self.unit_map
|
ground_object, country, self.game, self.m, self.unit_map
|
||||||
)
|
)
|
||||||
generator.generate()
|
generator.generate()
|
||||||
|
self.mission_data.runways = list(self.runways.values())
|
||||||
|
|||||||
@ -5,14 +5,14 @@ from typing import Optional, Any, TYPE_CHECKING
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game.missiongenerator.aircraft.flightdata import FlightData
|
from game.missiongenerator.aircraft.flightdata import FlightData
|
||||||
from game.missiongenerator.airsupport import AirSupport
|
from game.missiongenerator.missiondata import MissionData
|
||||||
|
|
||||||
|
|
||||||
class RadioChannelAllocator:
|
class RadioChannelAllocator:
|
||||||
"""Base class for radio channel allocators."""
|
"""Base class for radio channel allocators."""
|
||||||
|
|
||||||
def assign_channels_for_flight(
|
def assign_channels_for_flight(
|
||||||
self, flight: FlightData, air_support: AirSupport
|
self, flight: FlightData, mission_data: MissionData
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Assigns mission frequencies to preset channels for the flight."""
|
"""Assigns mission frequencies to preset channels for the flight."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
@ -44,7 +44,7 @@ class CommonRadioChannelAllocator(RadioChannelAllocator):
|
|||||||
intra_flight_radio_index: Optional[int]
|
intra_flight_radio_index: Optional[int]
|
||||||
|
|
||||||
def assign_channels_for_flight(
|
def assign_channels_for_flight(
|
||||||
self, flight: FlightData, air_support: AirSupport
|
self, flight: FlightData, mission_data: MissionData
|
||||||
) -> None:
|
) -> None:
|
||||||
if self.intra_flight_radio_index is not None:
|
if self.intra_flight_radio_index is not None:
|
||||||
flight.assign_channel(
|
flight.assign_channel(
|
||||||
@ -70,10 +70,10 @@ class CommonRadioChannelAllocator(RadioChannelAllocator):
|
|||||||
flight.assign_channel(radio_id, next(channel_alloc), flight.departure.atc)
|
flight.assign_channel(radio_id, next(channel_alloc), flight.departure.atc)
|
||||||
|
|
||||||
# TODO: If there ever are multiple AWACS, limit to mission relevant.
|
# TODO: If there ever are multiple AWACS, limit to mission relevant.
|
||||||
for awacs in air_support.awacs:
|
for awacs in mission_data.awacs:
|
||||||
flight.assign_channel(radio_id, next(channel_alloc), awacs.freq)
|
flight.assign_channel(radio_id, next(channel_alloc), awacs.freq)
|
||||||
|
|
||||||
for jtac in air_support.jtacs:
|
for jtac in mission_data.jtacs:
|
||||||
flight.assign_channel(radio_id, next(channel_alloc), jtac.freq)
|
flight.assign_channel(radio_id, next(channel_alloc), jtac.freq)
|
||||||
|
|
||||||
if flight.arrival != flight.departure and flight.arrival.atc is not None:
|
if flight.arrival != flight.departure and flight.arrival.atc is not None:
|
||||||
@ -81,7 +81,7 @@ class CommonRadioChannelAllocator(RadioChannelAllocator):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# TODO: Skip incompatible tankers.
|
# TODO: Skip incompatible tankers.
|
||||||
for tanker in air_support.tankers:
|
for tanker in mission_data.tankers:
|
||||||
flight.assign_channel(radio_id, next(channel_alloc), tanker.freq)
|
flight.assign_channel(radio_id, next(channel_alloc), tanker.freq)
|
||||||
|
|
||||||
if flight.divert is not None and flight.divert.atc is not None:
|
if flight.divert is not None and flight.divert.atc is not None:
|
||||||
@ -108,7 +108,7 @@ class NoOpChannelAllocator(RadioChannelAllocator):
|
|||||||
"""Channel allocator for aircraft that don't support preset channels."""
|
"""Channel allocator for aircraft that don't support preset channels."""
|
||||||
|
|
||||||
def assign_channels_for_flight(
|
def assign_channels_for_flight(
|
||||||
self, flight: FlightData, air_support: AirSupport
|
self, flight: FlightData, mission_data: MissionData
|
||||||
) -> None:
|
) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -122,7 +122,7 @@ class FarmerRadioChannelAllocator(RadioChannelAllocator):
|
|||||||
"""Preset channel allocator for the MiG-19P."""
|
"""Preset channel allocator for the MiG-19P."""
|
||||||
|
|
||||||
def assign_channels_for_flight(
|
def assign_channels_for_flight(
|
||||||
self, flight: FlightData, air_support: AirSupport
|
self, flight: FlightData, mission_data: MissionData
|
||||||
) -> None:
|
) -> None:
|
||||||
# The Farmer only has 6 preset channels. It also only has a VHF radio,
|
# The Farmer only has 6 preset channels. It also only has a VHF radio,
|
||||||
# and currently our ATC data and AWACS are only in the UHF band.
|
# and currently our ATC data and AWACS are only in the UHF band.
|
||||||
@ -141,7 +141,7 @@ class ViggenRadioChannelAllocator(RadioChannelAllocator):
|
|||||||
"""Preset channel allocator for the AJS37."""
|
"""Preset channel allocator for the AJS37."""
|
||||||
|
|
||||||
def assign_channels_for_flight(
|
def assign_channels_for_flight(
|
||||||
self, flight: FlightData, air_support: AirSupport
|
self, flight: FlightData, mission_data: MissionData
|
||||||
) -> None:
|
) -> None:
|
||||||
# The Viggen's preset channels are handled differently from other
|
# The Viggen's preset channels are handled differently from other
|
||||||
# aircraft. Since 2.7.9 the group channels will not be generated automatically
|
# aircraft. Since 2.7.9 the group channels will not be generated automatically
|
||||||
@ -161,10 +161,10 @@ class ViggenRadioChannelAllocator(RadioChannelAllocator):
|
|||||||
radio_id, next(channel_alloc), flight.intra_flight_channel
|
radio_id, next(channel_alloc), flight.intra_flight_channel
|
||||||
)
|
)
|
||||||
|
|
||||||
for awacs in air_support.awacs:
|
for awacs in mission_data.awacs:
|
||||||
flight.assign_channel(radio_id, next(channel_alloc), awacs.freq)
|
flight.assign_channel(radio_id, next(channel_alloc), awacs.freq)
|
||||||
|
|
||||||
for jtac in air_support.jtacs:
|
for jtac in mission_data.jtacs:
|
||||||
flight.assign_channel(radio_id, next(channel_alloc), jtac.freq)
|
flight.assign_channel(radio_id, next(channel_alloc), jtac.freq)
|
||||||
|
|
||||||
if flight.departure.atc is not None:
|
if flight.departure.atc is not None:
|
||||||
@ -184,7 +184,7 @@ class SCR522RadioChannelAllocator(RadioChannelAllocator):
|
|||||||
"""Preset channel allocator for the SCR522 WW2 radios. (4 channels)"""
|
"""Preset channel allocator for the SCR522 WW2 radios. (4 channels)"""
|
||||||
|
|
||||||
def assign_channels_for_flight(
|
def assign_channels_for_flight(
|
||||||
self, flight: FlightData, air_support: AirSupport
|
self, flight: FlightData, mission_data: MissionData
|
||||||
) -> None:
|
) -> None:
|
||||||
radio_id = 1
|
radio_id = 1
|
||||||
flight.assign_channel(radio_id, 1, flight.intra_flight_channel)
|
flight.assign_channel(radio_id, 1, flight.intra_flight_channel)
|
||||||
|
|||||||
@ -4,6 +4,7 @@ from fastapi import APIRouter, Depends
|
|||||||
from shapely.geometry import LineString, Point as ShapelyPoint
|
from shapely.geometry import LineString, Point as ShapelyPoint
|
||||||
|
|
||||||
from game import Game
|
from game import Game
|
||||||
|
from game.ato.flightplans.airassault import AirAssaultFlightPlan
|
||||||
from game.ato.flightplans.cas import CasFlightPlan
|
from game.ato.flightplans.cas import CasFlightPlan
|
||||||
from game.ato.flightplans.patrolling import PatrollingFlightPlan
|
from game.ato.flightplans.patrolling import PatrollingFlightPlan
|
||||||
from game.server import GameContext
|
from game.server import GameContext
|
||||||
@ -39,19 +40,23 @@ def commit_boundary(
|
|||||||
flight_id: UUID, game: Game = Depends(GameContext.require)
|
flight_id: UUID, game: Game = Depends(GameContext.require)
|
||||||
) -> LeafletPoly:
|
) -> LeafletPoly:
|
||||||
flight = game.db.flights.get(flight_id)
|
flight = game.db.flights.get(flight_id)
|
||||||
if not isinstance(flight.flight_plan, PatrollingFlightPlan):
|
if isinstance(flight.flight_plan, CasFlightPlan) or isinstance(
|
||||||
return []
|
flight.flight_plan, AirAssaultFlightPlan
|
||||||
start = flight.flight_plan.layout.patrol_start
|
):
|
||||||
end = flight.flight_plan.layout.patrol_end
|
# Special Commit boundary for CAS and AirAssault
|
||||||
if isinstance(flight.flight_plan, CasFlightPlan):
|
|
||||||
center = flight.flight_plan.layout.target.position
|
center = flight.flight_plan.layout.target.position
|
||||||
commit_center = ShapelyPoint(center.x, center.y)
|
commit_center = ShapelyPoint(center.x, center.y)
|
||||||
else:
|
elif isinstance(flight.flight_plan, PatrollingFlightPlan):
|
||||||
|
# Commit boundary for standard patrolling flight plan
|
||||||
|
start = flight.flight_plan.layout.patrol_start
|
||||||
|
end = flight.flight_plan.layout.patrol_end
|
||||||
commit_center = LineString(
|
commit_center = LineString(
|
||||||
[
|
[
|
||||||
ShapelyPoint(start.x, start.y),
|
ShapelyPoint(start.x, start.y),
|
||||||
ShapelyPoint(end.x, end.y),
|
ShapelyPoint(end.x, end.y),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
return []
|
||||||
bubble = commit_center.buffer(flight.flight_plan.engagement_distance.meters)
|
bubble = commit_center.buffer(flight.flight_plan.engagement_distance.meters)
|
||||||
return ShapelyUtil.poly_to_leaflet(bubble, game.theater)
|
return ShapelyUtil.poly_to_leaflet(bubble, game.theater)
|
||||||
|
|||||||
@ -1078,6 +1078,7 @@ class Airfield(ControlPoint):
|
|||||||
yield from [
|
yield from [
|
||||||
FlightType.OCA_AIRCRAFT,
|
FlightType.OCA_AIRCRAFT,
|
||||||
FlightType.OCA_RUNWAY,
|
FlightType.OCA_RUNWAY,
|
||||||
|
FlightType.AIR_ASSAULT,
|
||||||
]
|
]
|
||||||
|
|
||||||
yield from super().mission_types(for_player)
|
yield from super().mission_types(for_player)
|
||||||
@ -1394,6 +1395,7 @@ class Fob(ControlPoint):
|
|||||||
|
|
||||||
if not self.is_friendly(for_player):
|
if not self.is_friendly(for_player):
|
||||||
yield FlightType.STRIKE
|
yield FlightType.STRIKE
|
||||||
|
yield FlightType.AIR_ASSAULT
|
||||||
|
|
||||||
yield from super().mission_types(for_player)
|
yield from super().mission_types(for_player)
|
||||||
|
|
||||||
|
|||||||
@ -98,6 +98,8 @@ class TransferOrder:
|
|||||||
|
|
||||||
transport: Optional[Transport] = field(default=None)
|
transport: Optional[Transport] = field(default=None)
|
||||||
|
|
||||||
|
request_airflift: bool = field(default=False)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
"""Returns the text that should be displayed for the transfer."""
|
"""Returns the text that should be displayed for the transfer."""
|
||||||
count = self.size
|
count = self.size
|
||||||
@ -317,6 +319,7 @@ class AirliftPlanner:
|
|||||||
):
|
):
|
||||||
self.create_airlift_flight(squadron)
|
self.create_airlift_flight(squadron)
|
||||||
if self.package.flights:
|
if self.package.flights:
|
||||||
|
self.package.set_tot_asap()
|
||||||
self.game.ato_for(self.for_player).add_package(self.package)
|
self.game.ato_for(self.for_player).add_package(self.package)
|
||||||
|
|
||||||
def create_airlift_flight(self, squadron: Squadron) -> int:
|
def create_airlift_flight(self, squadron: Squadron) -> int:
|
||||||
@ -585,14 +588,17 @@ class PendingTransfers:
|
|||||||
network = self.network_for(transfer.position)
|
network = self.network_for(transfer.position)
|
||||||
path = network.shortest_path_between(transfer.position, transfer.destination)
|
path = network.shortest_path_between(transfer.position, transfer.destination)
|
||||||
next_stop = path[0]
|
next_stop = path[0]
|
||||||
if network.link_type(transfer.position, next_stop) == TransitConnection.Road:
|
if not transfer.request_airflift:
|
||||||
self.convoys.add(transfer, next_stop)
|
if (
|
||||||
|
network.link_type(transfer.position, next_stop)
|
||||||
|
== TransitConnection.Road
|
||||||
|
):
|
||||||
|
return self.convoys.add(transfer, next_stop)
|
||||||
elif (
|
elif (
|
||||||
network.link_type(transfer.position, next_stop)
|
network.link_type(transfer.position, next_stop)
|
||||||
== TransitConnection.Shipping
|
== TransitConnection.Shipping
|
||||||
):
|
):
|
||||||
self.cargo_ships.add(transfer, next_stop)
|
return self.cargo_ships.add(transfer, next_stop)
|
||||||
else:
|
|
||||||
AirliftPlanner(self.game, transfer, next_stop).create_package_for_airlift()
|
AirliftPlanner(self.game, transfer, next_stop).create_package_for_airlift()
|
||||||
|
|
||||||
def new_transfer(self, transfer: TransferOrder) -> None:
|
def new_transfer(self, transfer: TransferOrder) -> None:
|
||||||
@ -774,3 +780,13 @@ class PendingTransfers:
|
|||||||
self.game.coalition_for(self.player).add_procurement_request(
|
self.game.coalition_for(self.player).add_procurement_request(
|
||||||
AircraftProcurementRequest(control_point, FlightType.TRANSPORT, gap)
|
AircraftProcurementRequest(control_point, FlightType.TRANSPORT, gap)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def transfer_for_flight(self, flight: Flight) -> Optional[TransferOrder]:
|
||||||
|
for transfer in self.pending_transfers:
|
||||||
|
if transfer.transport is None or not isinstance(
|
||||||
|
transfer.transport, Airlift
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
if transfer.transport.flight == flight:
|
||||||
|
return transfer
|
||||||
|
return None
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
"""Combo box for selecting a flight's task type."""
|
"""Combo box for selecting a flight's task type."""
|
||||||
|
|
||||||
from PySide2.QtWidgets import QComboBox
|
from PySide2.QtWidgets import QComboBox
|
||||||
|
from game.ato.flighttype import FlightType
|
||||||
|
from game.settings.settings import Settings
|
||||||
|
|
||||||
from game.theater import ConflictTheater, MissionTarget
|
from game.theater import ConflictTheater, MissionTarget
|
||||||
|
|
||||||
@ -8,9 +10,16 @@ from game.theater import ConflictTheater, MissionTarget
|
|||||||
class QFlightTypeComboBox(QComboBox):
|
class QFlightTypeComboBox(QComboBox):
|
||||||
"""Combo box for selecting a flight task type."""
|
"""Combo box for selecting a flight task type."""
|
||||||
|
|
||||||
def __init__(self, theater: ConflictTheater, target: MissionTarget) -> None:
|
def __init__(
|
||||||
|
self, theater: ConflictTheater, target: MissionTarget, settings: Settings
|
||||||
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.theater = theater
|
self.theater = theater
|
||||||
self.target = target
|
self.target = target
|
||||||
for mission_type in self.target.mission_types(for_player=True):
|
for mission_type in self.target.mission_types(for_player=True):
|
||||||
|
if mission_type == FlightType.AIR_ASSAULT and not settings.plugin_option(
|
||||||
|
"ctld"
|
||||||
|
):
|
||||||
|
# Only add Air Assault if ctld plugin is enabled
|
||||||
|
continue
|
||||||
self.addItem(str(mission_type), userData=mission_type)
|
self.addItem(str(mission_type), userData=mission_type)
|
||||||
|
|||||||
@ -86,7 +86,11 @@ class TransferOptionsPanel(QVBoxLayout):
|
|||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
self.source_combo_box = TransferDestinationComboBox(game, origin)
|
self.source_combo_box = TransferDestinationComboBox(game, origin)
|
||||||
|
self.transport_type = QComboBox()
|
||||||
|
self.transport_type.addItem("Auto", "auto")
|
||||||
|
self.transport_type.addItem("Airlift", "airlift")
|
||||||
self.addLayout(QLabeledWidget("Destination:", self.source_combo_box))
|
self.addLayout(QLabeledWidget("Destination:", self.source_combo_box))
|
||||||
|
self.addLayout(QLabeledWidget("Requested transport type:", self.transport_type))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def changed(self):
|
def changed(self):
|
||||||
@ -96,6 +100,10 @@ class TransferOptionsPanel(QVBoxLayout):
|
|||||||
def current(self) -> ControlPoint:
|
def current(self) -> ControlPoint:
|
||||||
return self.source_combo_box.currentData()
|
return self.source_combo_box.currentData()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def request_airlift(self) -> bool:
|
||||||
|
return self.transport_type.currentData() == "airlift"
|
||||||
|
|
||||||
|
|
||||||
class TransferControls(QGroupBox):
|
class TransferControls(QGroupBox):
|
||||||
def __init__(
|
def __init__(
|
||||||
@ -293,6 +301,7 @@ class NewUnitTransferDialog(QDialog):
|
|||||||
origin=self.origin,
|
origin=self.origin,
|
||||||
destination=destination,
|
destination=destination,
|
||||||
units=transfers,
|
units=transfers,
|
||||||
|
request_airflift=self.dest_panel.request_airlift,
|
||||||
)
|
)
|
||||||
self.game_model.transfer_model.new_transfer(transfer)
|
self.game_model.transfer_model.new_transfer(transfer)
|
||||||
self.close()
|
self.close()
|
||||||
|
|||||||
@ -50,7 +50,9 @@ class QFlightCreator(QDialog):
|
|||||||
|
|
||||||
layout = QVBoxLayout()
|
layout = QVBoxLayout()
|
||||||
|
|
||||||
self.task_selector = QFlightTypeComboBox(self.game.theater, package.target)
|
self.task_selector = QFlightTypeComboBox(
|
||||||
|
self.game.theater, package.target, self.game.settings
|
||||||
|
)
|
||||||
self.task_selector.setCurrentIndex(0)
|
self.task_selector.setCurrentIndex(0)
|
||||||
self.task_selector.currentIndexChanged.connect(self.on_task_changed)
|
self.task_selector.currentIndexChanged.connect(self.on_task_changed)
|
||||||
layout.addLayout(QLabeledWidget("Task:", self.task_selector))
|
layout.addLayout(QLabeledWidget("Task:", self.task_selector))
|
||||||
|
|||||||
@ -62,6 +62,12 @@ class QFlightWaypointTab(QFrame):
|
|||||||
self.recreate_buttons.clear()
|
self.recreate_buttons.clear()
|
||||||
for task in self.package.target.mission_types(for_player=True):
|
for task in self.package.target.mission_types(for_player=True):
|
||||||
|
|
||||||
|
if task == FlightType.AIR_ASSAULT and not self.game.settings.plugin_option(
|
||||||
|
"ctld"
|
||||||
|
):
|
||||||
|
# Only add Air Assault if ctld plugin is enabled
|
||||||
|
continue
|
||||||
|
|
||||||
def make_closure(arg):
|
def make_closure(arg):
|
||||||
def closure():
|
def closure():
|
||||||
return self.confirm_recreate(arg)
|
return self.confirm_recreate(arg)
|
||||||
|
|||||||
@ -136,6 +136,26 @@ local unitPayloads = {
|
|||||||
[4] = 16,
|
[4] = 16,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
[6] = {
|
||||||
|
["displayName"] = "Liberation Air Assault",
|
||||||
|
["name"] = "Liberation Air Assault",
|
||||||
|
["pylons"] = {
|
||||||
|
[1] = {
|
||||||
|
["CLSID"] = "M60_SIDE_R",
|
||||||
|
["num"] = 4,
|
||||||
|
},
|
||||||
|
[2] = {
|
||||||
|
["CLSID"] = "M60_SIDE_L",
|
||||||
|
["num"] = 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
["tasks"] = {
|
||||||
|
[1] = 32,
|
||||||
|
[2] = 31,
|
||||||
|
[3] = 35,
|
||||||
|
[4] = 16,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
["unitType"] = "UH-1H",
|
["unitType"] = "UH-1H",
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,22 @@
|
|||||||
-- see https://github.com/dcs-liberation/dcs_liberation
|
-- see https://github.com/dcs-liberation/dcs_liberation
|
||||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function spawn_crates()
|
||||||
|
--- CrateSpawn script which needs to be run after CTLD was initialized (3s delay)
|
||||||
|
env.info("DCSLiberation|CTLD plugin - Spawn crates")
|
||||||
|
for _, crate in pairs(dcsLiberation.Logistics.crates) do
|
||||||
|
ctld.spawnCrateAtZone(crate.coalition, tonumber(crate.weight), crate.zone)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function preload_troops(preload_data)
|
||||||
|
--- Troop loading script which needs to be run after CTLD was initialized (5s delay)
|
||||||
|
env.info(string.format("DCSLiberation|CTLD plugin - Preloading Troops into %s", preload_data["unit"]))
|
||||||
|
ctld.preLoadTransport(preload_data["unit"], preload_data["amount"], true)
|
||||||
|
end
|
||||||
|
|
||||||
|
function toboolean(str) return str == "true" end
|
||||||
|
|
||||||
-- CTLD plugin - configuration
|
-- CTLD plugin - configuration
|
||||||
if dcsLiberation then
|
if dcsLiberation then
|
||||||
local ctld_pickup_smoke = "none"
|
local ctld_pickup_smoke = "none"
|
||||||
@ -19,26 +35,95 @@ if dcsLiberation then
|
|||||||
if dcsLiberation.plugins then
|
if dcsLiberation.plugins then
|
||||||
if dcsLiberation.plugins.ctld then
|
if dcsLiberation.plugins.ctld then
|
||||||
env.info("DCSLiberation|CTLD plugin - Setting Up")
|
env.info("DCSLiberation|CTLD plugin - Setting Up")
|
||||||
|
--- Debug Settings
|
||||||
ctld.Debug = dcsLiberation.plugins.ctld.debug
|
ctld.Debug = dcsLiberation.plugins.ctld.debug
|
||||||
ctld.Trace = dcsLiberation.plugins.ctld.debug
|
ctld.Trace = dcsLiberation.plugins.ctld.debug
|
||||||
ctld.transportPilotNames = {}
|
|
||||||
ctld.pickupZones = {}
|
|
||||||
ctld.dropOffZones = {}
|
|
||||||
ctld.wpZones = {}
|
|
||||||
|
|
||||||
for _, item in pairs(dcsLiberation.Logistics) do
|
-- Sling loadings settings
|
||||||
for _, pilot in pairs(item.pilot_names) do
|
ctld.enableCrates = true
|
||||||
table.insert(ctld.transportPilotNames, pilot)
|
ctld.slingLoad = dcsLiberation.plugins.ctld.slingload
|
||||||
|
ctld.staticBugFix = not dcsLiberation.plugins.ctld.slingload
|
||||||
|
|
||||||
|
--- Special unitLoad Settings as proposed in #2174
|
||||||
|
ctld.maximumDistanceLogistic = 300
|
||||||
|
ctld.unitLoadLimits = {}
|
||||||
|
ctld.unitActions = {}
|
||||||
|
for _, transport in pairs(dcsLiberation.Logistics.transports) do
|
||||||
|
ctld.unitLoadLimits[transport.aircraft_type] = tonumber(transport.cabin_size)
|
||||||
|
ctld.unitActions[transport.aircraft_type] = { crates = toboolean(transport.crates), troops = toboolean(transport.troops) }
|
||||||
end
|
end
|
||||||
|
|
||||||
if dcsLiberation.plugins.ctld.smoke then
|
if dcsLiberation.plugins.ctld.smoke then
|
||||||
ctld_pickup_smoke = "blue"
|
ctld_pickup_smoke = "blue"
|
||||||
ctld_dropoff_smoke = "green"
|
ctld_dropoff_smoke = "green"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Definition of spawnable things
|
||||||
|
local ctld_troops = ctld.loadableGroups
|
||||||
|
ctld.loadableGroups = {
|
||||||
|
{ name = "Liberation Troops (2)", inf = 2 },
|
||||||
|
{ name = "Liberation Troops (4)", inf = 4 },
|
||||||
|
{ name = "Liberation Troops (6)", inf = 4, mg = 1, at = 1 },
|
||||||
|
{ name = "Liberation Troops (10)", inf = 5, mg = 2, at = 2, aa = 1 },
|
||||||
|
{ name = "Liberation Troops (12)", inf = 6, mg = 2, at = 2, aa = 2 },
|
||||||
|
{ name = "Liberation Troops (24)", inf = 12, mg = 4, at = 4, aa = 3, jtac = 1 },
|
||||||
|
}
|
||||||
|
if dcsLiberation.plugins.ctld.tailorctld then
|
||||||
|
--- remove all default CTLD spawning settings
|
||||||
|
--- so that we can tailor them for the tasked missions
|
||||||
|
ctld.enableSmokeDrop = false
|
||||||
|
ctld.enabledRadioBeaconDrop = false
|
||||||
|
ctld.spawnableCrates = {}
|
||||||
|
ctld.vehiclesForTransportRED = {}
|
||||||
|
ctld.vehiclesForTransportBLUE = {}
|
||||||
|
ctld.transportPilotNames = {}
|
||||||
|
ctld.logisticUnits = {}
|
||||||
|
ctld.pickupZones = {}
|
||||||
|
ctld.dropOffZones = {}
|
||||||
|
ctld.wpZones = {}
|
||||||
|
else
|
||||||
|
--- append the default CTLD troops
|
||||||
|
for _, troop in pairs(ctld_troops) do
|
||||||
|
table.insert(ctld.loadableGroups, troop)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- add all carriers as pickup zone
|
||||||
|
if dcsLiberation.Carriers then
|
||||||
|
for _, carrier in pairs(dcsLiberation.Carriers) do
|
||||||
|
table.insert(ctld.pickupZones, { carrier.unit_name, ctld_pickup_smoke, -1, "yes", 0 })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- generate mission specific spawnable crates
|
||||||
|
local spawnable_crates = {}
|
||||||
|
for _, crate in pairs(dcsLiberation.Logistics.spawnable_crates) do
|
||||||
|
table.insert(spawnable_crates, { weight = tonumber(crate.weight), desc = crate.unit, unit = crate.unit })
|
||||||
|
end
|
||||||
|
ctld.spawnableCrates["Liberation Crates"] = spawnable_crates
|
||||||
|
|
||||||
|
--- Parse the LogisticsInfo for the mission
|
||||||
|
for _, item in pairs(dcsLiberation.Logistics.flights) do
|
||||||
|
for _, pilot in pairs(item.pilot_names) do
|
||||||
|
table.insert(ctld.transportPilotNames, pilot)
|
||||||
|
if toboolean(item.preload) then
|
||||||
|
local amount = ctld.unitLoadLimits[item.aircraft_type]
|
||||||
|
timer.scheduleFunction(preload_troops, { unit = pilot, amount = amount }, timer.getTime() + 5)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if item.pickup_zone then
|
||||||
table.insert(ctld.pickupZones, { item.pickup_zone, ctld_pickup_smoke, -1, "yes", tonumber(item.side) })
|
table.insert(ctld.pickupZones, { item.pickup_zone, ctld_pickup_smoke, -1, "yes", tonumber(item.side) })
|
||||||
|
end
|
||||||
|
if item.drop_off_zone then
|
||||||
table.insert(ctld.dropOffZones, { item.drop_off_zone, ctld_dropoff_smoke, tonumber(item.side) })
|
table.insert(ctld.dropOffZones, { item.drop_off_zone, ctld_dropoff_smoke, tonumber(item.side) })
|
||||||
|
end
|
||||||
|
if item.target_zone then
|
||||||
table.insert(ctld.wpZones, { item.target_zone, "none", "yes", tonumber(item.side) })
|
table.insert(ctld.wpZones, { item.target_zone, "none", "yes", tonumber(item.side) })
|
||||||
end
|
end
|
||||||
|
if dcsLiberation.plugins.ctld.logisticunit and item.logistic_unit then
|
||||||
|
table.insert(ctld.logisticUnits, item.logistic_unit)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
autolase = dcsLiberation.plugins.ctld.autolase
|
autolase = dcsLiberation.plugins.ctld.autolase
|
||||||
env.info(string.format("DCSLiberation|CTLD plugin - JTAC AutoLase enabled = %s", tostring(autolase)))
|
env.info(string.format("DCSLiberation|CTLD plugin - JTAC AutoLase enabled = %s", tostring(autolase)))
|
||||||
@ -52,14 +137,17 @@ if dcsLiberation then
|
|||||||
|
|
||||||
-- JTAC Autolase configuration code
|
-- JTAC Autolase configuration code
|
||||||
for _, jtac in pairs(dcsLiberation.JTACs) do
|
for _, jtac in pairs(dcsLiberation.JTACs) do
|
||||||
env.info(string.format("DCSLiberation|JTACAutolase - setting up %s", jtac.dcsUnit))
|
env.info(string.format("DCSLiberation|JTACAutolase - setting up %s", jtac.dcsGroupName))
|
||||||
if fc3LaserCode then
|
if fc3LaserCode then
|
||||||
-- If fc3LaserCode is enabled in the plugin configuration, force the JTAC
|
-- If fc3LaserCode is enabled in the plugin configuration, force the JTAC
|
||||||
-- laser code to 1113 to allow lasing for Su-25 Frogfoots and A-10A Warthogs.
|
-- laser code to 1113 to allow lasing for Su-25 Frogfoots and A-10A Warthogs.
|
||||||
jtac.laserCode = 1113
|
jtac.laserCode = 1113
|
||||||
end
|
end
|
||||||
ctld.JTACAutoLase(jtac.dcsUnit, jtac.laserCode, smoke, 'vehicle', nil, { freq = jtac.radio, mod = jtac.modulation, name = jtac.dcsGroupName })
|
ctld.JTACAutoLase(jtac.dcsGroupName, jtac.laserCode, smoke, 'vehicle', nil, { freq = jtac.radio, mod = jtac.modulation, name = jtac.dcsGroupName })
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
if dcsLiberation.plugins.ctld.airliftcrates then
|
||||||
|
timer.scheduleFunction(spawn_crates, nil, timer.getTime() + 3)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -2,11 +2,31 @@
|
|||||||
"nameInUI": "CTLD",
|
"nameInUI": "CTLD",
|
||||||
"defaultValue": true,
|
"defaultValue": true,
|
||||||
"specificOptions": [
|
"specificOptions": [
|
||||||
|
{
|
||||||
|
"nameInUI": "Tailor CTLD for the Liberation specific missions",
|
||||||
|
"mnemonic": "tailorctld",
|
||||||
|
"defaultValue": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nameInUI": "Create logistic unit in each pickup zone",
|
||||||
|
"mnemonic": "logisticunit",
|
||||||
|
"defaultValue": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"nameInUI": "CTLD Use smoke in zones",
|
"nameInUI": "CTLD Use smoke in zones",
|
||||||
"mnemonic": "smoke",
|
"mnemonic": "smoke",
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"nameInUI": "Automatically spawn crates for airlift",
|
||||||
|
"mnemonic": "airliftcrates",
|
||||||
|
"defaultValue": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nameInUI": "Use real sling loading",
|
||||||
|
"mnemonic": "slingload",
|
||||||
|
"defaultValue": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"nameInUI": "JTAC Autolase",
|
"nameInUI": "JTAC Autolase",
|
||||||
"mnemonic": "autolase",
|
"mnemonic": "autolase",
|
||||||
@ -18,13 +38,13 @@
|
|||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"nameInUI": "Use FC3 laser code (1113)",
|
"nameInUI": "JTAC Use FC3 laser code (1113)",
|
||||||
"mnemonic": "fc3LaserCode",
|
"mnemonic": "fc3LaserCode",
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"nameInUI": "CTLD Debug",
|
"nameInUI": "CTLD Debug",
|
||||||
"mnemonic": "ctld-debug",
|
"mnemonic": "debug",
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@ -10,3 +10,4 @@ mission_types:
|
|||||||
- CAS
|
- CAS
|
||||||
- BAI
|
- BAI
|
||||||
- Transport
|
- Transport
|
||||||
|
- Air Assault
|
||||||
|
|||||||
@ -10,3 +10,4 @@ mission_types:
|
|||||||
- CAS
|
- CAS
|
||||||
- BAI
|
- BAI
|
||||||
- Transport
|
- Transport
|
||||||
|
- Air Assault
|
||||||
|
|||||||
@ -10,3 +10,4 @@ mission_types:
|
|||||||
- Transport
|
- Transport
|
||||||
- CAS
|
- CAS
|
||||||
- BAI
|
- BAI
|
||||||
|
- Air Assault
|
||||||
@ -10,3 +10,4 @@ mission_types:
|
|||||||
- Transport
|
- Transport
|
||||||
- CAS
|
- CAS
|
||||||
- BAI
|
- BAI
|
||||||
|
- Air Assault
|
||||||
|
|||||||
@ -9,3 +9,4 @@ livery: standard
|
|||||||
mission_types:
|
mission_types:
|
||||||
- Transport
|
- Transport
|
||||||
- Anti-ship
|
- Anti-ship
|
||||||
|
- Air Assault
|
||||||
|
|||||||
@ -10,3 +10,4 @@ mission_types:
|
|||||||
- CAS
|
- CAS
|
||||||
- OCA/Aircraft
|
- OCA/Aircraft
|
||||||
- Transport
|
- Transport
|
||||||
|
- Air Assault
|
||||||
|
|||||||
@ -10,3 +10,4 @@ mission_types:
|
|||||||
- CAS
|
- CAS
|
||||||
- OCA/Aircraft
|
- OCA/Aircraft
|
||||||
- Transport
|
- Transport
|
||||||
|
- Air Assault
|
||||||
|
|||||||
@ -10,3 +10,4 @@ mission_types:
|
|||||||
- CAS
|
- CAS
|
||||||
- OCA/Aircraft
|
- OCA/Aircraft
|
||||||
- Transport
|
- Transport
|
||||||
|
- Air Assault
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
|
class: Helicopter
|
||||||
always_keeps_gun: true
|
always_keeps_gun: true
|
||||||
carrier_capable: true
|
carrier_capable: true
|
||||||
|
cabin_size: 0 # Can not transport troops
|
||||||
|
can_carry_crates: false # Can not carry crates
|
||||||
description: The AH-1 Cobra was developed in the mid-1960s as an interim gunship for
|
description: The AH-1 Cobra was developed in the mid-1960s as an interim gunship for
|
||||||
the U.S. Army for use during the Vietnam War. The Cobra shared the proven transmission,
|
the U.S. Army for use during the Vietnam War. The Cobra shared the proven transmission,
|
||||||
rotor system, and the T53 turboshaft engine of the UH-1 'Huey'. By June 1967, the
|
rotor system, and the T53 turboshaft engine of the UH-1 'Huey'. By June 1967, the
|
||||||
|
|||||||
@ -1,4 +1,7 @@
|
|||||||
|
class: Helicopter
|
||||||
always_keeps_gun: true
|
always_keeps_gun: true
|
||||||
|
cabin_size: 0 # Can not transport troops
|
||||||
|
can_carry_crates: false # Can not carry crates
|
||||||
description: The legendary 'Apache' is an US twin-turboshaft attack helicopter for
|
description: The legendary 'Apache' is an US twin-turboshaft attack helicopter for
|
||||||
a crew of two. It features a nose-mounted sensor suite for target acquisition and
|
a crew of two. It features a nose-mounted sensor suite for target acquisition and
|
||||||
night vision systems. It is armed with a 30 mm (1.18 in) M230 chain gun carried
|
night vision systems. It is armed with a 30 mm (1.18 in) M230 chain gun carried
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
|
class: Helicopter
|
||||||
always_keeps_gun: true
|
always_keeps_gun: true
|
||||||
lha_capable: true
|
lha_capable: true
|
||||||
|
cabin_size: 0 # Can not transport troops
|
||||||
|
can_carry_crates: false # Can not carry crates
|
||||||
description: The legendary 'Apache' is an US twin-turboshaft attack helicopter for
|
description: The legendary 'Apache' is an US twin-turboshaft attack helicopter for
|
||||||
a crew of two. It features a nose-mounted sensor suite for target acquisition and
|
a crew of two. It features a nose-mounted sensor suite for target acquisition and
|
||||||
night vision systems. It is armed with a 30 mm (1.18 in) M230 chain gun carried
|
night vision systems. It is armed with a 30 mm (1.18 in) M230 chain gun carried
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
|
class: Helicopter
|
||||||
always_keeps_gun: true
|
always_keeps_gun: true
|
||||||
lha_capable: true
|
lha_capable: true
|
||||||
|
cabin_size: 0 # Can not transport troops
|
||||||
|
can_carry_crates: false # Can not carry crates
|
||||||
description: The legendary 'Apache' is an US twin-turboshaft attack helicopter for
|
description: The legendary 'Apache' is an US twin-turboshaft attack helicopter for
|
||||||
a crew of two. It features a nose-mounted sensor suite for target acquisition and
|
a crew of two. It features a nose-mounted sensor suite for target acquisition and
|
||||||
night vision systems. It is armed with a 30 mm (1.18 in) M230 chain gun carried
|
night vision systems. It is armed with a 30 mm (1.18 in) M230 chain gun carried
|
||||||
|
|||||||
@ -1,4 +1,7 @@
|
|||||||
|
class: Helicopter
|
||||||
|
cabin_size: 24 # It should have 33 but we do not want so much for CTLD to be possible
|
||||||
|
can_carry_crates: true
|
||||||
description: The CH-47D is a transport helicopter.
|
description: The CH-47D is a transport helicopter.
|
||||||
price: 4
|
price: 6
|
||||||
variants:
|
variants:
|
||||||
CH-47D: null
|
CH-47D: null
|
||||||
|
|||||||
@ -1,4 +1,7 @@
|
|||||||
|
class: Helicopter
|
||||||
|
cabin_size: 24 # It should have 37 but we do not want so much for CTLD to be possible
|
||||||
|
can_carry_crates: true
|
||||||
description: The CH-53 is a military transport helicopter.
|
description: The CH-53 is a military transport helicopter.
|
||||||
price: 4
|
price: 6
|
||||||
variants:
|
variants:
|
||||||
CH-53E: null
|
CH-53E: null
|
||||||
|
|||||||
@ -9,5 +9,6 @@ origin: USA
|
|||||||
price: 18
|
price: 18
|
||||||
role: Transport
|
role: Transport
|
||||||
max_range: 1000
|
max_range: 1000
|
||||||
|
cabin_size: 24 # It should have more but we do not want so much for CTLD to be possible
|
||||||
variants:
|
variants:
|
||||||
C-130J-30 Super Hercules: {}
|
C-130J-30 Super Hercules: {}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
class: Helicopter
|
||||||
always_keeps_gun: true
|
always_keeps_gun: true
|
||||||
carrier_capable: true
|
carrier_capable: true
|
||||||
description:
|
description:
|
||||||
@ -8,6 +9,8 @@ description:
|
|||||||
that it has an ejection seat."
|
that it has an ejection seat."
|
||||||
introduced: 1995
|
introduced: 1995
|
||||||
lha_capable: true
|
lha_capable: true
|
||||||
|
cabin_size: 0 # Can not transport troops
|
||||||
|
can_carry_crates: true
|
||||||
manufacturer: Kamov
|
manufacturer: Kamov
|
||||||
origin: USSR/Russia
|
origin: USSR/Russia
|
||||||
price: 20
|
price: 20
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
class: Helicopter
|
||||||
always_keeps_gun: true
|
always_keeps_gun: true
|
||||||
description: "The Mil Mi-24 (Russian: \u041C\u0438\u043B\u044C \u041C\u0438-24; NATO\
|
description: "The Mil Mi-24 (Russian: \u041C\u0438\u043B\u044C \u041C\u0438-24; NATO\
|
||||||
\ reporting name: Hind) is a large helicopter gunship, attack helicopter and low-capacity\
|
\ reporting name: Hind) is a large helicopter gunship, attack helicopter and low-capacity\
|
||||||
@ -14,6 +15,8 @@ description: "The Mil Mi-24 (Russian: \u041C\u0438\u043B\u044C \u041C\u0438-24;
|
|||||||
\ cockpits. It served to a great success in the Afghanistan war, until the Taliban\
|
\ cockpits. It served to a great success in the Afghanistan war, until the Taliban\
|
||||||
\ where equipped with Stinger Missiles from the CIA."
|
\ where equipped with Stinger Missiles from the CIA."
|
||||||
lha_capable: true
|
lha_capable: true
|
||||||
|
cabin_size: 6
|
||||||
|
can_carry_crates: true
|
||||||
introduced: 1981
|
introduced: 1981
|
||||||
manufacturer: Mil
|
manufacturer: Mil
|
||||||
origin: USSR/Russia
|
origin: USSR/Russia
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
class: Helicopter
|
||||||
always_keeps_gun: true
|
always_keeps_gun: true
|
||||||
description: "The Mil Mi-24 (Russian: \u041C\u0438\u043B\u044C \u041C\u0438-24; NATO\
|
description: "The Mil Mi-24 (Russian: \u041C\u0438\u043B\u044C \u041C\u0438-24; NATO\
|
||||||
\ reporting name: Hind) is a large helicopter gunship, attack helicopter and low-capacity\
|
\ reporting name: Hind) is a large helicopter gunship, attack helicopter and low-capacity\
|
||||||
@ -14,6 +15,8 @@ description: "The Mil Mi-24 (Russian: \u041C\u0438\u043B\u044C \u041C\u0438-24;
|
|||||||
\ cockpits. It served to a great success in the Afghanistan war, until the Taliban\
|
\ cockpits. It served to a great success in the Afghanistan war, until the Taliban\
|
||||||
\ where equiped with Stinger Misseles from the CIA."
|
\ where equiped with Stinger Misseles from the CIA."
|
||||||
lha_capable: true
|
lha_capable: true
|
||||||
|
cabin_size: 6
|
||||||
|
can_carry_crates: true
|
||||||
introduced: 1976
|
introduced: 1976
|
||||||
manufacturer: Mil
|
manufacturer: Mil
|
||||||
origin: USSR/Russia
|
origin: USSR/Russia
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
price: 4
|
class: Helicopter
|
||||||
|
cabin_size: 24 # It should have 60+ but we do not want so much for CTLD to be possible
|
||||||
|
can_carry_crates: true
|
||||||
|
price: 6
|
||||||
variants:
|
variants:
|
||||||
Mi-26: null
|
Mi-26: null
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
class: Helicopter
|
||||||
|
cabin_size: 0
|
||||||
|
can_carry_crates: false
|
||||||
always_keeps_gun: true
|
always_keeps_gun: true
|
||||||
description: The Mil Mi-28 (NATO reporting name 'Havoc') is a Russian all-weather,
|
description: The Mil Mi-28 (NATO reporting name 'Havoc') is a Russian all-weather,
|
||||||
day-night, military tandem, two-seat anti-armor attack helicopter. It is an attack
|
day-night, military tandem, two-seat anti-armor attack helicopter. It is an attack
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
|
class: Helicopter
|
||||||
carrier_capable: true
|
carrier_capable: true
|
||||||
description: The Mil Mi-8MTV2 is an upgraded version of one of the most widely produced
|
description: The Mil Mi-8MTV2 is an upgraded version of one of the most widely produced
|
||||||
helicopters in history and a combat transport and fire support veteran of countless
|
helicopters in history and a combat transport and fire support veteran of countless
|
||||||
operations around the world.
|
operations around the world.
|
||||||
introduced: 1981
|
introduced: 1981
|
||||||
lha_capable: true
|
lha_capable: true
|
||||||
|
cabin_size: 12
|
||||||
|
can_carry_crates: true
|
||||||
manufacturer: Mil
|
manufacturer: Mil
|
||||||
origin: USSR/Russia
|
origin: USSR/Russia
|
||||||
price: 5
|
price: 5
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
class: Helicopter
|
||||||
|
cabin_size: 0 # Can not transport troops
|
||||||
|
can_carry_crates: false # Can not carry crates
|
||||||
carrier_capable: true
|
carrier_capable: true
|
||||||
description: The Bell OH-58 Kiowa is a family of single-engine, single-rotor, military
|
description: The Bell OH-58 Kiowa is a family of single-engine, single-rotor, military
|
||||||
helicopters used for observation, utility, and direct fire support. Bell Helicopter
|
helicopters used for observation, utility, and direct fire support. Bell Helicopter
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
class: Helicopter
|
||||||
carrier_capable: true
|
carrier_capable: true
|
||||||
description: "The SA342 Gazelle is a light scout/attack and transport helicopter.\
|
description: "The SA342 Gazelle is a light scout/attack and transport helicopter.\
|
||||||
\ It was introduced in 1968 as a result of cooperation between A\xE9rospatiale and\
|
\ It was introduced in 1968 as a result of cooperation between A\xE9rospatiale and\
|
||||||
@ -9,6 +10,8 @@ description: "The SA342 Gazelle is a light scout/attack and transport helicopter
|
|||||||
\ which features the famous Fenestron tail rotor."
|
\ which features the famous Fenestron tail rotor."
|
||||||
introduced: 1977
|
introduced: 1977
|
||||||
lha_capable: true
|
lha_capable: true
|
||||||
|
cabin_size: 2
|
||||||
|
can_carry_crates: false
|
||||||
manufacturer: "A\xE9rospatiale"
|
manufacturer: "A\xE9rospatiale"
|
||||||
origin: France
|
origin: France
|
||||||
price: 5
|
price: 5
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
class: Helicopter
|
||||||
carrier_capable: true
|
carrier_capable: true
|
||||||
description: "The SA342 Gazelle is a light scout/attack and transport helicopter.\
|
description: "The SA342 Gazelle is a light scout/attack and transport helicopter.\
|
||||||
\ It was introduced in 1968 as a result of cooperation between A\xE9rospatiale and\
|
\ It was introduced in 1968 as a result of cooperation between A\xE9rospatiale and\
|
||||||
@ -9,6 +10,8 @@ description: "The SA342 Gazelle is a light scout/attack and transport helicopter
|
|||||||
\ which features the famous Fenestron tail rotor."
|
\ which features the famous Fenestron tail rotor."
|
||||||
introduced: 1977
|
introduced: 1977
|
||||||
lha_capable: true
|
lha_capable: true
|
||||||
|
cabin_size: 2
|
||||||
|
can_carry_crates: false
|
||||||
manufacturer: "A\xE9rospatiale"
|
manufacturer: "A\xE9rospatiale"
|
||||||
origin: France
|
origin: France
|
||||||
price: 8
|
price: 8
|
||||||
|
|||||||
@ -1,4 +1,7 @@
|
|||||||
|
class: Helicopter
|
||||||
price: 4
|
price: 4
|
||||||
|
cabin_size: 2
|
||||||
|
can_carry_crates: false
|
||||||
variants:
|
variants:
|
||||||
SA342Minigun: null
|
SA342Minigun: null
|
||||||
kneeboard_units: "metric"
|
kneeboard_units: "metric"
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
class: Helicopter
|
||||||
carrier_capable: true
|
carrier_capable: true
|
||||||
description: "The SA342 Gazelle is a light scout/attack and transport helicopter.\
|
description: "The SA342 Gazelle is a light scout/attack and transport helicopter.\
|
||||||
\ It was introduced in 1968 as a result of cooperation between A\xE9rospatiale and\
|
\ It was introduced in 1968 as a result of cooperation between A\xE9rospatiale and\
|
||||||
@ -9,6 +10,8 @@ description: "The SA342 Gazelle is a light scout/attack and transport helicopter
|
|||||||
\ which features the famous Fenestron tail rotor."
|
\ which features the famous Fenestron tail rotor."
|
||||||
introduced: 1977
|
introduced: 1977
|
||||||
lha_capable: true
|
lha_capable: true
|
||||||
|
cabin_size: 2
|
||||||
|
can_carry_crates: false
|
||||||
manufacturer: "A\xE9rospatiale"
|
manufacturer: "A\xE9rospatiale"
|
||||||
origin: France
|
origin: France
|
||||||
price: 8
|
price: 8
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
class: Helicopter
|
||||||
|
cabin_size: 6
|
||||||
|
can_carry_crates: true
|
||||||
carrier_capable: true
|
carrier_capable: true
|
||||||
description: The Sikorsky SH-60/MH-60 Seahawk (or Sea Hawk) is a twin turboshaft engine,
|
description: The Sikorsky SH-60/MH-60 Seahawk (or Sea Hawk) is a twin turboshaft engine,
|
||||||
multi-mission United States Navy helicopter based on the United States Army UH-60
|
multi-mission United States Navy helicopter based on the United States Army UH-60
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
class: Helicopter
|
||||||
carrier_capable: true
|
carrier_capable: true
|
||||||
description:
|
description:
|
||||||
The UH-1 Iroquois, better known as the Huey, is one of the most iconic
|
The UH-1 Iroquois, better known as the Huey, is one of the most iconic
|
||||||
@ -5,6 +6,8 @@ description:
|
|||||||
serve in both military and civilian roles around the globe today.
|
serve in both military and civilian roles around the globe today.
|
||||||
introduced: 1967
|
introduced: 1967
|
||||||
lha_capable: true
|
lha_capable: true
|
||||||
|
cabin_size: 6
|
||||||
|
can_carry_crates: true
|
||||||
manufacturer: Bell
|
manufacturer: Bell
|
||||||
origin: USA
|
origin: USA
|
||||||
price: 4
|
price: 4
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
class: Helicopter
|
||||||
price: 4
|
price: 4
|
||||||
|
cabin_size: 10
|
||||||
|
can_carry_crates: true
|
||||||
variants:
|
variants:
|
||||||
UH-60A: null
|
UH-60A: null
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
class: Helicopter
|
||||||
description:
|
description:
|
||||||
The Sikorsky UH-60 Black Hawk is a four-blade, twin-engine, medium-lift utility helicopter manufactured by Sikorsky Aircraft.
|
The Sikorsky UH-60 Black Hawk is a four-blade, twin-engine, medium-lift utility helicopter manufactured by Sikorsky Aircraft.
|
||||||
The UH-60A entered service with the U.S. Army in 1979, to replace the Bell UH-1 Iroquois as the Army's tactical transport helicopter.
|
The UH-60A entered service with the U.S. Army in 1979, to replace the Bell UH-1 Iroquois as the Army's tactical transport helicopter.
|
||||||
@ -5,6 +6,8 @@ description:
|
|||||||
introduced: 1989
|
introduced: 1989
|
||||||
carrier_capable: true
|
carrier_capable: true
|
||||||
lha_capable: true
|
lha_capable: true
|
||||||
|
cabin_size: 10
|
||||||
|
can_carry_crates: true
|
||||||
manufacturer: Sikorsky
|
manufacturer: Sikorsky
|
||||||
origin: USA
|
origin: USA
|
||||||
price: 4
|
price: 4
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user