From a594f45aaeae87c53c23d1e54b95da4d1f430974 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 20 Nov 2020 17:29:58 -0800 Subject: [PATCH] Pick divert airfields when planning. https://github.com/Khopa/dcs_liberation/issues/342 --- game/inventory.py | 18 ++++++------- game/theater/controlpoint.py | 8 ++++++ gen/flights/ai_flight_planner.py | 25 ++++++++++++++++--- qt_ui/widgets/combos/QAircraftTypeSelector.py | 4 +-- .../combos/QArrivalAirfieldSelector.py | 15 +++-------- .../widgets/combos/QOriginAirfieldSelector.py | 6 ++--- 6 files changed, 47 insertions(+), 29 deletions(-) diff --git a/game/inventory.py b/game/inventory.py index 67c09618..3c92a80f 100644 --- a/game/inventory.py +++ b/game/inventory.py @@ -4,7 +4,7 @@ from __future__ import annotations from collections import defaultdict from typing import Dict, Iterable, Iterator, Set, Tuple, TYPE_CHECKING -from dcs.unittype import UnitType +from dcs.unittype import FlyingType from gen.flights.flight import Flight @@ -17,9 +17,9 @@ class ControlPointAircraftInventory: def __init__(self, control_point: ControlPoint) -> None: self.control_point = control_point - self.inventory: Dict[UnitType, int] = defaultdict(int) + self.inventory: Dict[FlyingType, int] = defaultdict(int) - def add_aircraft(self, aircraft: UnitType, count: int) -> None: + def add_aircraft(self, aircraft: FlyingType, count: int) -> None: """Adds aircraft to the inventory. Args: @@ -28,7 +28,7 @@ class ControlPointAircraftInventory: """ self.inventory[aircraft] += count - def remove_aircraft(self, aircraft: UnitType, count: int) -> None: + def remove_aircraft(self, aircraft: FlyingType, count: int) -> None: """Removes aircraft from the inventory. Args: @@ -47,7 +47,7 @@ class ControlPointAircraftInventory: ) self.inventory[aircraft] -= count - def available(self, aircraft: UnitType) -> int: + def available(self, aircraft: FlyingType) -> int: """Returns the number of available aircraft of the given type. Args: @@ -59,14 +59,14 @@ class ControlPointAircraftInventory: return 0 @property - def types_available(self) -> Iterator[UnitType]: + def types_available(self) -> Iterator[FlyingType]: """Iterates over all available aircraft types.""" for aircraft, count in self.inventory.items(): if count > 0: yield aircraft @property - def all_aircraft(self) -> Iterator[Tuple[UnitType, int]]: + def all_aircraft(self) -> Iterator[Tuple[FlyingType, int]]: """Iterates over all available aircraft types, including amounts.""" for aircraft, count in self.inventory.items(): if count > 0: @@ -106,9 +106,9 @@ class GlobalAircraftInventory: return self.inventories[control_point] @property - def available_types_for_player(self) -> Iterator[UnitType]: + def available_types_for_player(self) -> Iterator[FlyingType]: """Iterates over all aircraft types available to the player.""" - seen: Set[UnitType] = set() + seen: Set[FlyingType] = set() for control_point, inventory in self.inventories.items(): if control_point.captured: for aircraft in inventory.types_available: diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index 88d622a6..dc2097c1 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -16,6 +16,7 @@ from dcs.ships import ( Type_071_Amphibious_Transport_Dock, ) from dcs.terrain.terrain import Airport +from dcs.unittype import FlyingType from game import db from gen.ground_forces.combat_stance import CombatStance @@ -366,6 +367,13 @@ class ControlPoint(MissionTarget): # TODO: FlightType.STRIKE ] + def can_land(self, aircraft: FlyingType) -> bool: + if self.is_carrier and aircraft not in db.CARRIER_CAPABLE: + return False + if self.is_lha and aircraft not in db.LHA_CAPABLE: + return False + return True + class OffMapSpawn(ControlPoint): def __init__(self, id: int, name: str, position: Point): diff --git a/gen/flights/ai_flight_planner.py b/gen/flights/ai_flight_planner.py index 216972dd..182e0455 100644 --- a/gen/flights/ai_flight_planner.py +++ b/gen/flights/ai_flight_planner.py @@ -16,7 +16,7 @@ from typing import ( Type, ) -from dcs.unittype import FlyingType, UnitType +from dcs.unittype import FlyingType from game import db from game.data.radar_db import UNITS_WITH_RADAR @@ -119,7 +119,7 @@ class AircraftAllocator: def find_aircraft_for_flight( self, flight: ProposedFlight - ) -> Optional[Tuple[ControlPoint, UnitType]]: + ) -> Optional[Tuple[ControlPoint, FlyingType]]: """Finds aircraft suitable for the given mission. Searches for aircraft capable of performing the given mission within the @@ -190,7 +190,7 @@ class AircraftAllocator: def find_aircraft_of_type( self, flight: ProposedFlight, types: List[Type[FlyingType]], - ) -> Optional[Tuple[ControlPoint, UnitType]]: + ) -> Optional[Tuple[ControlPoint, FlyingType]]: airfields_in_range = self.closest_airfields.airfields_within( flight.max_distance ) @@ -214,6 +214,8 @@ class PackageBuilder: global_inventory: GlobalAircraftInventory, is_player: bool, start_type: str) -> None: + self.closest_airfields = closest_airfields + self.is_player = is_player self.package = Package(location) self.allocator = AircraftAllocator(closest_airfields, global_inventory, is_player) @@ -239,10 +241,25 @@ class PackageBuilder: flight = Flight(self.package, aircraft, plan.num_aircraft, plan.task, start_type, departure=airfield, arrival=airfield, - divert=None) + divert=self.find_divert_field(aircraft, airfield)) self.package.add_flight(flight) return True + def find_divert_field(self, aircraft: FlyingType, + arrival: ControlPoint) -> Optional[ControlPoint]: + divert_limit = nm_to_meter(150) + for airfield in self.closest_airfields.airfields_within(divert_limit): + if airfield.captured != self.is_player: + continue + if airfield == arrival: + continue + if not airfield.can_land(aircraft): + continue + if isinstance(airfield, OffMapSpawn): + continue + return airfield + return None + def build(self) -> Package: """Returns the built package.""" return self.package diff --git a/qt_ui/widgets/combos/QAircraftTypeSelector.py b/qt_ui/widgets/combos/QAircraftTypeSelector.py index 1f490e4d..2be6e48c 100644 --- a/qt_ui/widgets/combos/QAircraftTypeSelector.py +++ b/qt_ui/widgets/combos/QAircraftTypeSelector.py @@ -3,13 +3,13 @@ from typing import Iterable from PySide2.QtWidgets import QComboBox -from dcs.planes import PlaneType +from dcs.unittype import FlyingType class QAircraftTypeSelector(QComboBox): """Combo box for selecting among the given aircraft types.""" - def __init__(self, aircraft_types: Iterable[PlaneType]) -> None: + def __init__(self, aircraft_types: Iterable[FlyingType]) -> None: super().__init__() for aircraft in aircraft_types: self.addItem(f"{aircraft.id}", userData=aircraft) diff --git a/qt_ui/widgets/combos/QArrivalAirfieldSelector.py b/qt_ui/widgets/combos/QArrivalAirfieldSelector.py index a1fe88bb..c5d89b90 100644 --- a/qt_ui/widgets/combos/QArrivalAirfieldSelector.py +++ b/qt_ui/widgets/combos/QArrivalAirfieldSelector.py @@ -2,7 +2,7 @@ from typing import Iterable from PySide2.QtWidgets import QComboBox -from dcs.planes import PlaneType +from dcs.unittype import FlyingType from game import db from game.theater.controlpoint import ControlPoint @@ -16,7 +16,7 @@ class QArrivalAirfieldSelector(QComboBox): """ def __init__(self, destinations: Iterable[ControlPoint], - aircraft: PlaneType, optional_text: str) -> None: + aircraft: FlyingType, optional_text: str) -> None: super().__init__() self.destinations = list(destinations) self.aircraft = aircraft @@ -24,23 +24,16 @@ class QArrivalAirfieldSelector(QComboBox): self.rebuild_selector() self.setCurrentIndex(0) - def change_aircraft(self, aircraft: PlaneType) -> None: + def change_aircraft(self, aircraft: FlyingType) -> None: if self.aircraft == aircraft: return self.aircraft = aircraft self.rebuild_selector() - def valid_destination(self, destination: ControlPoint) -> bool: - if destination.is_carrier and self.aircraft not in db.CARRIER_CAPABLE: - return False - if destination.is_lha and self.aircraft not in db.LHA_CAPABLE: - return False - return True - def rebuild_selector(self) -> None: self.clear() for destination in self.destinations: - if self.valid_destination(destination): + if destination.can_land(self.aircraft): self.addItem(destination.name, destination) self.model().sort(0) self.insertItem(0, self.optional_text, None) diff --git a/qt_ui/widgets/combos/QOriginAirfieldSelector.py b/qt_ui/widgets/combos/QOriginAirfieldSelector.py index 14bdbb47..ce1c6301 100644 --- a/qt_ui/widgets/combos/QOriginAirfieldSelector.py +++ b/qt_ui/widgets/combos/QOriginAirfieldSelector.py @@ -3,7 +3,7 @@ from typing import Iterable from PySide2.QtCore import Signal from PySide2.QtWidgets import QComboBox -from dcs.planes import PlaneType +from dcs.unittype import FlyingType from game.inventory import GlobalAircraftInventory from game.theater.controlpoint import ControlPoint @@ -20,7 +20,7 @@ class QOriginAirfieldSelector(QComboBox): def __init__(self, global_inventory: GlobalAircraftInventory, origins: Iterable[ControlPoint], - aircraft: PlaneType) -> None: + aircraft: FlyingType) -> None: super().__init__() self.global_inventory = global_inventory self.origins = list(origins) @@ -28,7 +28,7 @@ class QOriginAirfieldSelector(QComboBox): self.rebuild_selector() self.currentIndexChanged.connect(self.index_changed) - def change_aircraft(self, aircraft: PlaneType) -> None: + def change_aircraft(self, aircraft: FlyingType) -> None: if self.aircraft == aircraft: return self.aircraft = aircraft