class PackageBuilder: """Builds a Package for the flights it receives.""" def __init__(self, location: MissionTarget, closest_airfields: ClosestAirfields, global_inventory: GlobalAircraftInventory, is_player: bool, start_type: str) -> None: self.package = Package(location) self.allocator = AircraftAllocator(closest_airfields, global_inventory, is_player) self.global_inventory = global_inventory self.start_type = start_type def plan_flight(self, plan: ProposedFlight) -> bool: """Allocates aircraft for the given flight and adds them to the package. If no suitable aircraft are available, False is returned. If the failed flight was critical and the rest of the mission will be scrubbed, the caller should return any previously planned flights to the inventory using release_planned_aircraft. """ assignment = self.allocator.find_aircraft_for_flight(plan) if assignment is None: return False airfield, aircraft = assignment flight = Flight(self.package, aircraft, plan.num_aircraft, airfield, plan.task, self.start_type) self.package.add_flight(flight) return True def build(self) -> Package: """Returns the built package.""" return self.package def release_planned_aircraft(self) -> None: """Returns any planned flights to the inventory.""" flights = list(self.package.flights) for flight in flights: self.global_inventory.return_from_flight(flight) self.package.remove_flight(flight)
class AirliftPlanner: #: Maximum range from for any link in the route of takeoff, pickup, dropoff, and RTB #: for a helicopter to be considered for airlift. Total route length is not #: considered because the helicopter can refuel at each stop. Cargo planes have no #: maximum range. HELO_MAX_RANGE = nautical_miles(100) def __init__( self, game: Game, transfer: TransferOrder, next_stop: ControlPoint ) -> None: self.game = game self.transfer = transfer self.next_stop = next_stop self.for_player = transfer.destination.captured self.package = Package(target=next_stop, auto_asap=True) def compatible_with_mission( self, unit_type: AircraftType, airfield: ControlPoint ) -> bool: if unit_type not in aircraft_for_task(FlightType.TRANSPORT): return False if not self.transfer.origin.can_operate(unit_type): return False if not self.next_stop.can_operate(unit_type): return False # Cargo planes have no maximum range. if not unit_type.dcs_unit_type.helicopter: return True # A helicopter that is transport capable and able to operate at both bases. Need # to check that no leg of the journey exceeds the maximum range. This doesn't # account for any routing around threats that might take place, but it's close # enough. home = airfield.position pickup = self.transfer.position.position drop_off = self.transfer.position.position if meters(home.distance_to_point(pickup)) > self.HELO_MAX_RANGE: return False if meters(pickup.distance_to_point(drop_off)) > self.HELO_MAX_RANGE: return False if meters(drop_off.distance_to_point(home)) > self.HELO_MAX_RANGE: return False return True def create_package_for_airlift(self) -> None: distance_cache = ObjectiveDistanceCache.get_closest_airfields( self.transfer.position ) air_wing = self.game.air_wing_for(self.for_player) for cp in distance_cache.closest_airfields: if cp.captured != self.for_player: continue inventory = self.game.aircraft_inventory.for_control_point(cp) for unit_type, available in inventory.all_aircraft: squadrons = air_wing.auto_assignable_for_task_with_type( unit_type, FlightType.TRANSPORT ) for squadron in squadrons: if self.compatible_with_mission(unit_type, cp): while ( available and squadron.has_available_pilots and self.transfer.transport is None ): flight_size = self.create_airlift_flight( squadron, inventory ) available -= flight_size if self.package.flights: self.game.ato_for(self.for_player).add_package(self.package) def create_airlift_flight( self, squadron: Squadron, inventory: ControlPointAircraftInventory ) -> int: available_aircraft = inventory.available(squadron.aircraft) capacity_each = 1 if squadron.aircraft.dcs_unit_type.helicopter else 2 required = math.ceil(self.transfer.size / capacity_each) flight_size = min( required, available_aircraft, squadron.aircraft.dcs_unit_type.group_size_max, ) # TODO: Use number_of_available_pilots directly once feature flag is gone. # The number of currently available pilots is not relevant when pilot limits # are disabled. if not squadron.can_provide_pilots(flight_size): flight_size = squadron.number_of_available_pilots capacity = flight_size * capacity_each if capacity < self.transfer.size: transfer = self.game.transfers.split_transfer(self.transfer, capacity) else: transfer = self.transfer player = inventory.control_point.captured flight = Flight( self.package, self.game.country_for(player), squadron, flight_size, FlightType.TRANSPORT, self.game.settings.default_start_type, departure=inventory.control_point, arrival=inventory.control_point, divert=None, cargo=transfer, ) transport = Airlift(transfer, flight, self.next_stop) transfer.transport = transport self.package.add_flight(flight) planner = FlightPlanBuilder(self.game, self.package, self.for_player) planner.populate_flight_plan(flight) self.game.aircraft_inventory.claim_for_flight(flight) return flight_size
class PackageBuilder: """Builds a Package for the flights it receives.""" def __init__( self, location: MissionTarget, closest_airfields: ClosestAirfields, global_inventory: GlobalAircraftInventory, is_player: bool, package_country: str, start_type: str, ) -> None: self.closest_airfields = closest_airfields self.is_player = is_player self.package_country = package_country self.package = Package(location) self.allocator = AircraftAllocator(closest_airfields, global_inventory, is_player) self.global_inventory = global_inventory self.start_type = start_type def plan_flight(self, plan: ProposedFlight) -> bool: """Allocates aircraft for the given flight and adds them to the package. If no suitable aircraft are available, False is returned. If the failed flight was critical and the rest of the mission will be scrubbed, the caller should return any previously planned flights to the inventory using release_planned_aircraft. """ assignment = self.allocator.find_aircraft_for_flight(plan) if assignment is None: return False airfield, aircraft = assignment if isinstance(airfield, OffMapSpawn): start_type = "In Flight" else: start_type = self.start_type flight = Flight( self.package, self.package_country, aircraft, plan.num_aircraft, plan.task, start_type, departure=airfield, arrival=airfield, divert=self.find_divert_field(aircraft, airfield), ) self.package.add_flight(flight) return True def find_divert_field(self, aircraft: Type[FlyingType], arrival: ControlPoint) -> Optional[ControlPoint]: divert_limit = nautical_miles(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_operate(aircraft): continue if isinstance(airfield, OffMapSpawn): continue return airfield return None def build(self) -> Package: """Returns the built package.""" return self.package def release_planned_aircraft(self) -> None: """Returns any planned flights to the inventory.""" flights = list(self.package.flights) for flight in flights: self.global_inventory.return_from_flight(flight) self.package.remove_flight(flight)