diff --git a/changelog.md b/changelog.md index 4b131551..e0e61174 100644 --- a/changelog.md +++ b/changelog.md @@ -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 diff --git a/game/commander/aircraftallocator.py b/game/commander/aircraftallocator.py index 16ea678a..523fad64 100644 --- a/game/commander/aircraftallocator.py +++ b/game/commander/aircraftallocator.py @@ -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( diff --git a/game/commander/missionproposals.py b/game/commander/missionproposals.py index 2b8fc074..a13802b8 100644 --- a/game/commander/missionproposals.py +++ b/game/commander/missionproposals.py @@ -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 diff --git a/game/commander/packagebuilder.py b/game/commander/packagebuilder.py index 490e0286..da96a8e2 100644 --- a/game/commander/packagebuilder.py +++ b/game/commander/packagebuilder.py @@ -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 diff --git a/game/commander/packagefulfiller.py b/game/commander/packagefulfiller.py index d4d8352b..1005bfa9 100644 --- a/game/commander/packagefulfiller.py +++ b/game/commander/packagefulfiller.py @@ -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, ) diff --git a/game/commander/tasks/packageplanningtask.py b/game/commander/tasks/packageplanningtask.py index fb50af23..8e2eb8a2 100644 --- a/game/commander/tasks/packageplanningtask.py +++ b/game/commander/tasks/packageplanningtask.py @@ -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 diff --git a/game/commander/tasks/primitive/aewc.py b/game/commander/tasks/primitive/aewc.py index 8153aac6..f9c6a7d2 100644 --- a/game/commander/tasks/primitive/aewc.py +++ b/game/commander/tasks/primitive/aewc.py @@ -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: diff --git a/game/commander/tasks/primitive/antiship.py b/game/commander/tasks/primitive/antiship.py index 3f85c74c..a135e1cd 100644 --- a/game/commander/tasks/primitive/antiship.py +++ b/game/commander/tasks/primitive/antiship.py @@ -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) diff --git a/game/commander/tasks/primitive/antishipping.py b/game/commander/tasks/primitive/antishipping.py index 303a9af1..64279d1b 100644 --- a/game/commander/tasks/primitive/antishipping.py +++ b/game/commander/tasks/primitive/antishipping.py @@ -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() diff --git a/game/commander/tasks/primitive/bai.py b/game/commander/tasks/primitive/bai.py index f9d61818..4878171d 100644 --- a/game/commander/tasks/primitive/bai.py +++ b/game/commander/tasks/primitive/bai.py @@ -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() diff --git a/game/commander/tasks/primitive/barcap.py b/game/commander/tasks/primitive/barcap.py index 77302adf..c2dafae7 100644 --- a/game/commander/tasks/primitive/barcap.py +++ b/game/commander/tasks/primitive/barcap.py @@ -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) diff --git a/game/commander/tasks/primitive/cas.py b/game/commander/tasks/primitive/cas.py index 7a9997ff..c2785405 100644 --- a/game/commander/tasks/primitive/cas.py +++ b/game/commander/tasks/primitive/cas.py @@ -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) diff --git a/game/commander/tasks/primitive/convoyinterdiction.py b/game/commander/tasks/primitive/convoyinterdiction.py index 11ed4ee4..285326c7 100644 --- a/game/commander/tasks/primitive/convoyinterdiction.py +++ b/game/commander/tasks/primitive/convoyinterdiction.py @@ -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() diff --git a/game/commander/tasks/primitive/dead.py b/game/commander/tasks/primitive/dead.py index 3861908c..45da3cc3 100644 --- a/game/commander/tasks/primitive/dead.py +++ b/game/commander/tasks/primitive/dead.py @@ -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) diff --git a/game/commander/tasks/primitive/oca.py b/game/commander/tasks/primitive/oca.py index 4c995f75..be88df32 100644 --- a/game/commander/tasks/primitive/oca.py +++ b/game/commander/tasks/primitive/oca.py @@ -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() diff --git a/game/commander/tasks/primitive/refueling.py b/game/commander/tasks/primitive/refueling.py index 005cbc3a..5f17f3df 100644 --- a/game/commander/tasks/primitive/refueling.py +++ b/game/commander/tasks/primitive/refueling.py @@ -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) diff --git a/game/commander/tasks/primitive/strike.py b/game/commander/tasks/primitive/strike.py index ce322dad..e89c9cac 100644 --- a/game/commander/tasks/primitive/strike.py +++ b/game/commander/tasks/primitive/strike.py @@ -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() diff --git a/game/data/doctrine.py b/game/data/doctrine.py index 21402501..359a1435 100644 --- a/game/data/doctrine.py +++ b/game/data/doctrine.py @@ -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, diff --git a/game/dcs/aircrafttype.py b/game/dcs/aircrafttype.py index dd9b5282..56fa3f0f 100644 --- a/game/dcs/aircrafttype.py +++ b/game/dcs/aircrafttype.py @@ -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, diff --git a/game/procurement.py b/game/procurement.py index 8820453c..3b1ea370 100644 --- a/game/procurement.py +++ b/game/procurement.py @@ -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: diff --git a/game/transfers.py b/game/transfers.py index 7401b03d..68e8dea1 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -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) ) diff --git a/resources/units/aircraft/A-50.yaml b/resources/units/aircraft/A-50.yaml index 574f8cd9..bef84e03 100644 --- a/resources/units/aircraft/A-50.yaml +++ b/resources/units/aircraft/A-50.yaml @@ -1,5 +1,6 @@ description: The A-50 is an AWACS plane. max_group_size: 1 +max_range: 2000 price: 50 patrol: altitude: 33000 diff --git a/resources/units/aircraft/AV8BNA.yaml b/resources/units/aircraft/AV8BNA.yaml index 7e4fec82..3accfba7 100644 --- a/resources/units/aircraft/AV8BNA.yaml +++ b/resources/units/aircraft/AV8BNA.yaml @@ -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: diff --git a/resources/units/aircraft/An-26B.yaml b/resources/units/aircraft/An-26B.yaml index 4ed84aa7..01f90c19 100644 --- a/resources/units/aircraft/An-26B.yaml +++ b/resources/units/aircraft/An-26B.yaml @@ -1,4 +1,5 @@ description: The An-26B is a military transport aircraft. price: 15 +max_range: 800 variants: An-26B: null diff --git a/resources/units/aircraft/B-1B.yaml b/resources/units/aircraft/B-1B.yaml index 0d34fb01..3c309c7b 100644 --- a/resources/units/aircraft/B-1B.yaml +++ b/resources/units/aircraft/B-1B.yaml @@ -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: {} diff --git a/resources/units/aircraft/B-52H.yaml b/resources/units/aircraft/B-52H.yaml index 65aa01c0..7221fd5e 100644 --- a/resources/units/aircraft/B-52H.yaml +++ b/resources/units/aircraft/B-52H.yaml @@ -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: {} diff --git a/resources/units/aircraft/C-130.yaml b/resources/units/aircraft/C-130.yaml index 4efe0d0a..eca68ffa 100644 --- a/resources/units/aircraft/C-130.yaml +++ b/resources/units/aircraft/C-130.yaml @@ -1,4 +1,5 @@ description: The C-130 is a military transport aircraft. price: 15 +max_range: 1000 variants: C-130: null diff --git a/resources/units/aircraft/C-17A.yaml b/resources/units/aircraft/C-17A.yaml index a121e07b..692e24a9 100644 --- a/resources/units/aircraft/C-17A.yaml +++ b/resources/units/aircraft/C-17A.yaml @@ -1,4 +1,5 @@ description: The C-17 is a military transport aircraft. price: 18 +max_range: 2000 variants: C-17A: null diff --git a/resources/units/aircraft/E-2C.yaml b/resources/units/aircraft/E-2C.yaml index ca25d97e..7154813a 100644 --- a/resources/units/aircraft/E-2C.yaml +++ b/resources/units/aircraft/E-2C.yaml @@ -8,6 +8,7 @@ manufacturer: Northrop Grumman origin: USA price: 50 role: AEW&C +max_range: 2000 patrol: altitude: 30000 variants: diff --git a/resources/units/aircraft/E-3A.yaml b/resources/units/aircraft/E-3A.yaml index ca781a23..a8e676e4 100644 --- a/resources/units/aircraft/E-3A.yaml +++ b/resources/units/aircraft/E-3A.yaml @@ -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: diff --git a/resources/units/aircraft/F-14A-135-GR.yaml b/resources/units/aircraft/F-14A-135-GR.yaml index eb593105..b467d7e9 100644 --- a/resources/units/aircraft/F-14A-135-GR.yaml +++ b/resources/units/aircraft/F-14A-135-GR.yaml @@ -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: diff --git a/resources/units/aircraft/F-14B.yaml b/resources/units/aircraft/F-14B.yaml index a6244a4a..2cc64fa4 100644 --- a/resources/units/aircraft/F-14B.yaml +++ b/resources/units/aircraft/F-14B.yaml @@ -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: diff --git a/resources/units/aircraft/F-16A.yaml b/resources/units/aircraft/F-16A.yaml index 99c2c2e5..fdfcdb5c 100644 --- a/resources/units/aircraft/F-16A.yaml +++ b/resources/units/aircraft/F-16A.yaml @@ -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 diff --git a/resources/units/aircraft/F-16C_50.yaml b/resources/units/aircraft/F-16C_50.yaml index 9e5b3740..adefe831 100644 --- a/resources/units/aircraft/F-16C_50.yaml +++ b/resources/units/aircraft/F-16C_50.yaml @@ -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: {} diff --git a/resources/units/aircraft/Hercules.yaml b/resources/units/aircraft/Hercules.yaml index 070409fa..af82aaa6 100644 --- a/resources/units/aircraft/Hercules.yaml +++ b/resources/units/aircraft/Hercules.yaml @@ -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: {} diff --git a/resources/units/aircraft/IL-76MD.yaml b/resources/units/aircraft/IL-76MD.yaml index 97020aca..74ca1ab1 100644 --- a/resources/units/aircraft/IL-76MD.yaml +++ b/resources/units/aircraft/IL-76MD.yaml @@ -1,3 +1,4 @@ price: 20 +max_range: 1000 variants: IL-76MD: null diff --git a/resources/units/aircraft/IL-78M.yaml b/resources/units/aircraft/IL-78M.yaml index 2acd1ea3..de5b76f2 100644 --- a/resources/units/aircraft/IL-78M.yaml +++ b/resources/units/aircraft/IL-78M.yaml @@ -1,5 +1,6 @@ price: 20 max_group_size: 1 +max_range: 1000 patrol: # ~280 knots IAS. speed: 400 diff --git a/resources/units/aircraft/KC-135.yaml b/resources/units/aircraft/KC-135.yaml index 138ae873..2cb5e40d 100644 --- a/resources/units/aircraft/KC-135.yaml +++ b/resources/units/aircraft/KC-135.yaml @@ -8,6 +8,7 @@ manufacturer: Beoing origin: USA price: 25 role: Tanker +max_range: 1000 patrol: # ~300 knots IAS. speed: 445 diff --git a/resources/units/aircraft/KC130.yaml b/resources/units/aircraft/KC130.yaml index 802a16bb..5b9ffdca 100644 --- a/resources/units/aircraft/KC130.yaml +++ b/resources/units/aircraft/KC130.yaml @@ -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 diff --git a/resources/units/aircraft/KC135MPRS.yaml b/resources/units/aircraft/KC135MPRS.yaml index 4ba28ff5..c59d3098 100644 --- a/resources/units/aircraft/KC135MPRS.yaml +++ b/resources/units/aircraft/KC135MPRS.yaml @@ -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 diff --git a/resources/units/aircraft/KJ-2000.yaml b/resources/units/aircraft/KJ-2000.yaml index cbb843c8..8078c359 100644 --- a/resources/units/aircraft/KJ-2000.yaml +++ b/resources/units/aircraft/KJ-2000.yaml @@ -1,4 +1,5 @@ price: 50 +max_range: 2000 patrol: altitude: 40000 variants: diff --git a/resources/units/aircraft/S-3B Tanker.yaml b/resources/units/aircraft/S-3B Tanker.yaml index dabe7056..6a4680e3 100644 --- a/resources/units/aircraft/S-3B Tanker.yaml +++ b/resources/units/aircraft/S-3B Tanker.yaml @@ -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 diff --git a/resources/units/aircraft/Yak-40.yaml b/resources/units/aircraft/Yak-40.yaml index d56a2b65..d69242fc 100644 --- a/resources/units/aircraft/Yak-40.yaml +++ b/resources/units/aircraft/Yak-40.yaml @@ -1,3 +1,4 @@ price: 25 +max_range: 600 variants: Yak-40: null