mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Add plannable tankers.
This Pull Request lets users plan Tanker flights. Features: - Introduction of `Refueling` flight type. - Tankers can be purchased at airbases and carriers. - Tankers get planned by AI. - Tankers are planned from airbases and at aircraft carriers. - Tankers aim to be at high, fast, and 70 miles from the nearest threat. (A10s won't be able to tank) - Tankers racetrack orbit for one hour. - Optional Tickbox to enable legacy tankers. - S-3B Tanker added to factions. - KC-130 MPRS added to factions. - Kneeboard shows planned tankers, their tacans, and radios. Limitations: - AI doesn't know whether to plan probe and drogue or boom refueling tankers. - User can't choose tanker speed. Heavily loaded aircraft may have trouble. - User can't choose tanker altitude. A-10s will not make it to high altitude. Problems: - Tanker callsigns do not increment, see attached image. (Investigated: Need to use `FlyingType.callsign_dict`, instead of just `FlyingType.callsign`. This seems like it might be significant work to do.). - Having a flight of two or more tankers only spawns one tanker. - Let me know if you have a solution, or feel free to commit one. https://user-images.githubusercontent.com/74509817/120909602-d7bc3680-c633-11eb-80d7-eccd4e095770.png
This commit is contained in:
parent
a9dacf4a29
commit
a53a648a63
@ -4,6 +4,10 @@ Saves from 3.x are not compatible with 4.0.
|
|||||||
|
|
||||||
## Features/Improvements
|
## Features/Improvements
|
||||||
|
|
||||||
|
* **[Flight Planner]** Added ability to plan Tankers.
|
||||||
|
* **[Campaign AI]** AI will plan Tanker flights.
|
||||||
|
* **[Factions]** Added more tankers to factions.
|
||||||
|
|
||||||
## Fixes
|
## Fixes
|
||||||
|
|
||||||
# 3.0.0
|
# 3.0.0
|
||||||
|
|||||||
@ -798,6 +798,7 @@ CARRIER_CAPABLE = [
|
|||||||
Su_33,
|
Su_33,
|
||||||
A_4E_C,
|
A_4E_C,
|
||||||
S_3B,
|
S_3B,
|
||||||
|
S_3B_Tanker,
|
||||||
E_2C,
|
E_2C,
|
||||||
UH_1H,
|
UH_1H,
|
||||||
Mi_8MT,
|
Mi_8MT,
|
||||||
|
|||||||
@ -167,7 +167,9 @@ class Faction:
|
|||||||
faction.awacs = load_all_aircraft(json.get("awacs", []))
|
faction.awacs = load_all_aircraft(json.get("awacs", []))
|
||||||
faction.tankers = load_all_aircraft(json.get("tankers", []))
|
faction.tankers = load_all_aircraft(json.get("tankers", []))
|
||||||
|
|
||||||
faction.aircrafts = list(set(faction.aircrafts + faction.awacs))
|
faction.aircrafts = list(
|
||||||
|
set(faction.aircrafts + faction.awacs + faction.tankers)
|
||||||
|
)
|
||||||
|
|
||||||
faction.frontline_units = load_all_vehicles(json.get("frontline_units", []))
|
faction.frontline_units = load_all_vehicles(json.get("frontline_units", []))
|
||||||
faction.artillery_units = load_all_vehicles(json.get("artillery_units", []))
|
faction.artillery_units = load_all_vehicles(json.get("artillery_units", []))
|
||||||
|
|||||||
@ -375,6 +375,7 @@ class Operation:
|
|||||||
cls.game.settings,
|
cls.game.settings,
|
||||||
cls.game,
|
cls.game,
|
||||||
cls.radio_registry,
|
cls.radio_registry,
|
||||||
|
cls.tacan_registry,
|
||||||
cls.unit_map,
|
cls.unit_map,
|
||||||
air_support=cls.airsupportgen.air_support,
|
air_support=cls.airsupportgen.air_support,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -44,6 +44,7 @@ class Settings:
|
|||||||
automate_aircraft_reinforcements: bool = False
|
automate_aircraft_reinforcements: bool = False
|
||||||
restrict_weapons_by_date: bool = False
|
restrict_weapons_by_date: bool = False
|
||||||
disable_legacy_aewc: bool = True
|
disable_legacy_aewc: bool = True
|
||||||
|
disable_legacy_tanker: bool = True
|
||||||
generate_dark_kneeboard: bool = False
|
generate_dark_kneeboard: bool = False
|
||||||
invulnerable_player_pilots: bool = True
|
invulnerable_player_pilots: bool = True
|
||||||
auto_ato_behavior: AutoAtoBehavior = AutoAtoBehavior.Default
|
auto_ato_behavior: AutoAtoBehavior = AutoAtoBehavior.Default
|
||||||
|
|||||||
@ -810,6 +810,7 @@ class Airfield(ControlPoint):
|
|||||||
if self.is_friendly(for_player):
|
if self.is_friendly(for_player):
|
||||||
yield from [
|
yield from [
|
||||||
FlightType.AEWC,
|
FlightType.AEWC,
|
||||||
|
FlightType.REFUELING,
|
||||||
# TODO: FlightType.INTERCEPTION
|
# TODO: FlightType.INTERCEPTION
|
||||||
# TODO: FlightType.LOGISTICS
|
# TODO: FlightType.LOGISTICS
|
||||||
]
|
]
|
||||||
@ -959,7 +960,10 @@ class Carrier(NavalControlPoint):
|
|||||||
|
|
||||||
yield from super().mission_types(for_player)
|
yield from super().mission_types(for_player)
|
||||||
if self.is_friendly(for_player):
|
if self.is_friendly(for_player):
|
||||||
yield FlightType.AEWC
|
yield from [
|
||||||
|
FlightType.AEWC,
|
||||||
|
FlightType.REFUELING,
|
||||||
|
]
|
||||||
|
|
||||||
def capture(self, game: Game, for_player: bool) -> None:
|
def capture(self, game: Game, for_player: bool) -> None:
|
||||||
raise RuntimeError("Carriers cannot be captured")
|
raise RuntimeError("Carriers cannot be captured")
|
||||||
|
|||||||
@ -81,6 +81,7 @@ class FrontLine(MissionTarget):
|
|||||||
yield from [
|
yield from [
|
||||||
FlightType.CAS,
|
FlightType.CAS,
|
||||||
FlightType.AEWC,
|
FlightType.AEWC,
|
||||||
|
FlightType.REFUELING
|
||||||
# TODO: FlightType.TROOP_TRANSPORT
|
# TODO: FlightType.TROOP_TRANSPORT
|
||||||
# TODO: FlightType.EVAC
|
# TODO: FlightType.EVAC
|
||||||
]
|
]
|
||||||
|
|||||||
109
gen/aircraft.py
109
gen/aircraft.py
@ -39,10 +39,12 @@ from dcs.planes import (
|
|||||||
Su_33,
|
Su_33,
|
||||||
Tu_22M3,
|
Tu_22M3,
|
||||||
)
|
)
|
||||||
|
from dcs.planes import IL_78M
|
||||||
from dcs.point import MovingPoint, PointAction
|
from dcs.point import MovingPoint, PointAction
|
||||||
from dcs.task import (
|
from dcs.task import (
|
||||||
AWACS,
|
AWACS,
|
||||||
AWACSTaskAction,
|
AWACSTaskAction,
|
||||||
|
ActivateBeaconCommand,
|
||||||
AntishipStrike,
|
AntishipStrike,
|
||||||
AttackGroup,
|
AttackGroup,
|
||||||
Bombing,
|
Bombing,
|
||||||
@ -61,8 +63,10 @@ from dcs.task import (
|
|||||||
OptReactOnThreat,
|
OptReactOnThreat,
|
||||||
OptRestrictJettison,
|
OptRestrictJettison,
|
||||||
OrbitAction,
|
OrbitAction,
|
||||||
|
Refueling,
|
||||||
RunwayAttack,
|
RunwayAttack,
|
||||||
StartCommand,
|
StartCommand,
|
||||||
|
Tanker,
|
||||||
Targets,
|
Targets,
|
||||||
Transport,
|
Transport,
|
||||||
WeaponType,
|
WeaponType,
|
||||||
@ -80,7 +84,7 @@ from game.data.weapons import Pylon
|
|||||||
from game.db import GUN_RELIANT_AIRFRAMES
|
from game.db import GUN_RELIANT_AIRFRAMES
|
||||||
from game.factions.faction import Faction
|
from game.factions.faction import Faction
|
||||||
from game.settings import Settings
|
from game.settings import Settings
|
||||||
from game.squadrons import Pilot, Squadron
|
from game.squadrons import Pilot
|
||||||
from game.theater.controlpoint import (
|
from game.theater.controlpoint import (
|
||||||
Airfield,
|
Airfield,
|
||||||
ControlPoint,
|
ControlPoint,
|
||||||
@ -103,13 +107,15 @@ from gen.flights.flight import (
|
|||||||
)
|
)
|
||||||
from gen.radios import MHz, Radio, RadioFrequency, RadioRegistry, get_radio
|
from gen.radios import MHz, Radio, RadioFrequency, RadioRegistry, get_radio
|
||||||
from gen.runways import RunwayData
|
from gen.runways import RunwayData
|
||||||
from .airsupportgen import AirSupport, AwacsInfo
|
from gen.tacan import TacanBand, TacanRegistry
|
||||||
|
from .airsupportgen import AirSupport, AwacsInfo, TankerInfo
|
||||||
from .callsigns import callsign_for_support_unit
|
from .callsigns import callsign_for_support_unit
|
||||||
from .flights.flightplan import (
|
from .flights.flightplan import (
|
||||||
AwacsFlightPlan,
|
AwacsFlightPlan,
|
||||||
CasFlightPlan,
|
CasFlightPlan,
|
||||||
LoiterFlightPlan,
|
LoiterFlightPlan,
|
||||||
PatrollingFlightPlan,
|
PatrollingFlightPlan,
|
||||||
|
RefuelingFlightPlan,
|
||||||
SweepFlightPlan,
|
SweepFlightPlan,
|
||||||
)
|
)
|
||||||
from .flights.traveltime import GroundSpeed, TotEstimator
|
from .flights.traveltime import GroundSpeed, TotEstimator
|
||||||
@ -684,6 +690,7 @@ class AircraftConflictGenerator:
|
|||||||
settings: Settings,
|
settings: Settings,
|
||||||
game: Game,
|
game: Game,
|
||||||
radio_registry: RadioRegistry,
|
radio_registry: RadioRegistry,
|
||||||
|
tacan_registry: TacanRegistry,
|
||||||
unit_map: UnitMap,
|
unit_map: UnitMap,
|
||||||
air_support: AirSupport,
|
air_support: AirSupport,
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -691,6 +698,7 @@ class AircraftConflictGenerator:
|
|||||||
self.game = game
|
self.game = game
|
||||||
self.settings = settings
|
self.settings = settings
|
||||||
self.radio_registry = radio_registry
|
self.radio_registry = radio_registry
|
||||||
|
self.tacan_registy = tacan_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.air_support = air_support
|
||||||
@ -824,7 +832,10 @@ class AircraftConflictGenerator:
|
|||||||
OptReactOnThreat(OptReactOnThreat.Values.EvadeFire)
|
OptReactOnThreat(OptReactOnThreat.Values.EvadeFire)
|
||||||
)
|
)
|
||||||
|
|
||||||
if flight.flight_type == FlightType.AEWC:
|
if (
|
||||||
|
flight.flight_type == FlightType.AEWC
|
||||||
|
or flight.flight_type == FlightType.REFUELING
|
||||||
|
):
|
||||||
channel = self.radio_registry.alloc_uhf()
|
channel = self.radio_registry.alloc_uhf()
|
||||||
else:
|
else:
|
||||||
channel = self.get_intra_flight_channel(unit_type)
|
channel = self.get_intra_flight_channel(unit_type)
|
||||||
@ -879,6 +890,24 @@ class AircraftConflictGenerator:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if isinstance(flight.flight_plan, RefuelingFlightPlan):
|
||||||
|
callsign = callsign_for_support_unit(group)
|
||||||
|
|
||||||
|
tacan = self.tacan_registy.alloc_for_band(TacanBand.Y)
|
||||||
|
variant = db.unit_type_name(flight.flight_plan.flight.unit_type)
|
||||||
|
self.air_support.tankers.append(
|
||||||
|
TankerInfo(
|
||||||
|
group_name=str(group.name),
|
||||||
|
callsign=callsign,
|
||||||
|
variant=variant,
|
||||||
|
freq=channel,
|
||||||
|
tacan=tacan,
|
||||||
|
start_time=flight.flight_plan.patrol_start_time,
|
||||||
|
end_time=flight.flight_plan.patrol_end_time,
|
||||||
|
blue=flight.departure.captured,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def _generate_at_airport(
|
def _generate_at_airport(
|
||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
@ -1457,6 +1486,32 @@ class AircraftConflictGenerator:
|
|||||||
|
|
||||||
group.points[0].tasks.append(AWACSTaskAction())
|
group.points[0].tasks.append(AWACSTaskAction())
|
||||||
|
|
||||||
|
def configure_refueling(
|
||||||
|
self,
|
||||||
|
group: FlyingGroup,
|
||||||
|
package: Package,
|
||||||
|
flight: Flight,
|
||||||
|
dynamic_runways: Dict[str, RunwayData],
|
||||||
|
) -> None:
|
||||||
|
group.task = Refueling.name
|
||||||
|
|
||||||
|
if not isinstance(flight.flight_plan, RefuelingFlightPlan):
|
||||||
|
logging.error(
|
||||||
|
f"Cannot configure racetrack refueling tasks for {flight} because it "
|
||||||
|
"does not have an racetrack refueling flight plan."
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
self._setup_group(group, package, flight, dynamic_runways)
|
||||||
|
|
||||||
|
self.configure_behavior(
|
||||||
|
flight,
|
||||||
|
group,
|
||||||
|
react_on_threat=OptReactOnThreat.Values.EvadeFire,
|
||||||
|
roe=OptROE.Values.WeaponHold,
|
||||||
|
restrict_jettison=True,
|
||||||
|
)
|
||||||
|
|
||||||
def configure_escort(
|
def configure_escort(
|
||||||
self,
|
self,
|
||||||
group: FlyingGroup,
|
group: FlyingGroup,
|
||||||
@ -1535,6 +1590,8 @@ class AircraftConflictGenerator:
|
|||||||
self.configure_sweep(group, package, flight, dynamic_runways)
|
self.configure_sweep(group, package, flight, dynamic_runways)
|
||||||
elif flight_type == FlightType.AEWC:
|
elif flight_type == FlightType.AEWC:
|
||||||
self.configure_awacs(group, package, flight, dynamic_runways)
|
self.configure_awacs(group, package, flight, dynamic_runways)
|
||||||
|
elif flight_type == FlightType.REFUELING:
|
||||||
|
self.configure_refueling(group, package, flight, dynamic_runways)
|
||||||
elif flight_type in [FlightType.CAS, FlightType.BAI]:
|
elif flight_type in [FlightType.CAS, FlightType.BAI]:
|
||||||
self.configure_cas(group, package, flight, dynamic_runways)
|
self.configure_cas(group, package, flight, dynamic_runways)
|
||||||
elif flight_type == FlightType.DEAD:
|
elif flight_type == FlightType.DEAD:
|
||||||
@ -1602,7 +1659,7 @@ class AircraftConflictGenerator:
|
|||||||
|
|
||||||
for idx, point in enumerate(filtered_points):
|
for idx, point in enumerate(filtered_points):
|
||||||
PydcsWaypointBuilder.for_waypoint(
|
PydcsWaypointBuilder.for_waypoint(
|
||||||
point, group, package, flight, self.m
|
point, group, package, flight, self.m, self.air_support
|
||||||
).build()
|
).build()
|
||||||
|
|
||||||
# Set here rather than when the FlightData is created so they waypoints
|
# Set here rather than when the FlightData is created so they waypoints
|
||||||
@ -1676,12 +1733,14 @@ class PydcsWaypointBuilder:
|
|||||||
package: Package,
|
package: Package,
|
||||||
flight: Flight,
|
flight: Flight,
|
||||||
mission: Mission,
|
mission: Mission,
|
||||||
|
air_support: AirSupport,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.waypoint = waypoint
|
self.waypoint = waypoint
|
||||||
self.group = group
|
self.group = group
|
||||||
self.package = package
|
self.package = package
|
||||||
self.flight = flight
|
self.flight = flight
|
||||||
self.mission = mission
|
self.mission = mission
|
||||||
|
self.air_support = air_support
|
||||||
|
|
||||||
def build(self) -> MovingPoint:
|
def build(self) -> MovingPoint:
|
||||||
waypoint = self.group.add_waypoint(
|
waypoint = self.group.add_waypoint(
|
||||||
@ -1717,6 +1776,7 @@ class PydcsWaypointBuilder:
|
|||||||
package: Package,
|
package: Package,
|
||||||
flight: Flight,
|
flight: Flight,
|
||||||
mission: Mission,
|
mission: Mission,
|
||||||
|
air_support: AirSupport,
|
||||||
) -> PydcsWaypointBuilder:
|
) -> PydcsWaypointBuilder:
|
||||||
builders = {
|
builders = {
|
||||||
FlightWaypointType.DROP_OFF: CargoStopBuilder,
|
FlightWaypointType.DROP_OFF: CargoStopBuilder,
|
||||||
@ -1736,7 +1796,7 @@ class PydcsWaypointBuilder:
|
|||||||
FlightWaypointType.PICKUP: CargoStopBuilder,
|
FlightWaypointType.PICKUP: CargoStopBuilder,
|
||||||
}
|
}
|
||||||
builder = builders.get(waypoint.waypoint_type, DefaultWaypointBuilder)
|
builder = builders.get(waypoint.waypoint_type, DefaultWaypointBuilder)
|
||||||
return builder(waypoint, group, package, flight, mission)
|
return builder(waypoint, group, package, flight, mission, air_support)
|
||||||
|
|
||||||
def _viggen_client_tot(self) -> bool:
|
def _viggen_client_tot(self) -> bool:
|
||||||
"""Viggen player aircraft consider any waypoint with a TOT set to be a target ("M") waypoint.
|
"""Viggen player aircraft consider any waypoint with a TOT set to be a target ("M") waypoint.
|
||||||
@ -2119,6 +2179,8 @@ class RaceTrackBuilder(PydcsWaypointBuilder):
|
|||||||
# is their first priority and they will not engage any targets because
|
# is their first priority and they will not engage any targets because
|
||||||
# they're fully focused on orbiting. If the STE task is first, they will
|
# they're fully focused on orbiting. If the STE task is first, they will
|
||||||
# engage targets if available and orbit if they find nothing to shoot.
|
# engage targets if available and orbit if they find nothing to shoot.
|
||||||
|
if self.flight.flight_type is FlightType.REFUELING:
|
||||||
|
self.configure_refueling_actions(waypoint)
|
||||||
|
|
||||||
# TODO: Move the properties of this task into the flight plan?
|
# TODO: Move the properties of this task into the flight plan?
|
||||||
# CAP is the only current user of this so it's not a big deal, but might
|
# CAP is the only current user of this so it's not a big deal, but might
|
||||||
@ -2133,17 +2195,48 @@ class RaceTrackBuilder(PydcsWaypointBuilder):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
racetrack = ControlledTask(
|
# TODO: Set orbit speeds for all race tracks and remove this special case.
|
||||||
OrbitAction(
|
if isinstance(flight_plan, RefuelingFlightPlan):
|
||||||
|
orbit = OrbitAction(
|
||||||
|
altitude=waypoint.alt,
|
||||||
|
pattern=OrbitAction.OrbitPattern.RaceTrack,
|
||||||
|
speed=int(flight_plan.patrol_speed.kph),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
orbit = OrbitAction(
|
||||||
altitude=waypoint.alt, pattern=OrbitAction.OrbitPattern.RaceTrack
|
altitude=waypoint.alt, pattern=OrbitAction.OrbitPattern.RaceTrack
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
racetrack = ControlledTask(orbit)
|
||||||
self.set_waypoint_tot(waypoint, flight_plan.patrol_start_time)
|
self.set_waypoint_tot(waypoint, flight_plan.patrol_start_time)
|
||||||
racetrack.stop_after_time(int(flight_plan.patrol_end_time.total_seconds()))
|
racetrack.stop_after_time(int(flight_plan.patrol_end_time.total_seconds()))
|
||||||
waypoint.add_task(racetrack)
|
waypoint.add_task(racetrack)
|
||||||
|
|
||||||
return waypoint
|
return waypoint
|
||||||
|
|
||||||
|
def configure_refueling_actions(self, waypoint: MovingPoint) -> None:
|
||||||
|
waypoint.add_task(Tanker())
|
||||||
|
|
||||||
|
if self.flight.unit_type != IL_78M:
|
||||||
|
tanker_info = self.air_support.tankers[-1]
|
||||||
|
tacan = tanker_info.tacan
|
||||||
|
tacan_callsign = {
|
||||||
|
"Texaco": "TEX",
|
||||||
|
"Arco": "ARC",
|
||||||
|
"Shell": "SHL",
|
||||||
|
}.get(tanker_info.callsign)
|
||||||
|
|
||||||
|
waypoint.add_task(
|
||||||
|
ActivateBeaconCommand(
|
||||||
|
tacan.number,
|
||||||
|
tacan.band.value,
|
||||||
|
tacan_callsign,
|
||||||
|
bearing=True,
|
||||||
|
unit_id=self.group.units[0].id,
|
||||||
|
aa=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class RaceTrackEndBuilder(PydcsWaypointBuilder):
|
class RaceTrackEndBuilder(PydcsWaypointBuilder):
|
||||||
def build(self) -> MovingPoint:
|
def build(self) -> MovingPoint:
|
||||||
|
|||||||
@ -54,6 +54,8 @@ class TankerInfo:
|
|||||||
variant: str
|
variant: str
|
||||||
freq: RadioFrequency
|
freq: RadioFrequency
|
||||||
tacan: TacanChannel
|
tacan: TacanChannel
|
||||||
|
start_time: Optional[timedelta]
|
||||||
|
end_time: Optional[timedelta]
|
||||||
blue: bool
|
blue: bool
|
||||||
|
|
||||||
|
|
||||||
@ -100,84 +102,86 @@ class AirSupportConflictGenerator:
|
|||||||
else self.conflict.red_cp
|
else self.conflict.red_cp
|
||||||
)
|
)
|
||||||
|
|
||||||
fallback_tanker_number = 0
|
if not self.game.settings.disable_legacy_tanker:
|
||||||
|
|
||||||
for i, tanker_unit_type in enumerate(
|
fallback_tanker_number = 0
|
||||||
self.game.faction_for(player=True).tankers
|
|
||||||
):
|
for i, tanker_unit_type in enumerate(
|
||||||
alt, airspeed = self._get_tanker_params(tanker_unit_type)
|
self.game.faction_for(player=True).tankers
|
||||||
variant = db.unit_type_name(tanker_unit_type)
|
):
|
||||||
freq = self.radio_registry.alloc_uhf()
|
alt, airspeed = self._get_tanker_params(tanker_unit_type)
|
||||||
tacan = self.tacan_registry.alloc_for_band(TacanBand.Y)
|
variant = db.unit_type_name(tanker_unit_type)
|
||||||
tanker_heading = (
|
freq = self.radio_registry.alloc_uhf()
|
||||||
self.conflict.red_cp.position.heading_between_point(
|
tacan = self.tacan_registry.alloc_for_band(TacanBand.Y)
|
||||||
self.conflict.blue_cp.position
|
tanker_heading = (
|
||||||
|
self.conflict.red_cp.position.heading_between_point(
|
||||||
|
self.conflict.blue_cp.position
|
||||||
|
)
|
||||||
|
+ TANKER_HEADING_OFFSET * i
|
||||||
)
|
)
|
||||||
+ TANKER_HEADING_OFFSET * i
|
tanker_position = player_cp.position.point_from_heading(
|
||||||
)
|
tanker_heading, TANKER_DISTANCE
|
||||||
tanker_position = player_cp.position.point_from_heading(
|
)
|
||||||
tanker_heading, TANKER_DISTANCE
|
tanker_group = self.mission.refuel_flight(
|
||||||
)
|
country=self.mission.country(self.game.player_country),
|
||||||
tanker_group = self.mission.refuel_flight(
|
name=namegen.next_tanker_name(
|
||||||
country=self.mission.country(self.game.player_country),
|
self.mission.country(self.game.player_country), tanker_unit_type
|
||||||
name=namegen.next_tanker_name(
|
),
|
||||||
self.mission.country(self.game.player_country), tanker_unit_type
|
airport=None,
|
||||||
),
|
plane_type=tanker_unit_type,
|
||||||
airport=None,
|
position=tanker_position,
|
||||||
plane_type=tanker_unit_type,
|
altitude=alt,
|
||||||
position=tanker_position,
|
race_distance=58000,
|
||||||
altitude=alt,
|
frequency=freq.mhz,
|
||||||
race_distance=58000,
|
start_type=StartType.Warm,
|
||||||
frequency=freq.mhz,
|
speed=airspeed,
|
||||||
start_type=StartType.Warm,
|
tacanchannel=str(tacan),
|
||||||
speed=airspeed,
|
)
|
||||||
tacanchannel=str(tacan),
|
tanker_group.set_frequency(freq.mhz)
|
||||||
)
|
|
||||||
tanker_group.set_frequency(freq.mhz)
|
|
||||||
|
|
||||||
callsign = callsign_for_support_unit(tanker_group)
|
callsign = callsign_for_support_unit(tanker_group)
|
||||||
tacan_callsign = {
|
tacan_callsign = {
|
||||||
"Texaco": "TEX",
|
"Texaco": "TEX",
|
||||||
"Arco": "ARC",
|
"Arco": "ARC",
|
||||||
"Shell": "SHL",
|
"Shell": "SHL",
|
||||||
}.get(callsign)
|
}.get(callsign)
|
||||||
if tacan_callsign is None:
|
if tacan_callsign is None:
|
||||||
# The dict above is all the callsigns currently in the game, but
|
# The dict above is all the callsigns currently in the game, but
|
||||||
# non-Western countries don't use the callsigns and instead just
|
# non-Western countries don't use the callsigns and instead just
|
||||||
# use numbers. It's possible that none of those nations have
|
# use numbers. It's possible that none of those nations have
|
||||||
# TACAN compatible refueling aircraft, but fallback just in
|
# TACAN compatible refueling aircraft, but fallback just in
|
||||||
# case.
|
# case.
|
||||||
tacan_callsign = f"TK{fallback_tanker_number}"
|
tacan_callsign = f"TK{fallback_tanker_number}"
|
||||||
fallback_tanker_number += 1
|
fallback_tanker_number += 1
|
||||||
|
|
||||||
if tanker_unit_type != IL_78M:
|
if tanker_unit_type != IL_78M:
|
||||||
# Override PyDCS tacan channel.
|
# Override PyDCS tacan channel.
|
||||||
tanker_group.points[0].tasks.pop()
|
tanker_group.points[0].tasks.pop()
|
||||||
tanker_group.points[0].tasks.append(
|
tanker_group.points[0].tasks.append(
|
||||||
ActivateBeaconCommand(
|
ActivateBeaconCommand(
|
||||||
tacan.number,
|
tacan.number,
|
||||||
tacan.band.value,
|
tacan.band.value,
|
||||||
tacan_callsign,
|
tacan_callsign,
|
||||||
True,
|
True,
|
||||||
tanker_group.units[0].id,
|
tanker_group.units[0].id,
|
||||||
True,
|
True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
tanker_group.points[0].tasks.append(SetInvisibleCommand(True))
|
||||||
|
tanker_group.points[0].tasks.append(SetImmortalCommand(True))
|
||||||
|
|
||||||
|
self.air_support.tankers.append(
|
||||||
|
TankerInfo(
|
||||||
|
str(tanker_group.name),
|
||||||
|
callsign,
|
||||||
|
variant,
|
||||||
|
freq,
|
||||||
|
tacan,
|
||||||
|
blue=True,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
tanker_group.points[0].tasks.append(SetInvisibleCommand(True))
|
|
||||||
tanker_group.points[0].tasks.append(SetImmortalCommand(True))
|
|
||||||
|
|
||||||
self.air_support.tankers.append(
|
|
||||||
TankerInfo(
|
|
||||||
str(tanker_group.name),
|
|
||||||
callsign,
|
|
||||||
variant,
|
|
||||||
freq,
|
|
||||||
tacan,
|
|
||||||
blue=True,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if not self.game.settings.disable_legacy_aewc:
|
if not self.game.settings.disable_legacy_aewc:
|
||||||
possible_awacs = [
|
possible_awacs = [
|
||||||
a
|
a
|
||||||
|
|||||||
@ -183,6 +183,7 @@ class Package:
|
|||||||
FlightType.TARCAP,
|
FlightType.TARCAP,
|
||||||
FlightType.BARCAP,
|
FlightType.BARCAP,
|
||||||
FlightType.AEWC,
|
FlightType.AEWC,
|
||||||
|
FlightType.REFUELING,
|
||||||
FlightType.SWEEP,
|
FlightType.SWEEP,
|
||||||
FlightType.ESCORT,
|
FlightType.ESCORT,
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import math
|
||||||
import operator
|
import operator
|
||||||
import random
|
import random
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
@ -524,6 +525,24 @@ class ObjectiveFinder:
|
|||||||
raise RuntimeError("Found no friendly control points. You probably lost.")
|
raise RuntimeError("Found no friendly control points. You probably lost.")
|
||||||
return farthest
|
return farthest
|
||||||
|
|
||||||
|
def closest_friendly_control_point(self) -> ControlPoint:
|
||||||
|
"""Finds the friendly control point that is closest to any threats."""
|
||||||
|
threat_zones = self.game.threat_zone_for(not self.is_player)
|
||||||
|
|
||||||
|
closest = None
|
||||||
|
min_distance = meters(math.inf)
|
||||||
|
for cp in self.friendly_control_points():
|
||||||
|
if isinstance(cp, OffMapSpawn):
|
||||||
|
continue
|
||||||
|
distance = threat_zones.distance_to_threat(cp.position)
|
||||||
|
if distance < min_distance:
|
||||||
|
closest = cp
|
||||||
|
min_distance = distance
|
||||||
|
|
||||||
|
if closest is None:
|
||||||
|
raise RuntimeError("Found no friendly control points. You probably lost.")
|
||||||
|
return closest
|
||||||
|
|
||||||
def enemy_control_points(self) -> Iterator[ControlPoint]:
|
def enemy_control_points(self) -> Iterator[ControlPoint]:
|
||||||
"""Iterates over all enemy control points."""
|
"""Iterates over all enemy control points."""
|
||||||
return (
|
return (
|
||||||
@ -582,6 +601,7 @@ class CoalitionMissionPlanner:
|
|||||||
MAX_SEAD_RANGE = nautical_miles(150)
|
MAX_SEAD_RANGE = nautical_miles(150)
|
||||||
MAX_STRIKE_RANGE = nautical_miles(150)
|
MAX_STRIKE_RANGE = nautical_miles(150)
|
||||||
MAX_AWEC_RANGE = nautical_miles(200)
|
MAX_AWEC_RANGE = nautical_miles(200)
|
||||||
|
MAX_TANKER_RANGE = nautical_miles(200)
|
||||||
|
|
||||||
def __init__(self, game: Game, is_player: bool) -> None:
|
def __init__(self, game: Game, is_player: bool) -> None:
|
||||||
self.game = game
|
self.game = game
|
||||||
@ -628,6 +648,11 @@ class CoalitionMissionPlanner:
|
|||||||
asap=True,
|
asap=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
yield ProposedMission(
|
||||||
|
self.objective_finder.closest_friendly_control_point(),
|
||||||
|
[ProposedFlight(FlightType.REFUELING, 1, self.MAX_TANKER_RANGE)],
|
||||||
|
)
|
||||||
|
|
||||||
# Find friendly CPs within 100 nmi from an enemy airfield, plan CAP.
|
# Find friendly CPs within 100 nmi from an enemy airfield, plan CAP.
|
||||||
for cp in self.objective_finder.vulnerable_control_points():
|
for cp in self.objective_finder.vulnerable_control_points():
|
||||||
# Plan CAP in such a way, that it is established during the whole desired mission length
|
# Plan CAP in such a way, that it is established during the whole desired mission length
|
||||||
|
|||||||
@ -51,10 +51,13 @@ from dcs.planes import (
|
|||||||
F_5E_3,
|
F_5E_3,
|
||||||
F_86F_Sabre,
|
F_86F_Sabre,
|
||||||
IL_76MD,
|
IL_76MD,
|
||||||
I_16,
|
IL_78M,
|
||||||
JF_17,
|
JF_17,
|
||||||
J_11A,
|
J_11A,
|
||||||
Ju_88A4,
|
Ju_88A4,
|
||||||
|
KC130,
|
||||||
|
KC135MPRS,
|
||||||
|
KC_135,
|
||||||
KJ_2000,
|
KJ_2000,
|
||||||
L_39ZA,
|
L_39ZA,
|
||||||
MQ_9_Reaper,
|
MQ_9_Reaper,
|
||||||
@ -77,6 +80,7 @@ from dcs.planes import (
|
|||||||
P_51D_30_NA,
|
P_51D_30_NA,
|
||||||
RQ_1A_Predator,
|
RQ_1A_Predator,
|
||||||
S_3B,
|
S_3B,
|
||||||
|
S_3B_Tanker,
|
||||||
SpitfireLFMkIX,
|
SpitfireLFMkIX,
|
||||||
SpitfireLFMkIXCW,
|
SpitfireLFMkIXCW,
|
||||||
Su_17M4,
|
Su_17M4,
|
||||||
@ -103,8 +107,8 @@ from dcs.unittype import FlyingType
|
|||||||
from gen.flights.flight import FlightType
|
from gen.flights.flight import FlightType
|
||||||
from pydcs_extensions.a4ec.a4ec import A_4E_C
|
from pydcs_extensions.a4ec.a4ec import A_4E_C
|
||||||
from pydcs_extensions.f22a.f22a import F_22A
|
from pydcs_extensions.f22a.f22a import F_22A
|
||||||
from pydcs_extensions.jas39.jas39 import JAS39Gripen, JAS39Gripen_AG
|
|
||||||
from pydcs_extensions.hercules.hercules import Hercules
|
from pydcs_extensions.hercules.hercules import Hercules
|
||||||
|
from pydcs_extensions.jas39.jas39 import JAS39Gripen, JAS39Gripen_AG
|
||||||
from pydcs_extensions.mb339.mb339 import MB_339PAN
|
from pydcs_extensions.mb339.mb339 import MB_339PAN
|
||||||
from pydcs_extensions.su57.su57 import Su_57
|
from pydcs_extensions.su57.su57 import Su_57
|
||||||
|
|
||||||
@ -401,6 +405,15 @@ AEWC_CAPABLE = [
|
|||||||
KJ_2000,
|
KJ_2000,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Priority is given to the tankers that can carry the most fuel.
|
||||||
|
REFUELING_CAPABALE = [
|
||||||
|
KC_135,
|
||||||
|
KC135MPRS,
|
||||||
|
IL_78M,
|
||||||
|
KC130,
|
||||||
|
S_3B_Tanker,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def aircraft_for_task(task: FlightType) -> List[Type[FlyingType]]:
|
def aircraft_for_task(task: FlightType) -> List[Type[FlyingType]]:
|
||||||
cap_missions = (FlightType.BARCAP, FlightType.TARCAP, FlightType.SWEEP)
|
cap_missions = (FlightType.BARCAP, FlightType.TARCAP, FlightType.SWEEP)
|
||||||
@ -428,6 +441,8 @@ def aircraft_for_task(task: FlightType) -> List[Type[FlyingType]]:
|
|||||||
return CAP_CAPABLE
|
return CAP_CAPABLE
|
||||||
elif task == FlightType.AEWC:
|
elif task == FlightType.AEWC:
|
||||||
return AEWC_CAPABLE
|
return AEWC_CAPABLE
|
||||||
|
elif task == FlightType.REFUELING:
|
||||||
|
return REFUELING_CAPABALE
|
||||||
elif task == FlightType.TRANSPORT:
|
elif task == FlightType.TRANSPORT:
|
||||||
return TRANSPORT_CAPABLE
|
return TRANSPORT_CAPABLE
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -69,6 +69,7 @@ class FlightType(Enum):
|
|||||||
AEWC = "AEW&C"
|
AEWC = "AEW&C"
|
||||||
TRANSPORT = "Transport"
|
TRANSPORT = "Transport"
|
||||||
SEAD_ESCORT = "SEAD Escort"
|
SEAD_ESCORT = "SEAD Escort"
|
||||||
|
REFUELING = "Refueling"
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.value
|
return self.value
|
||||||
|
|||||||
@ -16,12 +16,21 @@ from functools import cached_property
|
|||||||
from typing import Iterator, List, Optional, Set, TYPE_CHECKING, Tuple
|
from typing import Iterator, List, Optional, Set, TYPE_CHECKING, Tuple
|
||||||
|
|
||||||
from dcs.mapping import Point
|
from dcs.mapping import Point
|
||||||
from dcs.planes import E_3A, E_2C, A_50, KJ_2000
|
from dcs.planes import (
|
||||||
|
E_3A,
|
||||||
|
E_2C,
|
||||||
|
A_50,
|
||||||
|
IL_78M,
|
||||||
|
KC130,
|
||||||
|
KC135MPRS,
|
||||||
|
KC_135,
|
||||||
|
KJ_2000,
|
||||||
|
S_3B_Tanker,
|
||||||
|
)
|
||||||
from dcs.unit import Unit
|
from dcs.unit import Unit
|
||||||
from shapely.geometry import Point as ShapelyPoint
|
from shapely.geometry import Point as ShapelyPoint
|
||||||
|
|
||||||
from game.data.doctrine import Doctrine
|
from game.data.doctrine import Doctrine
|
||||||
from game.squadrons import Pilot
|
|
||||||
from game.theater import (
|
from game.theater import (
|
||||||
Airfield,
|
Airfield,
|
||||||
ControlPoint,
|
ControlPoint,
|
||||||
@ -31,7 +40,7 @@ from game.theater import (
|
|||||||
TheaterGroundObject,
|
TheaterGroundObject,
|
||||||
)
|
)
|
||||||
from game.theater.theatergroundobject import EwrGroundObject
|
from game.theater.theatergroundobject import EwrGroundObject
|
||||||
from game.utils import Distance, Speed, feet, meters, nautical_miles
|
from game.utils import Distance, Speed, feet, meters, nautical_miles, knots
|
||||||
from .closestairfields import ObjectiveDistanceCache
|
from .closestairfields import ObjectiveDistanceCache
|
||||||
from .flight import Flight, FlightType, FlightWaypoint, FlightWaypointType
|
from .flight import Flight, FlightType, FlightWaypoint, FlightWaypointType
|
||||||
from .traveltime import GroundSpeed, TravelTime
|
from .traveltime import GroundSpeed, TravelTime
|
||||||
@ -769,6 +778,28 @@ class AwacsFlightPlan(LoiterFlightPlan):
|
|||||||
return self.push_time
|
return self.push_time
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class RefuelingFlightPlan(PatrollingFlightPlan):
|
||||||
|
takeoff: FlightWaypoint
|
||||||
|
land: FlightWaypoint
|
||||||
|
divert: Optional[FlightWaypoint]
|
||||||
|
bullseye: FlightWaypoint
|
||||||
|
|
||||||
|
#: Racetrack speed.
|
||||||
|
patrol_speed: Speed
|
||||||
|
|
||||||
|
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
||||||
|
yield self.takeoff
|
||||||
|
yield from self.nav_to
|
||||||
|
yield self.patrol_start
|
||||||
|
yield self.patrol_end
|
||||||
|
yield from self.nav_from
|
||||||
|
yield self.land
|
||||||
|
if self.divert is not None:
|
||||||
|
yield self.divert
|
||||||
|
yield self.bullseye
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class AirliftFlightPlan(FlightPlan):
|
class AirliftFlightPlan(FlightPlan):
|
||||||
takeoff: FlightWaypoint
|
takeoff: FlightWaypoint
|
||||||
@ -919,6 +950,8 @@ class FlightPlanBuilder:
|
|||||||
return self.generate_aewc(flight)
|
return self.generate_aewc(flight)
|
||||||
elif task == FlightType.TRANSPORT:
|
elif task == FlightType.TRANSPORT:
|
||||||
return self.generate_transport(flight)
|
return self.generate_transport(flight)
|
||||||
|
elif task == FlightType.REFUELING:
|
||||||
|
return self.generate_refueling_racetrack(flight)
|
||||||
raise PlanningError(f"{task} flight plan generation not implemented")
|
raise PlanningError(f"{task} flight plan generation not implemented")
|
||||||
|
|
||||||
def regenerate_package_waypoints(self) -> None:
|
def regenerate_package_waypoints(self) -> None:
|
||||||
@ -1612,6 +1645,88 @@ class FlightPlanBuilder:
|
|||||||
bullseye=builder.bullseye(),
|
bullseye=builder.bullseye(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def generate_refueling_racetrack(self, flight: Flight) -> RefuelingFlightPlan:
|
||||||
|
location = self.package.target
|
||||||
|
|
||||||
|
closest_boundary = self.threat_zones.closest_boundary(location.position)
|
||||||
|
heading_to_threat_boundary = location.position.heading_between_point(
|
||||||
|
closest_boundary
|
||||||
|
)
|
||||||
|
distance_to_threat = meters(
|
||||||
|
location.position.distance_to_point(closest_boundary)
|
||||||
|
)
|
||||||
|
orbit_heading = heading_to_threat_boundary
|
||||||
|
|
||||||
|
# Station 70nm outside the threat zone.
|
||||||
|
threat_buffer = nautical_miles(70)
|
||||||
|
if self.threat_zones.threatened(location.position):
|
||||||
|
orbit_distance = distance_to_threat + threat_buffer
|
||||||
|
else:
|
||||||
|
orbit_distance = distance_to_threat - threat_buffer
|
||||||
|
|
||||||
|
racetrack_center = location.position.point_from_heading(
|
||||||
|
orbit_heading, orbit_distance.meters
|
||||||
|
)
|
||||||
|
|
||||||
|
racetrack_half_distance = Distance.from_nautical_miles(20).meters
|
||||||
|
|
||||||
|
racetrack_start = racetrack_center.point_from_heading(
|
||||||
|
orbit_heading + 90, racetrack_half_distance
|
||||||
|
)
|
||||||
|
racetrack_end = racetrack_center.point_from_heading(
|
||||||
|
orbit_heading - 90, racetrack_half_distance
|
||||||
|
)
|
||||||
|
|
||||||
|
builder = WaypointBuilder(flight, self.game, self.is_player)
|
||||||
|
|
||||||
|
tanker_type = flight.unit_type
|
||||||
|
if tanker_type is KC_135:
|
||||||
|
# ~300 knots IAS.
|
||||||
|
speed = knots(445)
|
||||||
|
altitude = feet(24000)
|
||||||
|
elif tanker_type is KC135MPRS:
|
||||||
|
# ~300 knots IAS.
|
||||||
|
speed = knots(440)
|
||||||
|
altitude = feet(23000)
|
||||||
|
elif tanker_type is KC130:
|
||||||
|
# ~210 knots IAS, roughly the max for the KC-130 at altitude.
|
||||||
|
speed = knots(370)
|
||||||
|
altitude = feet(22000)
|
||||||
|
elif tanker_type is S_3B_Tanker:
|
||||||
|
# ~265 knots IAS.
|
||||||
|
speed = knots(320)
|
||||||
|
altitude = feet(12000)
|
||||||
|
elif tanker_type is IL_78M:
|
||||||
|
# ~280 knots IAS.
|
||||||
|
speed = knots(400)
|
||||||
|
altitude = feet(21000)
|
||||||
|
else:
|
||||||
|
# ~280 knots IAS.
|
||||||
|
speed = knots(400)
|
||||||
|
altitude = feet(21000)
|
||||||
|
|
||||||
|
racetrack = builder.race_track(racetrack_start, racetrack_end, altitude)
|
||||||
|
|
||||||
|
return RefuelingFlightPlan(
|
||||||
|
package=self.package,
|
||||||
|
flight=flight,
|
||||||
|
takeoff=builder.takeoff(flight.departure),
|
||||||
|
nav_to=builder.nav_path(
|
||||||
|
flight.departure.position, racetrack_start, altitude
|
||||||
|
),
|
||||||
|
nav_from=builder.nav_path(racetrack_end, flight.arrival.position, altitude),
|
||||||
|
patrol_start=racetrack[0],
|
||||||
|
patrol_end=racetrack[1],
|
||||||
|
land=builder.land(flight.arrival),
|
||||||
|
divert=builder.divert(flight.divert),
|
||||||
|
bullseye=builder.bullseye(),
|
||||||
|
patrol_duration=timedelta(hours=1),
|
||||||
|
patrol_speed=speed,
|
||||||
|
# TODO: Factor out a common base of the combat and non-combat race-tracks.
|
||||||
|
# No harm in setting this, but we ought to clean up a bit.
|
||||||
|
engagement_distance=meters(0),
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def target_waypoint(
|
def target_waypoint(
|
||||||
flight: Flight, builder: WaypointBuilder, target: StrikeTarget
|
flight: Flight, builder: WaypointBuilder, target: StrikeTarget
|
||||||
|
|||||||
@ -143,4 +143,6 @@ class QUnitInfoWindow(QDialog):
|
|||||||
aircraft_tasks = aircraft_tasks + f"{FlightType.OCA_RUNWAY}, "
|
aircraft_tasks = aircraft_tasks + f"{FlightType.OCA_RUNWAY}, "
|
||||||
if self.unit_type in gen.flights.ai_flight_planner_db.STRIKE_CAPABLE:
|
if self.unit_type in gen.flights.ai_flight_planner_db.STRIKE_CAPABLE:
|
||||||
aircraft_tasks = aircraft_tasks + f"{FlightType.STRIKE}, "
|
aircraft_tasks = aircraft_tasks + f"{FlightType.STRIKE}, "
|
||||||
|
if self.unit_type in gen.flights.ai_flight_planner_db.REFUELING_CAPABALE:
|
||||||
|
aircraft_tasks = aircraft_tasks + f"{FlightType.REFUELING}, "
|
||||||
return aircraft_tasks[:-2]
|
return aircraft_tasks[:-2]
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import Optional, Set, Type
|
from typing import Set, Type
|
||||||
|
|
||||||
from PySide2.QtCore import Qt
|
from PySide2.QtCore import Qt
|
||||||
from PySide2.QtWidgets import (
|
from PySide2.QtWidgets import (
|
||||||
@ -13,7 +13,6 @@ from PySide2.QtWidgets import (
|
|||||||
QWidget,
|
QWidget,
|
||||||
)
|
)
|
||||||
from dcs.helicopters import helicopter_map
|
from dcs.helicopters import helicopter_map
|
||||||
from dcs.task import CAP, CAS, AWACS, Transport
|
|
||||||
from dcs.unittype import FlyingType, UnitType
|
from dcs.unittype import FlyingType, UnitType
|
||||||
|
|
||||||
from game import db
|
from game import db
|
||||||
@ -45,8 +44,6 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour):
|
|||||||
def init_ui(self):
|
def init_ui(self):
|
||||||
main_layout = QVBoxLayout()
|
main_layout = QVBoxLayout()
|
||||||
|
|
||||||
tasks = [CAP, CAS, AWACS, Transport]
|
|
||||||
|
|
||||||
scroll_content = QWidget()
|
scroll_content = QWidget()
|
||||||
task_box_layout = QGridLayout()
|
task_box_layout = QGridLayout()
|
||||||
row = 0
|
row = 0
|
||||||
|
|||||||
@ -494,6 +494,28 @@ class QSettingsWindow(QDialog):
|
|||||||
general_layout.addWidget(old_awac_label, 1, 0)
|
general_layout.addWidget(old_awac_label, 1, 0)
|
||||||
general_layout.addWidget(old_awac, 1, 1, Qt.AlignRight)
|
general_layout.addWidget(old_awac, 1, 1, Qt.AlignRight)
|
||||||
|
|
||||||
|
def set_old_tanker(value: bool) -> None:
|
||||||
|
self.game.settings.disable_legacy_tanker = not value
|
||||||
|
|
||||||
|
old_tanker = QCheckBox()
|
||||||
|
old_tanker.setChecked(not self.game.settings.disable_legacy_tanker)
|
||||||
|
old_tanker.toggled.connect(set_old_tanker)
|
||||||
|
|
||||||
|
old_tanker_info = (
|
||||||
|
"If checked, an invulnerable friendly Tanker aircraft that begins the "
|
||||||
|
"mission on station will be be spawned. This behavior will be removed in a "
|
||||||
|
"future release."
|
||||||
|
)
|
||||||
|
|
||||||
|
old_tanker.setToolTip(old_tanker_info)
|
||||||
|
old_tanker_label = QLabel(
|
||||||
|
"Spawn invulnerable, always-available Tanker aircraft (deprecated)."
|
||||||
|
)
|
||||||
|
old_tanker_label.setToolTip(old_tanker_info)
|
||||||
|
|
||||||
|
general_layout.addWidget(old_tanker_label, 2, 0)
|
||||||
|
general_layout.addWidget(old_tanker, 2, 1, Qt.AlignRight)
|
||||||
|
|
||||||
campaign_layout.addWidget(HqAutomationSettingsBox(self.game))
|
campaign_layout.addWidget(HqAutomationSettingsBox(self.game))
|
||||||
|
|
||||||
def initGeneratorLayout(self):
|
def initGeneratorLayout(self):
|
||||||
|
|||||||
@ -32,7 +32,8 @@
|
|||||||
],
|
],
|
||||||
"tankers": [
|
"tankers": [
|
||||||
"KC_135",
|
"KC_135",
|
||||||
"KC130"
|
"KC130",
|
||||||
|
"S-3B Tanker"
|
||||||
],
|
],
|
||||||
"frontline_units": [
|
"frontline_units": [
|
||||||
"MBT_M1A2_Abrams",
|
"MBT_M1A2_Abrams",
|
||||||
|
|||||||
@ -33,7 +33,9 @@
|
|||||||
],
|
],
|
||||||
"tankers": [
|
"tankers": [
|
||||||
"KC_135",
|
"KC_135",
|
||||||
"KC130"
|
"KC135MPRS",
|
||||||
|
"KC130",
|
||||||
|
"S-3B Tanker"
|
||||||
],
|
],
|
||||||
"frontline_units": [
|
"frontline_units": [
|
||||||
"MBT_M1A2_Abrams",
|
"MBT_M1A2_Abrams",
|
||||||
|
|||||||
@ -28,7 +28,9 @@
|
|||||||
],
|
],
|
||||||
"tankers": [
|
"tankers": [
|
||||||
"KC_135",
|
"KC_135",
|
||||||
"KC130"
|
"KC135MPRS",
|
||||||
|
"KC130",
|
||||||
|
"S-3B Tanker"
|
||||||
],
|
],
|
||||||
"frontline_units": [
|
"frontline_units": [
|
||||||
"MBT_M1A2_Abrams",
|
"MBT_M1A2_Abrams",
|
||||||
|
|||||||
@ -15,6 +15,10 @@
|
|||||||
"awacs": [
|
"awacs": [
|
||||||
"E_2C"
|
"E_2C"
|
||||||
],
|
],
|
||||||
|
"tankers": [
|
||||||
|
"KC_135",
|
||||||
|
"KC130"
|
||||||
|
],
|
||||||
"frontline_units": [
|
"frontline_units": [
|
||||||
"MBT_M60A3_Patton",
|
"MBT_M60A3_Patton",
|
||||||
"APC_M113",
|
"APC_M113",
|
||||||
|
|||||||
@ -18,6 +18,10 @@
|
|||||||
"awacs": [
|
"awacs": [
|
||||||
"E_2C"
|
"E_2C"
|
||||||
],
|
],
|
||||||
|
"tankers": [
|
||||||
|
"KC_135",
|
||||||
|
"KC130"
|
||||||
|
],
|
||||||
"frontline_units": [
|
"frontline_units": [
|
||||||
"MBT_M60A3_Patton",
|
"MBT_M60A3_Patton",
|
||||||
"APC_M113",
|
"APC_M113",
|
||||||
|
|||||||
@ -32,7 +32,8 @@
|
|||||||
],
|
],
|
||||||
"tankers": [
|
"tankers": [
|
||||||
"KC_135",
|
"KC_135",
|
||||||
"KC130"
|
"KC130",
|
||||||
|
"S-3B Tanker"
|
||||||
],
|
],
|
||||||
"frontline_units": [
|
"frontline_units": [
|
||||||
"MBT_M1A2_Abrams",
|
"MBT_M1A2_Abrams",
|
||||||
|
|||||||
@ -32,7 +32,9 @@
|
|||||||
],
|
],
|
||||||
"tankers": [
|
"tankers": [
|
||||||
"KC_135",
|
"KC_135",
|
||||||
"KC130"
|
"KC135MPRS",
|
||||||
|
"KC130",
|
||||||
|
"S-3B Tanker"
|
||||||
],
|
],
|
||||||
"frontline_units": [
|
"frontline_units": [
|
||||||
"MBT_M1A2_Abrams",
|
"MBT_M1A2_Abrams",
|
||||||
|
|||||||
@ -33,7 +33,9 @@
|
|||||||
],
|
],
|
||||||
"tankers": [
|
"tankers": [
|
||||||
"KC_135",
|
"KC_135",
|
||||||
"KC130"
|
"KC135MPRS",
|
||||||
|
"KC130",
|
||||||
|
"S-3B Tanker"
|
||||||
],
|
],
|
||||||
"frontline_units": [
|
"frontline_units": [
|
||||||
"MBT_M1A2_Abrams",
|
"MBT_M1A2_Abrams",
|
||||||
|
|||||||
@ -28,7 +28,9 @@
|
|||||||
],
|
],
|
||||||
"tankers": [
|
"tankers": [
|
||||||
"KC_135",
|
"KC_135",
|
||||||
"KC130"
|
"KC135MPRS",
|
||||||
|
"KC130",
|
||||||
|
"S-3B Tanker"
|
||||||
],
|
],
|
||||||
"frontline_units": [
|
"frontline_units": [
|
||||||
"MBT_M1A2_Abrams",
|
"MBT_M1A2_Abrams",
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
"E_2C"
|
"E_2C"
|
||||||
],
|
],
|
||||||
"tankers": [
|
"tankers": [
|
||||||
"S_3B_Tanker"
|
"S-3B Tanker"
|
||||||
],
|
],
|
||||||
"frontline_units": [
|
"frontline_units": [
|
||||||
"MBT_M60A3_Patton",
|
"MBT_M60A3_Patton",
|
||||||
|
|||||||
BIN
resources/ui/units/aircrafts/icons/KC130_24.jpg
Normal file
BIN
resources/ui/units/aircrafts/icons/KC130_24.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
BIN
resources/ui/units/aircrafts/icons/KC135MPRS_24.jpg
Normal file
BIN
resources/ui/units/aircrafts/icons/KC135MPRS_24.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
BIN
resources/ui/units/aircrafts/icons/S-3B Tanker_24.jpg
Normal file
BIN
resources/ui/units/aircrafts/icons/S-3B Tanker_24.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
@ -422,6 +422,36 @@
|
|||||||
"year-of-variant-introduction": "1995"
|
"year-of-variant-introduction": "1995"
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
|
"KC130": [{
|
||||||
|
"default": {
|
||||||
|
"name": "KC-130",
|
||||||
|
"text": "The Lockheed Martin (previously Lockheed) KC-130 is a family of the extended-range tanker version of the C-130 Hercules transport aircraft modified for aerial refueling.",
|
||||||
|
"country-of-origin": "USA",
|
||||||
|
"manufacturer": "Lockheed Martin",
|
||||||
|
"role": "Tanker",
|
||||||
|
"year-of-variant-introduction": "1962"
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
"KC-135": [{
|
||||||
|
"default": {
|
||||||
|
"name": "KC-135 Stratotanker",
|
||||||
|
"text": "The Boeing KC-135 Stratotanker is a military aerial refueling aircraft that was developed from the Boeing 367-80 prototype, alongside the Boeing 707 airliner.",
|
||||||
|
"country-of-origin": "USA",
|
||||||
|
"manufacturer": "Beoing",
|
||||||
|
"role": "Tanker",
|
||||||
|
"year-of-variant-introduction": "1957"
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
"KC135MPRS": [{
|
||||||
|
"default": {
|
||||||
|
"name": "KC-135 Stratotanker MPRS",
|
||||||
|
"text": "The Boeing KC-135 Stratotanker is a military aerial refueling aircraft that was developed from the Boeing 367-80 prototype, alongside the Boeing 707 airliner. This model has the Multi-point Refueling System modification, allowing for probe and drogue refuelling.",
|
||||||
|
"country-of-origin": "USA",
|
||||||
|
"manufacturer": "Beoing",
|
||||||
|
"role": "Tanker",
|
||||||
|
"year-of-variant-introduction": "1994"
|
||||||
|
}
|
||||||
|
}],
|
||||||
"L-39C": [{
|
"L-39C": [{
|
||||||
"default": {
|
"default": {
|
||||||
"name": "L-39C Albatros",
|
"name": "L-39C Albatros",
|
||||||
@ -743,6 +773,16 @@
|
|||||||
"year-of-variant-introduction": "1984"
|
"year-of-variant-introduction": "1984"
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
|
"S-3B Tanker": [{
|
||||||
|
"default": {
|
||||||
|
"name": "S-3B Tanker",
|
||||||
|
"text": "The Lockheed S-3 Viking is a 4-crew, twin-engine turbofan-powered jet aircraft that was used by the U.S. Navy (USN) primarily for anti-submarine warfare. In the late 1990s, the S-3B's mission focus shifted to surface warfare and aerial refueling. The Viking also provided electronic warfare and surface surveillance capabilities to a carrier battle group. A carrier-based, subsonic, all-weather, long-range, multi-mission aircraft, it carried automated weapon systems and was capable of extended missions with in-flight refueling. Because of its characteristic sound, it was nicknamed the \"War Hoover\" after the vacuum cleaner brand. The S-3 was phased out from front-line fleet service aboard aircraft carriers in January 2009, with its missions taken over by aircraft like the P-3C Orion, P-8 Poseidon, Sikorsky SH-60 Seahawk and Boeing F/A-18E/F Super Hornet",
|
||||||
|
"country-of-origin": "USA",
|
||||||
|
"manufacturer": "Lockheed",
|
||||||
|
"role": "Carrier-based Tanker",
|
||||||
|
"year-of-variant-introduction": "1984"
|
||||||
|
}
|
||||||
|
}],
|
||||||
"SA342L": [{
|
"SA342L": [{
|
||||||
"default": {
|
"default": {
|
||||||
"name": "SA 342L Gazelle",
|
"name": "SA 342L Gazelle",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user