Recovery tanker support (#429)

* fix conflict

* squash bugs and reuse patrol layout

* fix tanker tacan and formatting

* fix unlimited fuel option

* update pretense for tanker changes

* reuse refueling flight plan and bugfix for sunken carrier

changelog

* remove unitmap dependency

* formatting and more unit map removal

* more formatting

* typing and black

* keep tanker out of clouds

* fix if there are no clouds

* better cloud handling

* groundwork for recovery task

* remove changes to game/commander

* Finishing up recovery tankers

---------

Co-authored-by: Raffson <Raffson@users.noreply.github.com>
This commit is contained in:
Druss99
2024-12-22 23:39:10 -05:00
committed by GitHub
parent a4671571bc
commit dd7e4c908e
46 changed files with 395 additions and 25 deletions

View File

@@ -30,22 +30,33 @@ from dcs.task import (
OptNoReportWaypointPass,
OptRadioUsageContact,
OptRadioSilence,
Tanker,
RecoveryTanker,
ActivateBeaconCommand,
ControlledTask,
)
from dcs.unitgroup import FlyingGroup
from dcs.unitgroup import FlyingGroup, ShipGroup
from game.ato import Flight, FlightType
from game.ato import Flight, FlightType, Package
from game.ato.flightplans.aewc import AewcFlightPlan
from game.ato.flightplans.formationattack import FormationAttackLayout
from game.ato.flightplans.packagerefueling import PackageRefuelingFlightPlan
from game.ato.flightplans.shiprecoverytanker import RecoveryTankerFlightPlan
from game.ato.flightplans.theaterrefueling import TheaterRefuelingFlightPlan
from game.utils import nautical_miles
from game.missiongenerator.missiondata import MissionData
from game.utils import nautical_miles, knots, feet
class AircraftBehavior:
def __init__(self, task: FlightType) -> None:
def __init__(self, task: FlightType, mission_data: MissionData) -> None:
self.task = task
self.mission_data = mission_data
def apply_to(self, flight: Flight, group: FlyingGroup[Any]) -> None:
def apply_to(
self,
flight: Flight,
group: FlyingGroup[Any],
) -> None:
if self.task in [
FlightType.BARCAP,
FlightType.TARCAP,
@@ -58,6 +69,8 @@ class AircraftBehavior:
self.configure_awacs(group, flight)
elif self.task == FlightType.REFUELING:
self.configure_refueling(group, flight)
elif self.task == FlightType.RECOVERY:
self.configure_recovery(group, flight)
elif self.task in [FlightType.CAS, FlightType.BAI]:
self.configure_cas(group, flight)
elif self.task == FlightType.ARMED_RECON:
@@ -317,6 +330,7 @@ class AircraftBehavior:
if not (
isinstance(flight.flight_plan, TheaterRefuelingFlightPlan)
or isinstance(flight.flight_plan, PackageRefuelingFlightPlan)
or isinstance(flight.flight_plan, RecoveryTankerFlightPlan)
):
logging.error(
f"Cannot configure racetrack refueling tasks for {flight} because it "
@@ -332,6 +346,95 @@ class AircraftBehavior:
restrict_jettison=True,
)
def configure_recovery(
self,
group: FlyingGroup[Any],
flight: Flight,
) -> None:
self.configure_refueling(group, flight)
if not isinstance(flight.flight_plan, RecoveryTankerFlightPlan):
logging.error(
f"Cannot configure recovery task for {flight} because it "
"does not have an recovery tanker flight plan."
)
return
self.configure_tanker_tacan(flight, group)
clouds = flight.squadron.coalition.game.conditions.weather.clouds
speed = knots(250).meters_per_second
altitude = feet(6000).meters
if clouds is not None:
if abs(clouds.base - altitude) < feet(1000).meters:
altitude = clouds.base - feet(1000).meters
if altitude < feet(2000).meters:
altitude = clouds.base + feet(6000).meters
naval_group = self._get_carrier_group(flight.package)
last_waypoint = len(naval_group.points) # last waypoint of the CVN/LHA
tanker_tos = flight.coalition.game.settings.desired_tanker_on_station_time
lua_predicate = f"""
local lowfuel = false
for i, unitObject in pairs(Group.getByName('{group.name}'):getUnits()) do
if Unit.getFuel(unitObject) < 0.2 then lowfuel = true end
end
return lowfuel
"""
tanker = ControlledTask(Tanker())
tanker.stop_after_duration(int(tanker_tos.total_seconds()) + 1)
tanker.stop_if_lua_predicate(lua_predicate)
group.points[0].add_task(tanker)
recovery = ControlledTask(
RecoveryTanker(naval_group.id, speed, altitude, last_waypoint)
)
recovery.stop_if_lua_predicate(lua_predicate)
recovery.stop_after_duration(int(tanker_tos.total_seconds()) + 1)
group.points[0].add_task(recovery)
def configure_tanker_tacan(self, flight: Flight, group: FlyingGroup[Any]) -> None:
tanker_info = self.mission_data.tankers[-1]
tacan = tanker_info.tacan
if flight.unit_type.dcs_unit_type.tacan and tacan:
if flight.tcn_name is None:
cs = tanker_info.callsign[:-2]
csn = tanker_info.callsign[-1]
tacan_callsign = {
"Texaco": "TX",
"Arco": "AC",
"Shell": "SH",
}.get(cs)
if tacan_callsign:
tacan_callsign = tacan_callsign + csn
else:
tacan_callsign = cs[0:2] + csn
else:
tacan_callsign = flight.tcn_name
group.points[0].add_task(
ActivateBeaconCommand(
tacan.number,
tacan.band.value,
tacan_callsign.upper(),
bearing=True,
unit_id=group.units[0].id,
aa=True,
)
)
def _get_carrier_group(self, package: Package) -> ShipGroup:
name = package.target.name
carrier_position = package.target.position
for carrier in self.mission_data.carriers:
if carrier.ship_group.position == carrier_position:
return carrier.ship_group
raise RuntimeError(
f"Could not find a carrier in the mission matching {name} at "
f"({carrier_position.x}, {carrier_position.y})"
)
def configure_escort(self, group: FlyingGroup[Any], flight: Flight) -> None:
self.configure_task(flight, group, Escort)
self.configure_behavior(

View File

@@ -15,6 +15,7 @@ from dcs.unit import Skill
from dcs.unitgroup import FlyingGroup
from game.ato import Flight, FlightType
from game.ato.flightplans.shiprecoverytanker import RecoveryTankerFlightPlan
from game.callsigns import callsign_for_support_unit
from game.data.weapons import Pylon, WeaponType
from game.missiongenerator.logisticsgenerator import LogisticsGenerator
@@ -79,12 +80,14 @@ class FlightGroupConfigurator:
self.use_client = use_client
def configure(self) -> FlightData:
AircraftBehavior(self.flight.flight_type).apply_to(self.flight, self.group)
flight_channel = self.setup_radios()
AircraftBehavior(self.flight.flight_type, self.mission_data).apply_to(
self.flight, self.group
)
AircraftPainter(self.flight, self.group).apply_livery()
self.setup_props()
self.setup_payloads()
self.setup_fuel()
flight_channel = self.setup_radios()
laser_codes: list[Optional[int]] = []
for unit, member in zip(self.group.units, self.flight.iter_members()):
@@ -197,7 +200,11 @@ class FlightGroupConfigurator:
if freq not in self.radio_registry.allocated_channels:
self.radio_registry.reserve(freq)
if self.flight.flight_type in {FlightType.AEWC, FlightType.REFUELING}:
if self.flight.flight_type in {
FlightType.AEWC,
FlightType.REFUELING,
FlightType.RECOVERY,
}:
self.register_air_support(freq)
elif self.flight.client_count and self.flight.squadron.radio_presets:
freq = self.flight.squadron.radio_presets["intra_flight"][0]
@@ -222,9 +229,11 @@ class FlightGroupConfigurator:
unit=self.group.units[0],
)
)
elif isinstance(
self.flight.flight_plan, TheaterRefuelingFlightPlan
) or isinstance(self.flight.flight_plan, PackageRefuelingFlightPlan):
elif (
isinstance(self.flight.flight_plan, TheaterRefuelingFlightPlan)
or isinstance(self.flight.flight_plan, PackageRefuelingFlightPlan)
or isinstance(self.flight.flight_plan, RecoveryTankerFlightPlan)
):
tacan = self.flight.tacan
if tacan is None and self.flight.squadron.aircraft.dcs_unit_type.tacan:
try:

View File

@@ -100,7 +100,7 @@ class RaceTrackBuilder(PydcsWaypointBuilder):
ActivateBeaconCommand(
tacan.number,
tacan.band.value,
tacan_callsign,
tacan_callsign.upper(),
bearing=True,
unit_id=self.group.units[0].id,
aa=True,

View File

@@ -9,8 +9,6 @@ from .pydcswaypointbuilder import PydcsWaypointBuilder
class RaceTrackEndBuilder(PydcsWaypointBuilder):
def add_tasks(self, waypoint: MovingPoint) -> None:
flight_plan = self.flight.flight_plan
# Unlimited fuel option : enable at racetrack end. Must be first option to work.
if self.flight.squadron.coalition.game.settings.ai_unlimited_fuel:
waypoint.tasks.insert(0, SetUnlimitedFuelCommand(True))

View File

@@ -8,6 +8,17 @@ class RefuelPointBuilder(PydcsWaypointBuilder):
def add_tasks(self, waypoint: MovingPoint) -> None:
if not self.ai_despawn(waypoint, True):
refuel = ControlledTask(RefuelingTaskAction())
refuel.start_probability(10)
refuel.start_if_lua_predicate(self._get_lua_predicate(0.2))
refuel.stop_if_lua_predicate(self._get_lua_predicate(0.5))
waypoint.add_task(refuel)
return super().add_tasks(waypoint)
def _get_lua_predicate(self, fuel_level: float) -> str:
return f"""
local okfuel = true
for i, unitObject in pairs(Group.getByName('{self.group.name}'):getUnits()) do
--trigger.action.outText(tostring(Unit.getFuel(unitObject)), 15)
if Unit.getFuel(unitObject) < {fuel_level} then okfuel = false; break end
end
return lowfuel
"""

View File

@@ -5,6 +5,7 @@ from datetime import datetime
from typing import Optional, TYPE_CHECKING
from dcs.flyingunit import FlyingUnit
from dcs.unitgroup import ShipGroup
from game.dcs.aircrafttype import AircraftType
from game.dcs.groundunittype import GroundUnitType
@@ -58,6 +59,7 @@ class CarrierInfo(UnitInfo):
tacan: TacanChannel
icls_channel: int | None
link4_freq: RadioFrequency | None
ship_group: ShipGroup
@dataclass

View File

@@ -650,6 +650,7 @@ class GenericCarrierGenerator(GroundObjectGenerator):
icls_channel=icls,
link4_freq=link4,
blue=self.control_point.captured,
ship_group=ship_group,
)
)