Esempio n. 1
0
    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)
Esempio n. 2
0
    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()
Esempio n. 5
0
 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()
Esempio n. 6
0
    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
Esempio n. 7
0
    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)
Esempio n. 11
0
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()