def draw_test_flight_plan(self, scene: QGraphicsScene, task: FlightType, point_near_target: Point, player: bool) -> None: for line in self.shortest_path_segments: try: scene.removeItem(line) except RuntimeError: pass self.clear_flight_paths(scene) target = self.game.theater.closest_target(point_near_target) if player: origin = self.game.theater.player_points()[0] else: origin = self.game.theater.enemy_points()[0] package = Package(target) flight = Flight(package, F_16C_50, 2, task, start_type="Warm", departure=origin, arrival=origin, divert=None) package.add_flight(flight) planner = FlightPlanBuilder(self.game, package, is_player=player) try: planner.populate_flight_plan(flight) except InvalidObjectiveLocation: return package.time_over_target = TotEstimator(package).earliest_tot() self.draw_flight_plan(scene, flight, selected=True)
def plan_mission(self, mission: ProposedMission) -> None: """Allocates aircraft for a proposed mission and adds it to the ATO.""" if self.game.settings.perf_ai_parking_start: start_type = "Cold" else: start_type = "Warm" builder = PackageBuilder( mission.location, self.objective_finder.closest_airfields_to(mission.location), self.game.aircraft_inventory, self.is_player, start_type) missing_types: Set[FlightType] = set() for proposed_flight in mission.flights: if not builder.plan_flight(proposed_flight): missing_types.add(proposed_flight.task) if missing_types: missing_types_str = ", ".join( sorted([t.name for t in missing_types])) builder.release_planned_aircraft() self.message( "Insufficient aircraft", f"Not enough aircraft in range for {mission.location.name} " f"capable of: {missing_types_str}") return package = builder.build() flight_plan_builder = FlightPlanBuilder(self.game, package, self.is_player) for flight in package.flights: flight_plan_builder.populate_flight_plan(flight) self.ato.add_package(package)
def add_flight(self, flight: Flight) -> None: """Adds the new flight to the package.""" self.game.aircraft_inventory.claim_for_flight(flight) self.package_model.add_flight(flight) planner = FlightPlanBuilder(self.game, self.package_model.package, is_player=True) planner.populate_flight_plan(flight) # noinspection PyUnresolvedReferences self.package_changed.emit()
def __init__(self, game: Game, package: Package, flight: Flight): super(QFlightWaypointTab, self).__init__() self.game = game self.package = package self.flight = flight self.planner = FlightPlanBuilder(self.game, package, is_player=True) self.flight_waypoint_list: Optional[QFlightWaypointList] = None self.rtb_waypoint: Optional[QPushButton] = None self.delete_selected: Optional[QPushButton] = None self.open_fast_waypoint_button: Optional[QPushButton] = None self.recreate_buttons: List[QPushButton] = [] self.init_ui()
def add_flight(self, flight: Flight) -> None: """Adds the new flight to the package.""" self.game.aircraft_inventory.claim_for_flight(flight) self.package_model.add_flight(flight) planner = FlightPlanBuilder(self.game, self.package_model.package, is_player=True) try: planner.populate_flight_plan(flight) except PlanningError as ex: self.game.aircraft_inventory.return_from_flight(flight) self.package_model.delete_flight(flight) logging.exception("Could not create flight") QMessageBox.critical( self, "Could not create flight", str(ex), QMessageBox.Ok ) # noinspection PyUnresolvedReferences self.package_changed.emit()
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
def plan_mission(self, mission: ProposedMission, reserves: bool = False) -> None: """Allocates aircraft for a proposed mission and adds it to the ATO.""" if self.is_player: package_country = self.game.player_country else: package_country = self.game.enemy_country builder = PackageBuilder( mission.location, self.objective_finder.closest_airfields_to(mission.location), self.game.aircraft_inventory, self.is_player, package_country, self.game.settings.default_start_type, ) # Attempt to plan all the main elements of the mission first. Escorts # will be planned separately so we can prune escorts for packages that # are not expected to encounter that type of threat. missing_types: Set[FlightType] = set() escorts = [] for proposed_flight in mission.flights: if proposed_flight.escort_type is not None: # Escorts are planned after the primary elements of the package. # If the package does not need escorts they may be pruned. escorts.append(proposed_flight) continue self.plan_flight(mission, proposed_flight, builder, missing_types, reserves) if missing_types: self.scrub_mission_missing_aircraft(mission, builder, missing_types, escorts, reserves) return # Create flight plans for the main flights of the package so we can # determine threats. This is done *after* creating all of the flights # rather than as each flight is added because the flight plan for # flights that will rendezvous with their package will be affected by # the other flights in the package. Escorts will not be able to # contribute to this. flight_plan_builder = FlightPlanBuilder(self.game, builder.package, self.is_player) for flight in builder.package.flights: flight_plan_builder.populate_flight_plan(flight) needed_escorts = self.check_needed_escorts(builder) for escort in escorts: # This list was generated from the not None set, so this should be # impossible. assert escort.escort_type is not None if needed_escorts[escort.escort_type]: self.plan_flight(mission, escort, builder, missing_types, reserves) # Check again for unavailable aircraft. If the escort was required and # none were found, scrub the mission. if missing_types: self.scrub_mission_missing_aircraft(mission, builder, missing_types, escorts, reserves) return if reserves: # Mission is planned reserves which will not be used this turn. # Return reserves to the inventory. builder.release_planned_aircraft() return package = builder.build() # Add flight plans for escorts. for flight in package.flights: if not flight.flight_plan.waypoints: flight_plan_builder.populate_flight_plan(flight) self.ato.add_package(package)
def plan_mission( self, mission: ProposedMission, tracer: MultiEventTracer, reserves: bool = False ) -> None: """Allocates aircraft for a proposed mission and adds it to the ATO.""" builder = PackageBuilder( mission.location, self.objective_finder.closest_airfields_to(mission.location), self.game.aircraft_inventory, self.game.air_wing_for(self.is_player), self.is_player, self.game.country_for(self.is_player), self.game.settings.default_start_type, mission.asap, ) # Attempt to plan all the main elements of the mission first. Escorts # will be planned separately so we can prune escorts for packages that # are not expected to encounter that type of threat. missing_types: Set[FlightType] = set() escorts = [] for proposed_flight in mission.flights: if not self.air_wing_can_plan(proposed_flight.task): # This air wing can never plan this mission type because they do not # have compatible aircraft or squadrons. Skip fulfillment so that we # don't place the purchase request. continue if proposed_flight.escort_type is not None: # Escorts are planned after the primary elements of the package. # If the package does not need escorts they may be pruned. escorts.append(proposed_flight) continue with tracer.trace("Flight planning"): self.plan_flight( mission, proposed_flight, builder, missing_types, reserves ) if missing_types: self.scrub_mission_missing_aircraft( mission, builder, missing_types, escorts, reserves ) return if not builder.package.flights: # The non-escort part of this mission is unplannable by this faction. Scrub # the mission and do not attempt planning escorts because there's no reason # to buy them because this mission will never be planned. return # Create flight plans for the main flights of the package so we can # determine threats. This is done *after* creating all of the flights # rather than as each flight is added because the flight plan for # flights that will rendezvous with their package will be affected by # the other flights in the package. Escorts will not be able to # contribute to this. flight_plan_builder = FlightPlanBuilder( self.game, builder.package, self.is_player ) for flight in builder.package.flights: with tracer.trace("Flight plan population"): flight_plan_builder.populate_flight_plan(flight) needed_escorts = self.check_needed_escorts(builder) for escort in escorts: # This list was generated from the not None set, so this should be # impossible. assert escort.escort_type is not None if needed_escorts[escort.escort_type]: with tracer.trace("Flight planning"): self.plan_flight(mission, escort, builder, missing_types, reserves) # Check again for unavailable aircraft. If the escort was required and # none were found, scrub the mission. if missing_types: self.scrub_mission_missing_aircraft( mission, builder, missing_types, escorts, reserves ) return if reserves: # Mission is planned reserves which will not be used this turn. # Return reserves to the inventory. builder.release_planned_aircraft() return package = builder.build() # Add flight plans for escorts. for flight in package.flights: if not flight.flight_plan.waypoints: with tracer.trace("Flight plan population"): flight_plan_builder.populate_flight_plan(flight) if package.has_players and self.game.settings.auto_ato_player_missions_asap: package.auto_asap = True package.set_tot_asap() self.ato.add_package(package)
class QFlightWaypointTab(QFrame): def __init__(self, game: Game, package: Package, flight: Flight): super(QFlightWaypointTab, self).__init__() self.game = game self.package = package self.flight = flight self.planner = FlightPlanBuilder(self.game, package, is_player=True) self.flight_waypoint_list: Optional[QFlightWaypointList] = None self.rtb_waypoint: Optional[QPushButton] = None self.delete_selected: Optional[QPushButton] = None self.open_fast_waypoint_button: Optional[QPushButton] = None self.recreate_buttons: List[QPushButton] = [] self.init_ui() def init_ui(self): layout = QGridLayout() self.flight_waypoint_list = QFlightWaypointList( self.package, self.flight) layout.addWidget(self.flight_waypoint_list, 0, 0) rlayout = QVBoxLayout() layout.addLayout(rlayout, 0, 1) rlayout.addWidget(QLabel("<strong>Generator :</strong>")) rlayout.addWidget(QLabel("<small>AI compatible</small>")) self.recreate_buttons.clear() for task in self.package.target.mission_types(for_player=True): def make_closure(arg): def closure(): return self.confirm_recreate(arg) return closure button = QPushButton(f"Recreate as {task}") button.clicked.connect(make_closure(task)) rlayout.addWidget(button) self.recreate_buttons.append(button) rlayout.addWidget(QLabel("<strong>Advanced : </strong>")) rlayout.addWidget(QLabel("<small>Do not use for AI flights</small>")) self.rtb_waypoint = QPushButton("Add RTB Waypoint") self.rtb_waypoint.clicked.connect(self.on_rtb_waypoint) rlayout.addWidget(self.rtb_waypoint) self.delete_selected = QPushButton("Delete Selected") self.delete_selected.clicked.connect(self.on_delete_waypoint) rlayout.addWidget(self.delete_selected) self.open_fast_waypoint_button = QPushButton("Add Waypoint") self.open_fast_waypoint_button.clicked.connect(self.on_fast_waypoint) rlayout.addWidget(self.open_fast_waypoint_button) rlayout.addStretch() self.setLayout(layout) def on_delete_waypoint(self): wpt = self.flight_waypoint_list.selectionModel().currentIndex().row() if wpt > 0: self.delete_waypoint(self.flight.flight_plan.waypoints[wpt]) self.flight_waypoint_list.update_list() self.on_change() def delete_waypoint(self, waypoint: FlightWaypoint) -> None: # Need to degrade to a custom flight plan and remove the waypoint. # If the waypoint is a target waypoint and is not the last target # waypoint, we don't need to degrade. if isinstance(self.flight.flight_plan, StrikeFlightPlan): is_target = waypoint in self.flight.flight_plan.targets if is_target and len(self.flight.flight_plan.targets) > 1: self.flight.flight_plan.targets.remove(waypoint) return self.degrade_to_custom_flight_plan() assert isinstance(self.flight.flight_plan, CustomFlightPlan) self.flight.flight_plan.custom_waypoints.remove(waypoint) def on_fast_waypoint(self): self.subwindow = QPredefinedWaypointSelectionWindow( self.game, self.flight, self.flight_waypoint_list) self.subwindow.waypoints_added.connect(self.on_waypoints_added) self.subwindow.show() def on_waypoints_added(self, waypoints: Iterable[FlightWaypoint]) -> None: if not waypoints: return self.degrade_to_custom_flight_plan() assert isinstance(self.flight.flight_plan, CustomFlightPlan) self.flight.flight_plan.custom_waypoints.extend(waypoints) self.flight_waypoint_list.update_list() self.on_change() def on_rtb_waypoint(self): rtb = self.planner.generate_rtb_waypoint(self.flight, self.flight.from_cp) self.degrade_to_custom_flight_plan() assert isinstance(self.flight.flight_plan, CustomFlightPlan) self.flight.flight_plan.custom_waypoints.append(rtb) self.flight_waypoint_list.update_list() self.on_change() def degrade_to_custom_flight_plan(self) -> None: if not isinstance(self.flight.flight_plan, CustomFlightPlan): self.flight.flight_plan = CustomFlightPlan( package=self.flight.package, flight=self.flight, custom_waypoints=self.flight.flight_plan.waypoints) def confirm_recreate(self, task: FlightType) -> None: result = QMessageBox.question(self, "Regenerate flight?", ( "Changing the flight type will reset its flight plan. Do you want " "to continue?"), QMessageBox.No, QMessageBox.Yes) original_task = self.flight.flight_type if result == QMessageBox.Yes: self.flight.flight_type = task try: self.planner.populate_flight_plan(self.flight) except PlanningError as ex: self.flight.flight_type = original_task logging.exception("Could not recreate flight") QMessageBox.critical(self, "Could not recreate flight", str(ex), QMessageBox.Ok) self.flight_waypoint_list.update_list() self.on_change() def on_change(self): self.flight_waypoint_list.update_list()
def update_flight_plan(self) -> None: planner = FlightPlanBuilder(self.game, self.package_model.package, is_player=True) planner.populate_flight_plan(self.flight)
class QFlightWaypointTab(QFrame): on_flight_changed = Signal() def __init__(self, game: Game, package: Package, flight: Flight): super(QFlightWaypointTab, self).__init__() self.game = game self.package = package self.flight = flight self.planner = FlightPlanBuilder(self.game, package, is_player=True) self.flight_waypoint_list: Optional[QFlightWaypointList] = None self.rtb_waypoint: Optional[QPushButton] = None self.delete_selected: Optional[QPushButton] = None self.open_fast_waypoint_button: Optional[QPushButton] = None self.recreate_buttons: List[QPushButton] = [] self.init_ui() def init_ui(self): layout = QGridLayout() self.flight_waypoint_list = QFlightWaypointList(self.package, self.flight) layout.addWidget(self.flight_waypoint_list, 0, 0) rlayout = QVBoxLayout() layout.addLayout(rlayout, 0, 1) rlayout.addWidget(QLabel("<strong>Generator :</strong>")) rlayout.addWidget(QLabel("<small>AI compatible</small>")) # TODO: Filter by objective type. self.recreate_buttons.clear() recreate_types = [ FlightType.CAS, FlightType.CAP, FlightType.DEAD, FlightType.ESCORT, FlightType.SEAD, FlightType.STRIKE ] for task in recreate_types: def make_closure(arg): def closure(): return self.confirm_recreate(arg) return closure button = QPushButton(f"Recreate as {task.name}") button.clicked.connect(make_closure(task)) rlayout.addWidget(button) self.recreate_buttons.append(button) rlayout.addWidget(QLabel("<strong>Advanced : </strong>")) rlayout.addWidget(QLabel("<small>Do not use for AI flights</small>")) self.rtb_waypoint = QPushButton("Add RTB Waypoint") self.rtb_waypoint.clicked.connect(self.on_rtb_waypoint) rlayout.addWidget(self.rtb_waypoint) self.delete_selected = QPushButton("Delete Selected") self.delete_selected.clicked.connect(self.on_delete_waypoint) rlayout.addWidget(self.delete_selected) self.open_fast_waypoint_button = QPushButton("Add Waypoint") self.open_fast_waypoint_button.clicked.connect(self.on_fast_waypoint) rlayout.addWidget(self.open_fast_waypoint_button) rlayout.addStretch() self.setLayout(layout) def on_delete_waypoint(self): wpt = self.flight_waypoint_list.selectionModel().currentIndex().row() if wpt > 0: self.delete_waypoint(self.flight.flight_plan.waypoints[wpt]) self.flight_waypoint_list.update_list() self.on_change() def delete_waypoint(self, waypoint: FlightWaypoint) -> None: # Need to degrade to a custom flight plan and remove the waypoint. # If the waypoint is a target waypoint and is not the last target # waypoint, we don't need to degrade. if isinstance(self.flight.flight_plan, StrikeFlightPlan): is_target = waypoint in self.flight.flight_plan.targets if is_target and len(self.flight.flight_plan.targets) > 1: self.flight.flight_plan.targets.remove(waypoint) return self.degrade_to_custom_flight_plan() self.flight.flight_plan.waypoints.remove(waypoint) def on_fast_waypoint(self): self.subwindow = QPredefinedWaypointSelectionWindow(self.game, self.flight, self.flight_waypoint_list) self.subwindow.waypoints_added.connect(self.on_waypoints_added) self.subwindow.show() def on_waypoints_added(self, waypoints: Iterable[FlightWaypoint]) -> None: if not waypoints: return self.degrade_to_custom_flight_plan() self.flight.flight_plan.waypoints.extend(waypoints) self.flight_waypoint_list.update_list() self.on_change() def on_rtb_waypoint(self): rtb = self.planner.generate_rtb_waypoint(self.flight, self.flight.from_cp) self.degrade_to_custom_flight_plan() self.flight.flight_plan.waypoints.append(rtb) self.flight_waypoint_list.update_list() self.on_change() def degrade_to_custom_flight_plan(self) -> None: if not isinstance(self.flight.flight_plan, CustomFlightPlan): self.flight.flight_plan = CustomFlightPlan( package=self.flight.package, flight=self.flight, custom_waypoints=self.flight.flight_plan.waypoints ) def confirm_recreate(self, task: FlightType) -> None: result = QMessageBox.question( self, "Regenerate flight?", ("Changing the flight type will reset its flight plan. Do you want " "to continue?"), QMessageBox.No, QMessageBox.Yes ) if result == QMessageBox.Yes: # TODO: Should be buttons for both BARCAP and TARCAP. # BARCAP and TARCAP behave differently. TARCAP arrives a few minutes # ahead of the rest of the package and stays until the package # departs, whereas BARCAP usually isn't part of a strike package and # has a fixed mission time. if task == FlightType.CAP: if isinstance(self.package.target, FrontLine): task = FlightType.TARCAP else: task = FlightType.BARCAP self.flight.flight_type = task self.planner.populate_flight_plan(self.flight) self.flight_waypoint_list.update_list() self.on_change() def on_change(self): self.flight_waypoint_list.update_list() self.on_flight_changed.emit()