Move mission range data into the aircraft type.

The doctrine/task limits were capturing a reasonable average for the
era, but it did a bad job for cases like the Harrier vs the Hornet,
which perform similar missions but have drastically different max
ranges. It also forced us into limiting CAS missions (even those flown
by long range aircraft like the A-10) to 50nm since helicopters could
commonly be fragged to them.

This should allow us to design campaigns without needing airfields to be
a max of ~50-100nm apart.
This commit is contained in:
Dan Albert 2021-07-17 15:31:52 -07:00
parent 04a8040292
commit c65ac5a7cf
43 changed files with 120 additions and 143 deletions

View File

@ -9,6 +9,7 @@ Saves from 3.x are not compatible with 5.0.
* **[Campaign AI]** Overhauled campaign AI target prioritization. This currently only affects the ordering of DEAD missions.
* **[Campaign AI]** Player front line stances can now be automated. Improved stance selection for AI.
* **[Campaign AI]** Reworked layout of hold, join, split, and ingress points. Should result in much shorter flight plans in general while still maintaining safe join/split/hold points.
* **[Campaign AI]** Auto-planning mission range limits are now specified per-aircraft. On average this means that longer range missions will now be plannable. The limit only accounts for the direct distance to the target, not the path taken.
* **[New Game Wizard]** Can now customize the player's air wing before campaign start to disable or rename squadrons.
## Fixes

View File

@ -3,7 +3,8 @@ from typing import Optional, Tuple
from game.commander.missionproposals import ProposedFlight
from game.inventory import GlobalAircraftInventory
from game.squadrons import AirWing, Squadron
from game.theater import ControlPoint
from game.theater import ControlPoint, MissionTarget
from game.utils import meters
from gen.flights.ai_flight_planner_db import aircraft_for_task
from gen.flights.closestairfields import ClosestAirfields
from gen.flights.flight import FlightType
@ -25,7 +26,7 @@ class AircraftAllocator:
self.is_player = is_player
def find_squadron_for_flight(
self, flight: ProposedFlight
self, target: MissionTarget, flight: ProposedFlight
) -> Optional[Tuple[ControlPoint, Squadron]]:
"""Finds aircraft suitable for the given mission.
@ -45,17 +46,13 @@ class AircraftAllocator:
on subsequent calls. If the found aircraft are not used, the caller is
responsible for returning them to the inventory.
"""
return self.find_aircraft_for_task(flight, flight.task)
return self.find_aircraft_for_task(target, flight, flight.task)
def find_aircraft_for_task(
self, flight: ProposedFlight, task: FlightType
self, target: MissionTarget, flight: ProposedFlight, task: FlightType
) -> Optional[Tuple[ControlPoint, Squadron]]:
types = aircraft_for_task(task)
airfields_in_range = self.closest_airfields.operational_airfields_within(
flight.max_distance
)
for airfield in airfields_in_range:
for airfield in self.closest_airfields.operational_airfields:
if not airfield.is_friendly(self.is_player):
continue
inventory = self.global_inventory.for_control_point(airfield)
@ -64,6 +61,9 @@ class AircraftAllocator:
continue
if inventory.available(aircraft) < flight.num_aircraft:
continue
distance_to_target = meters(target.distance_to(airfield))
if distance_to_target > aircraft.max_mission_range:
continue
# Valid location with enough aircraft available. Find a squadron to fit
# the role.
squadrons = self.air_wing.auto_assignable_for_task_with_type(

View File

@ -3,7 +3,6 @@ from enum import Enum, auto
from typing import Optional
from game.theater import MissionTarget
from game.utils import Distance
from gen.flights.flight import FlightType
@ -27,9 +26,6 @@ class ProposedFlight:
#: The number of aircraft required.
num_aircraft: int
#: The maximum distance between the objective and the departure airfield.
max_distance: Distance
#: The type of threat this flight defends against if it is an escort. Escort
#: flights will be pruned if the rest of the package is not threatened by
#: the threat they defend against. If this flight is not an escort, this

View File

@ -44,7 +44,7 @@ class PackageBuilder:
caller should return any previously planned flights to the inventory
using release_planned_aircraft.
"""
assignment = self.allocator.find_squadron_for_flight(plan)
assignment = self.allocator.find_squadron_for_flight(self.package.target, plan)
if assignment is None:
return False
airfield, squadron = assignment

View File

@ -83,7 +83,6 @@ class PackageFulfiller:
missing_types.add(flight.task)
purchase_order = AircraftProcurementRequest(
near=mission.location,
range=flight.max_distance,
task_capability=flight.task,
number=flight.num_aircraft,
)

View File

@ -59,28 +59,23 @@ class PackagePlanningTask(TheaterCommanderTask, Generic[MissionTargetT]):
coalition.ato.add_package(self.package)
@abstractmethod
def propose_flights(self, doctrine: Doctrine) -> None:
def propose_flights(self) -> None:
...
def propose_flight(
self,
task: FlightType,
num_aircraft: int,
max_distance: Optional[Distance],
escort_type: Optional[EscortType] = None,
) -> None:
if max_distance is None:
max_distance = Distance.inf()
self.flights.append(
ProposedFlight(task, num_aircraft, max_distance, escort_type)
)
self.flights.append(ProposedFlight(task, num_aircraft, escort_type))
@property
def asap(self) -> bool:
return False
def fulfill_mission(self, state: TheaterState) -> bool:
self.propose_flights(state.context.coalition.doctrine)
self.propose_flights()
fulfiller = PackageFulfiller(
state.context.coalition,
state.context.theater,
@ -92,20 +87,9 @@ class PackagePlanningTask(TheaterCommanderTask, Generic[MissionTargetT]):
)
return self.package is not None
def propose_common_escorts(self, doctrine: Doctrine) -> None:
self.propose_flight(
FlightType.SEAD_ESCORT,
2,
doctrine.mission_ranges.offensive,
EscortType.Sead,
)
self.propose_flight(
FlightType.ESCORT,
2,
doctrine.mission_ranges.offensive,
EscortType.AirToAir,
)
def propose_common_escorts(self) -> None:
self.propose_flight(FlightType.SEAD_ESCORT, 2, EscortType.Sead)
self.propose_flight(FlightType.ESCORT, 2, EscortType.AirToAir)
def iter_iads_ranges(
self, state: TheaterState, range_type: RangeType

View File

@ -4,7 +4,6 @@ from dataclasses import dataclass
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.data.doctrine import Doctrine
from game.theater import MissionTarget
from gen.flights.flight import FlightType
@ -19,8 +18,8 @@ class PlanAewc(PackagePlanningTask[MissionTarget]):
def apply_effects(self, state: TheaterState) -> None:
state.aewc_targets.remove(self.target)
def propose_flights(self, doctrine: Doctrine) -> None:
self.propose_flight(FlightType.AEWC, 1, doctrine.mission_ranges.aewc)
def propose_flights(self) -> None:
self.propose_flight(FlightType.AEWC, 1)
@property
def asap(self) -> bool:

View File

@ -5,7 +5,6 @@ from dataclasses import dataclass
from game.commander.missionproposals import EscortType
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.data.doctrine import Doctrine
from game.theater.theatergroundobject import NavalGroundObject
from gen.flights.flight import FlightType
@ -22,11 +21,6 @@ class PlanAntiShip(PackagePlanningTask[NavalGroundObject]):
def apply_effects(self, state: TheaterState) -> None:
state.eliminate_ship(self.target)
def propose_flights(self, doctrine: Doctrine) -> None:
self.propose_flight(FlightType.ANTISHIP, 2, doctrine.mission_ranges.offensive)
self.propose_flight(
FlightType.ESCORT,
2,
doctrine.mission_ranges.offensive,
EscortType.AirToAir,
)
def propose_flights(self) -> None:
self.propose_flight(FlightType.ANTISHIP, 2)
self.propose_flight(FlightType.ESCORT, 2, EscortType.AirToAir)

View File

@ -4,7 +4,6 @@ from dataclasses import dataclass
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.data.doctrine import Doctrine
from game.transfers import CargoShip
from gen.flights.flight import FlightType
@ -21,6 +20,6 @@ class PlanAntiShipping(PackagePlanningTask[CargoShip]):
def apply_effects(self, state: TheaterState) -> None:
state.enemy_shipping.remove(self.target)
def propose_flights(self, doctrine: Doctrine) -> None:
self.propose_flight(FlightType.ANTISHIP, 2, doctrine.mission_ranges.offensive)
self.propose_common_escorts(doctrine)
def propose_flights(self) -> None:
self.propose_flight(FlightType.ANTISHIP, 2)
self.propose_common_escorts()

View File

@ -4,7 +4,6 @@ from dataclasses import dataclass
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.data.doctrine import Doctrine
from game.theater.theatergroundobject import VehicleGroupGroundObject
from gen.flights.flight import FlightType
@ -21,6 +20,6 @@ class PlanBai(PackagePlanningTask[VehicleGroupGroundObject]):
def apply_effects(self, state: TheaterState) -> None:
state.eliminate_garrison(self.target)
def propose_flights(self, doctrine: Doctrine) -> None:
self.propose_flight(FlightType.BAI, 2, doctrine.mission_ranges.offensive)
self.propose_common_escorts(doctrine)
def propose_flights(self) -> None:
self.propose_flight(FlightType.BAI, 2)
self.propose_common_escorts()

View File

@ -4,7 +4,6 @@ from dataclasses import dataclass
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.data.doctrine import Doctrine
from game.theater import ControlPoint
from gen.flights.flight import FlightType
@ -19,5 +18,5 @@ class PlanBarcap(PackagePlanningTask[ControlPoint]):
def apply_effects(self, state: TheaterState) -> None:
state.barcaps_needed[self.target] -= 1
def propose_flights(self, doctrine: Doctrine) -> None:
self.propose_flight(FlightType.BARCAP, 2, doctrine.mission_ranges.cap)
def propose_flights(self) -> None:
self.propose_flight(FlightType.BARCAP, 2)

View File

@ -4,7 +4,6 @@ from dataclasses import dataclass
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.data.doctrine import Doctrine
from game.theater import FrontLine
from gen.flights.flight import FlightType
@ -19,6 +18,6 @@ class PlanCas(PackagePlanningTask[FrontLine]):
def apply_effects(self, state: TheaterState) -> None:
state.vulnerable_front_lines.remove(self.target)
def propose_flights(self, doctrine: Doctrine) -> None:
self.propose_flight(FlightType.CAS, 2, doctrine.mission_ranges.cas)
self.propose_flight(FlightType.TARCAP, 2, doctrine.mission_ranges.cap)
def propose_flights(self) -> None:
self.propose_flight(FlightType.CAS, 2)
self.propose_flight(FlightType.TARCAP, 2)

View File

@ -21,6 +21,6 @@ class PlanConvoyInterdiction(PackagePlanningTask[Convoy]):
def apply_effects(self, state: TheaterState) -> None:
state.enemy_convoys.remove(self.target)
def propose_flights(self, doctrine: Doctrine) -> None:
self.propose_flight(FlightType.BAI, 2, doctrine.mission_ranges.offensive)
self.propose_common_escorts(doctrine)
def propose_flights(self) -> None:
self.propose_flight(FlightType.BAI, 2)
self.propose_common_escorts()

View File

@ -5,7 +5,6 @@ from dataclasses import dataclass
from game.commander.missionproposals import EscortType
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.data.doctrine import Doctrine
from game.theater.theatergroundobject import IadsGroundObject
from gen.flights.flight import FlightType
@ -25,8 +24,8 @@ class PlanDead(PackagePlanningTask[IadsGroundObject]):
def apply_effects(self, state: TheaterState) -> None:
state.eliminate_air_defense(self.target)
def propose_flights(self, doctrine: Doctrine) -> None:
self.propose_flight(FlightType.DEAD, 2, doctrine.mission_ranges.offensive)
def propose_flights(self) -> None:
self.propose_flight(FlightType.DEAD, 2)
# Only include SEAD against SAMs that still have emitters. No need to
# suppress an EWR, and SEAD isn't useful against a SAM that no longer has a
@ -41,18 +40,7 @@ class PlanDead(PackagePlanningTask[IadsGroundObject]):
# package is *only* threatened by the target though. Could be improved, but
# needs a decent refactor to the escort planning to do so.
if self.target.has_live_radar_sam:
self.propose_flight(FlightType.SEAD, 2, doctrine.mission_ranges.offensive)
self.propose_flight(FlightType.SEAD, 2)
else:
self.propose_flight(
FlightType.SEAD_ESCORT,
2,
doctrine.mission_ranges.offensive,
EscortType.Sead,
)
self.propose_flight(
FlightType.ESCORT,
2,
doctrine.mission_ranges.offensive,
EscortType.AirToAir,
)
self.propose_flight(FlightType.SEAD_ESCORT, 2, EscortType.Sead)
self.propose_flight(FlightType.ESCORT, 2, EscortType.AirToAir)

View File

@ -4,7 +4,6 @@ from dataclasses import dataclass
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.data.doctrine import Doctrine
from game.theater import ControlPoint
from gen.flights.flight import FlightType
@ -23,10 +22,8 @@ class PlanOcaStrike(PackagePlanningTask[ControlPoint]):
def apply_effects(self, state: TheaterState) -> None:
state.oca_targets.remove(self.target)
def propose_flights(self, doctrine: Doctrine) -> None:
self.propose_flight(FlightType.OCA_RUNWAY, 2, doctrine.mission_ranges.offensive)
def propose_flights(self) -> None:
self.propose_flight(FlightType.OCA_RUNWAY, 2)
if self.aircraft_cold_start:
self.propose_flight(
FlightType.OCA_AIRCRAFT, 2, doctrine.mission_ranges.offensive
)
self.propose_common_escorts(doctrine)
self.propose_flight(FlightType.OCA_AIRCRAFT, 2)
self.propose_common_escorts()

View File

@ -4,7 +4,6 @@ from dataclasses import dataclass
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.data.doctrine import Doctrine
from game.theater import MissionTarget
from gen.flights.flight import FlightType
@ -19,5 +18,5 @@ class PlanRefueling(PackagePlanningTask[MissionTarget]):
def apply_effects(self, state: TheaterState) -> None:
state.refueling_targets.remove(self.target)
def propose_flights(self, doctrine: Doctrine) -> None:
self.propose_flight(FlightType.REFUELING, 1, doctrine.mission_ranges.refueling)
def propose_flights(self) -> None:
self.propose_flight(FlightType.REFUELING, 1)

View File

@ -5,7 +5,6 @@ from typing import Any
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.data.doctrine import Doctrine
from game.theater.theatergroundobject import TheaterGroundObject
from gen.flights.flight import FlightType
@ -22,6 +21,6 @@ class PlanStrike(PackagePlanningTask[TheaterGroundObject[Any]]):
def apply_effects(self, state: TheaterState) -> None:
state.strike_targets.remove(self.target)
def propose_flights(self, doctrine: Doctrine) -> None:
self.propose_flight(FlightType.STRIKE, 2, doctrine.mission_ranges.offensive)
self.propose_common_escorts(doctrine)
def propose_flights(self) -> None:
self.propose_flight(FlightType.STRIKE, 2)
self.propose_common_escorts()

View File

@ -1,4 +1,4 @@
from dataclasses import dataclass, field
from dataclasses import dataclass
from datetime import timedelta
from typing import Any
@ -18,15 +18,6 @@ class GroundUnitProcurementRatios:
return 0.0
@dataclass(frozen=True)
class MissionPlannerMaxRanges:
cap: Distance = field(default=nautical_miles(100))
cas: Distance = field(default=nautical_miles(50))
offensive: Distance = field(default=nautical_miles(150))
aewc: Distance = field(default=Distance.inf())
refueling: Distance = field(default=nautical_miles(200))
@dataclass(frozen=True)
class Doctrine:
cas: bool
@ -88,8 +79,6 @@ class Doctrine:
ground_unit_procurement_ratios: GroundUnitProcurementRatios
mission_ranges: MissionPlannerMaxRanges = field(default=MissionPlannerMaxRanges())
@has_save_compat_for(5)
def __setstate__(self, state: dict[str, Any]) -> None:
if "max_ingress_distance" not in state:
@ -111,6 +100,12 @@ class Doctrine:
self.__dict__.update(state)
class MissionPlannerMaxRanges:
@has_save_compat_for(5)
def __init__(self) -> None:
pass
MODERN_DOCTRINE = Doctrine(
cap=True,
cas=True,

View File

@ -29,7 +29,7 @@ from game.radio.channels import (
ViggenRadioChannelAllocator,
NoOpChannelAllocator,
)
from game.utils import Distance, Speed, feet, kph, knots
from game.utils import Distance, Speed, feet, kph, knots, nautical_miles
if TYPE_CHECKING:
from gen.aircraft import FlightData
@ -112,13 +112,18 @@ class AircraftType(UnitType[Type[FlyingType]]):
lha_capable: bool
always_keeps_gun: bool
# If true, the aircraft does not use the guns as the last resort weapons, but as a main weapon.
# It'll RTB when it doesn't have gun ammo left.
# If true, the aircraft does not use the guns as the last resort weapons, but as a
# main weapon. It'll RTB when it doesn't have gun ammo left.
gunfighter: bool
max_group_size: int
patrol_altitude: Optional[Distance]
patrol_speed: Optional[Speed]
#: The maximum range between the origin airfield and the target for which the auto-
#: planner will consider this aircraft usable for a mission.
max_mission_range: Distance
intra_flight_radio: Optional[Radio]
channel_allocator: Optional[RadioChannelAllocator]
channel_namer: Type[ChannelNamer]
@ -230,6 +235,13 @@ class AircraftType(UnitType[Type[FlyingType]]):
radio_config = RadioConfig.from_data(data.get("radios", {}))
patrol_config = PatrolConfig.from_data(data.get("patrol", {}))
try:
mission_range = nautical_miles(int(data["max_range"]))
except (KeyError, ValueError):
mission_range = (
nautical_miles(50) if aircraft.helicopter else nautical_miles(150)
)
try:
introduction = data["introduced"]
if introduction is None:
@ -257,6 +269,7 @@ class AircraftType(UnitType[Type[FlyingType]]):
max_group_size=data.get("max_group_size", aircraft.group_size_max),
patrol_altitude=patrol_config.altitude,
patrol_speed=patrol_config.speed,
max_mission_range=mission_range,
intra_flight_radio=radio_config.intra_flight,
channel_allocator=radio_config.channel_allocator,
channel_namer=radio_config.channel_namer,

View File

@ -11,7 +11,7 @@ from game.dcs.aircrafttype import AircraftType
from game.dcs.groundunittype import GroundUnitType
from game.factions.faction import Faction
from game.theater import ControlPoint, MissionTarget
from game.utils import Distance
from game.utils import meters
from gen.flights.ai_flight_planner_db import aircraft_for_task
from gen.flights.closestairfields import ObjectiveDistanceCache
from gen.flights.flight import FlightType
@ -25,15 +25,13 @@ FRONTLINE_RESERVES_FACTOR = 1.3
@dataclass(frozen=True)
class AircraftProcurementRequest:
near: MissionTarget
range: Distance
task_capability: FlightType
number: int
def __str__(self) -> str:
task = self.task_capability.value
distance = self.range.nautical_miles
target = self.near.name
return f"{self.number} ship {task} within {distance} nm of {target}"
return f"{self.number} ship {task} near {target}"
class ProcurementAi:
@ -211,24 +209,24 @@ class ProcurementAi:
return GroundUnitClass.Tank
return worst_balanced
def _affordable_aircraft_for_task(
self,
task: FlightType,
airbase: ControlPoint,
number: int,
max_price: float,
def affordable_aircraft_for(
self, request: AircraftProcurementRequest, airbase: ControlPoint, budget: float
) -> Optional[AircraftType]:
best_choice: Optional[AircraftType] = None
for unit in aircraft_for_task(task):
for unit in aircraft_for_task(request.task_capability):
if unit not in self.faction.aircrafts:
continue
if unit.price * number > max_price:
if unit.price * request.number > budget:
continue
if not airbase.can_operate(unit):
continue
distance_to_target = meters(request.near.distance_to(airbase))
if distance_to_target > unit.max_mission_range:
continue
for squadron in self.air_wing.squadrons_for(unit):
if task in squadron.auto_assignable_mission_types:
if request.task_capability in squadron.auto_assignable_mission_types:
break
else:
continue
@ -241,13 +239,6 @@ class ProcurementAi:
break
return best_choice
def affordable_aircraft_for(
self, request: AircraftProcurementRequest, airbase: ControlPoint, budget: float
) -> Optional[AircraftType]:
return self._affordable_aircraft_for_task(
request.task_capability, airbase, request.number, budget
)
def fulfill_aircraft_request(
self, request: AircraftProcurementRequest, budget: float
) -> Tuple[float, bool]:
@ -293,7 +284,7 @@ class ProcurementAi:
) -> Iterator[ControlPoint]:
distance_cache = ObjectiveDistanceCache.get_closest_airfields(request.near)
threatened = []
for cp in distance_cache.operational_airfields_within(request.range):
for cp in distance_cache.operational_airfields:
if not cp.is_friendly(self.is_player):
continue
if cp.unclaimed_parking(self.game) < request.number:

View File

@ -688,7 +688,5 @@ class PendingTransfers:
gap += 1
self.game.procurement_requests_for(self.player).append(
AircraftProcurementRequest(
control_point, nautical_miles(200), FlightType.TRANSPORT, gap
)
AircraftProcurementRequest(control_point, FlightType.TRANSPORT, gap)
)

View File

@ -1,5 +1,6 @@
description: The A-50 is an AWACS plane.
max_group_size: 1
max_range: 2000
price: 50
patrol:
altitude: 33000

View File

@ -27,6 +27,7 @@ manufacturer: McDonnell Douglas
origin: USA/UK
price: 15
role: V/STOL Attack
max_range: 100
variants:
AV-8B Harrier II Night Attack: {}
radios:

View File

@ -1,4 +1,5 @@
description: The An-26B is a military transport aircraft.
price: 15
max_range: 800
variants:
An-26B: null

View File

@ -1,4 +1,5 @@
description: The Rockwell B-1 Lancer is a supersonic variable-sweep wing, heavy bomber
description:
The Rockwell B-1 Lancer is a supersonic variable-sweep wing, heavy bomber
used by the United States Air Force. It is commonly called the 'Bone' (from 'B-One').It
is one of three strategic bombers in the U.S. Air Force fleet as of 2021, the other
two being the B-2 Spirit and the B-52 Stratofortress. It first served in combat
@ -12,5 +13,6 @@ manufacturer: Rockwell
origin: USA
price: 45
role: Supersonic Strategic Bomber
max_range: 2000
variants:
B-1B Lancer: {}

View File

@ -1,4 +1,5 @@
description: The Boeing B-52 Stratofortress is capable of carrying up to 70,000 pounds
description:
The Boeing B-52 Stratofortress is capable of carrying up to 70,000 pounds
(32,000 kg) of weapons, and has a typical combat range of more than 8,800 miles
(14,080 km) without aerial refueling. The B-52 completed sixty years of continuous
service with its original operator in 2015. After being upgraded between 2013 and
@ -8,5 +9,6 @@ manufacturer: Boeing
origin: USA
price: 35
role: Strategic Bomber
max_range: 2000
variants:
B-52H Stratofortress: {}

View File

@ -1,4 +1,5 @@
description: The C-130 is a military transport aircraft.
price: 15
max_range: 1000
variants:
C-130: null

View File

@ -1,4 +1,5 @@
description: The C-17 is a military transport aircraft.
price: 18
max_range: 2000
variants:
C-17A: null

View File

@ -8,6 +8,7 @@ manufacturer: Northrop Grumman
origin: USA
price: 50
role: AEW&C
max_range: 2000
patrol:
altitude: 30000
variants:

View File

@ -1,6 +1,7 @@
description: The E-3A is a AWACS aicraft.
price: 50
max_group_size: 1
max_range: 2000
patrol:
altitude: 35000
variants:

View File

@ -21,6 +21,7 @@ manufacturer: Grumman
origin: USA
price: 22
role: Carrier-based Air-Superiority Fighter/Fighter Bomber
max_range: 250
variants:
F-14A Tomcat (Block 135-GR Late): {}
radios:

View File

@ -21,6 +21,7 @@ manufacturer: Grumman
origin: USA
price: 26
role: Carrier-based Air-Superiority Fighter/Fighter Bomber
max_range: 250
variants:
F-14B Tomcat: {}
radios:

View File

@ -1,4 +1,5 @@
description: The early verison of the F-16. It flew in Desert Storm.
price: 15
max_range: 200
variants:
F-16A: null

View File

@ -27,6 +27,7 @@ manufacturer: General Dynamics
origin: USA
price: 22
role: Multirole Fighter
max_range: 200
variants:
F-16CM Fighting Falcon (Block 50): {}
F-2A: {}

View File

@ -1,4 +1,5 @@
description: The Lockheed Martin C-130J Super Hercules is a four-engine turboprop
description:
The Lockheed Martin C-130J Super Hercules is a four-engine turboprop
military transport aircraft. The C-130J is a comprehensive update of the Lockheed
C-130 Hercules, with new engines, flight deck, and other systems. As of February
2018, 400 C-130J aircraft have been delivered to 17 nations.
@ -7,5 +8,6 @@ manufacturer: Lockheed
origin: USA
price: 18
role: Transport
max_range: 1000
variants:
C-130J-30 Super Hercules: {}

View File

@ -1,3 +1,4 @@
price: 20
max_range: 1000
variants:
IL-76MD: null

View File

@ -1,5 +1,6 @@
price: 20
max_group_size: 1
max_range: 1000
patrol:
# ~280 knots IAS.
speed: 400

View File

@ -8,6 +8,7 @@ manufacturer: Beoing
origin: USA
price: 25
role: Tanker
max_range: 1000
patrol:
# ~300 knots IAS.
speed: 445

View File

@ -1,10 +1,12 @@
description: The Lockheed Martin (previously Lockheed) KC-130 is a family of the extended-range
description:
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.
introduced: 1962
manufacturer: Lockheed Martin
origin: USA
price: 25
role: Tanker
max_range: 1000
patrol:
# ~210 knots IAS, roughly the max for the KC-130 at altitude.
speed: 370

View File

@ -1,4 +1,5 @@
description: The Boeing KC-135 Stratotanker is a military aerial refueling aircraft
description:
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.
@ -7,6 +8,7 @@ manufacturer: Boeing
origin: USA
price: 25
role: Tanker
max_range: 1000
patrol:
# 300 knots IAS.
speed: 440

View File

@ -1,4 +1,5 @@
price: 50
max_range: 2000
patrol:
altitude: 40000
variants:

View File

@ -1,5 +1,6 @@
carrier_capable: true
description: The Lockheed S-3 Viking is a 4-crew, twin-engine turbofan-powered jet
description:
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
@ -16,6 +17,7 @@ origin: USA
price: 20
max_group_size: 1
role: Carrier-based Tanker
max_range: 1000
patrol:
# ~265 knots IAS.
speed: 320

View File

@ -1,3 +1,4 @@
price: 25
max_range: 600
variants:
Yak-40: null