mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
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:
@@ -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(
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -650,6 +650,7 @@ class GenericCarrierGenerator(GroundObjectGenerator):
|
||||
icls_channel=icls,
|
||||
link4_freq=link4,
|
||||
blue=self.control_point.captured,
|
||||
ship_group=ship_group,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user