Beispiel #1
0
    def generate_frontline_cap(self, flight, ally_cp, enemy_cp):
        """
        Generate a cap flight for the frontline between ally_cp and enemy cp in order to ensure air superiority and
        protect friendly CAP airbase
        :param flight: Flight to setup
        :param ally_cp: CP to protect
        :param enemy_cp: Enemy connected cp
        """
        flight.flight_type = FlightType.CAP
        patrol_alt = random.randint(self.doctrine["PATROL_ALT_RANGE"][0],
                                    self.doctrine["PATROL_ALT_RANGE"][1])

        # Find targets waypoints
        ingress, heading, distance = Conflict.frontline_vector(
            ally_cp, enemy_cp, self.game.theater)
        center = ingress.point_from_heading(heading, distance / 2)
        orbit_center = center.point_from_heading(
            heading - 90, random.randint(nm_to_meter(6), nm_to_meter(15)))

        combat_width = distance / 2
        if combat_width > 500000:
            combat_width = 500000
        if combat_width < 35000:
            combat_width = 35000

        radius = combat_width * 1.25
        orbit0p = orbit_center.point_from_heading(heading, radius)
        orbit1p = orbit_center.point_from_heading(heading + 180, radius)

        # Create points
        ascend = self.generate_ascend_point(flight.from_cp)
        flight.points.append(ascend)

        orbit0 = FlightWaypoint(orbit0p.x, orbit0p.y, patrol_alt)
        orbit0.name = "ORBIT 0"
        orbit0.description = "Standby between this point and the next one"
        orbit0.pretty_name = "Race-track start"
        orbit0.waypoint_type = FlightWaypointType.PATROL_TRACK
        flight.points.append(orbit0)

        orbit1 = FlightWaypoint(orbit1p.x, orbit1p.y, patrol_alt)
        orbit1.name = "ORBIT 1"
        orbit1.description = "Standby between this point and the previous one"
        orbit1.pretty_name = "Race-track end"
        orbit1.waypoint_type = FlightWaypointType.PATROL
        flight.points.append(orbit1)

        # Note : Targets of a PATROL TRACK waypoints are the points to be defended
        orbit0.targets.append(flight.from_cp)
        orbit0.targets.append(center)

        descend = self.generate_descend_point(flight.from_cp)
        flight.points.append(descend)

        rtb = self.generate_rtb_waypoint(flight.from_cp)
        flight.points.append(rtb)
    def generate_frontline_cap(self, flight: Flight) -> FrontLineCapFlightPlan:
        """Generate a CAP flight plan for the given front line.

        Args:
            flight: The flight to generate the flight plan for.
        """
        location = self.package.target

        if not isinstance(location, FrontLine):
            raise InvalidObjectiveLocation(flight.flight_type, location)

        ally_cp, enemy_cp = location.control_points
        patrol_alt = random.randint(self.doctrine.min_patrol_altitude,
                                    self.doctrine.max_patrol_altitude)

        # Find targets waypoints
        ingress, heading, distance = Conflict.frontline_vector(
            ally_cp, enemy_cp, self.game.theater)
        center = ingress.point_from_heading(heading, distance / 2)
        orbit_center = center.point_from_heading(
            heading - 90, random.randint(nm_to_meter(6), nm_to_meter(15)))

        combat_width = distance / 2
        if combat_width > 500000:
            combat_width = 500000
        if combat_width < 35000:
            combat_width = 35000

        radius = combat_width * 1.25
        orbit0p = orbit_center.point_from_heading(heading, radius)
        orbit1p = orbit_center.point_from_heading(heading + 180, radius)

        # Create points
        builder = WaypointBuilder(self.game.conditions, flight, self.doctrine)

        start, end = builder.race_track(orbit0p, orbit1p, patrol_alt)
        descent, land = builder.rtb(flight.from_cp)
        return FrontLineCapFlightPlan(
            package=self.package,
            flight=flight,
            # Note that this duration only has an effect if there are no
            # flights in the package that have requested escort. If the package
            # requests an escort the CAP flight will remain on station for the
            # duration of the escorted mission, or until it is winchester/bingo.
            patrol_duration=self.doctrine.cap_duration,
            takeoff=builder.takeoff(flight.from_cp),
            ascent=builder.ascent(flight.from_cp),
            patrol_start=start,
            patrol_end=end,
            descent=descent,
            land=land)
 def setGame(self, game: Optional[Game]):
     self.game = game
     if self.game is not None:
         logging.debug("Reloading Map Canvas")
         self.nm_to_pixel_ratio = self.km_to_pixel(
             float(nm_to_meter(1)) / 1000.0)
         self.reload_scene()
    def generate_barcap(self, flight: Flight) -> BarCapFlightPlan:
        """Generate a BARCAP flight at a given location.

        Args:
            flight: The flight to generate the flight plan for.
        """
        location = self.package.target

        if isinstance(location, FrontLine):
            raise InvalidObjectiveLocation(flight.flight_type, location)

        patrol_alt = random.randint(self.doctrine.min_patrol_altitude,
                                    self.doctrine.max_patrol_altitude)

        closest_cache = ObjectiveDistanceCache.get_closest_airfields(location)
        for airfield in closest_cache.closest_airfields:
            # If the mission is a BARCAP of an enemy airfield, find the *next*
            # closest enemy airfield.
            if airfield == self.package.target:
                continue
            if airfield.captured != self.is_player:
                closest_airfield = airfield
                break
        else:
            raise PlanningError("Could not find any enemy airfields")

        heading = location.position.heading_between_point(
            closest_airfield.position)

        min_distance_from_enemy = nm_to_meter(20)
        distance_to_airfield = int(
            closest_airfield.position.distance_to_point(
                self.package.target.position))
        distance_to_no_fly = distance_to_airfield - min_distance_from_enemy
        min_cap_distance = min(self.doctrine.cap_min_distance_from_cp,
                               distance_to_no_fly)
        max_cap_distance = min(self.doctrine.cap_max_distance_from_cp,
                               distance_to_no_fly)

        end = location.position.point_from_heading(
            heading, random.randint(min_cap_distance, max_cap_distance))
        diameter = random.randint(self.doctrine.cap_min_track_length,
                                  self.doctrine.cap_max_track_length)
        start = end.point_from_heading(heading - 180, diameter)

        builder = WaypointBuilder(self.game.conditions, flight, self.doctrine)
        start, end = builder.race_track(start, end, patrol_alt)
        descent, land = builder.rtb(flight.from_cp)

        return BarCapFlightPlan(package=self.package,
                                flight=flight,
                                patrol_duration=self.doctrine.cap_duration,
                                takeoff=builder.takeoff(flight.from_cp),
                                ascent=builder.ascent(flight.from_cp),
                                patrol_start=start,
                                patrol_end=end,
                                descent=descent,
                                land=land)
 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_operate(aircraft):
             continue
         if isinstance(airfield, OffMapSpawn):
             continue
         return airfield
     return None
    def draw_scale(self, scale_distance_nm=20, number_of_points=4):

        PADDING = 14
        POS_X = 0
        POS_Y = 10
        BIG_LINE = 5
        SMALL_LINE = 2

        dist = self.km_to_pixel(nm_to_meter(scale_distance_nm) / 1000.0)
        self.scene().addRect(POS_X,
                             POS_Y - PADDING,
                             PADDING * 2 + dist,
                             BIG_LINE * 2 + 3 * PADDING,
                             pen=CONST.COLORS["black"],
                             brush=CONST.COLORS["black"])
        l = self.scene().addLine(POS_X + PADDING, POS_Y + BIG_LINE * 2,
                                 POS_X + PADDING + dist, POS_Y + BIG_LINE * 2)

        text = self.scene().addText("0nm",
                                    font=QFont("Trebuchet MS",
                                               6,
                                               weight=5,
                                               italic=False))
        text.setPos(POS_X, POS_Y + BIG_LINE * 2)
        text.setDefaultTextColor(Qt.white)

        text2 = self.scene().addText(str(scale_distance_nm) + "nm",
                                     font=QFont("Trebuchet MS",
                                                6,
                                                weight=5,
                                                italic=False))
        text2.setPos(POS_X + dist, POS_Y + BIG_LINE * 2)
        text2.setDefaultTextColor(Qt.white)

        l.setPen(CONST.COLORS["white"])
        for i in range(number_of_points + 1):
            d = float(i) / float(number_of_points)
            if i == 0 or i == number_of_points:
                h = BIG_LINE
            else:
                h = SMALL_LINE

            l = self.scene().addLine(POS_X + PADDING + d * dist,
                                     POS_Y + BIG_LINE * 2,
                                     POS_X + PADDING + d * dist,
                                     POS_Y + BIG_LINE - h)
            l.setPen(CONST.COLORS["white"])
Beispiel #7
0
    def ascent(self, departure: ControlPoint) -> FlightWaypoint:
        """Create ascent waypoint for the given departure airfield or carrier.

        Args:
            departure: Departure airfield or carrier.
        """
        heading = RunwayAssigner(self.conditions).takeoff_heading(departure)
        position = departure.position.point_from_heading(
            heading, nm_to_meter(5))
        waypoint = FlightWaypoint(
            FlightWaypointType.ASCEND_POINT, position.x, position.y,
            500 if self.is_helo else self.doctrine.pattern_altitude)
        waypoint.name = "ASCEND"
        waypoint.alt_type = "RADIO"
        waypoint.description = "Ascend"
        waypoint.pretty_name = "Ascend"
        return waypoint
Beispiel #8
0
    def descent(self, arrival: ControlPoint) -> FlightWaypoint:
        """Create descent waypoint for the given arrival airfield or carrier.

        Args:
            arrival: Arrival airfield or carrier.
        """
        landing_heading = RunwayAssigner(
            self.conditions).landing_heading(arrival)
        heading = (landing_heading + 180) % 360
        position = arrival.position.point_from_heading(heading, nm_to_meter(5))
        waypoint = FlightWaypoint(
            FlightWaypointType.DESCENT_POINT, position.x, position.y,
            300 if self.is_helo else self.doctrine.pattern_altitude)
        waypoint.name = "DESCEND"
        waypoint.alt_type = "RADIO"
        waypoint.description = "Descend to pattern altitude"
        waypoint.pretty_name = "Descend"
        return waypoint
    def setup_flight_group(self, group, flight, flight_type,
                           dynamic_runways: Dict[str, RunwayData]):

        if flight_type in [
                FlightType.CAP, FlightType.BARCAP, FlightType.TARCAP,
                FlightType.INTERCEPTION
        ]:
            group.task = CAP.name
            self._setup_group(group, CAP, flight, dynamic_runways)
            # group.points[0].tasks.clear()
            group.points[0].tasks.clear()
            group.points[0].tasks.append(
                EngageTargets(max_distance=nm_to_meter(50),
                              targets=[Targets.All.Air]))
            # group.tasks.append(EngageTargets(max_distance=nm_to_meter(120), targets=[Targets.All.Air]))
            if flight.unit_type not in GUNFIGHTERS:
                group.points[0].tasks.append(
                    OptRTBOnOutOfAmmo(OptRTBOnOutOfAmmo.Values.AAM))
            else:
                group.points[0].tasks.append(
                    OptRTBOnOutOfAmmo(OptRTBOnOutOfAmmo.Values.Cannon))

        elif flight_type in [FlightType.CAS, FlightType.BAI]:
            group.task = CAS.name
            self._setup_group(group, CAS, flight, dynamic_runways)
            group.points[0].tasks.clear()
            group.points[0].tasks.append(
                EngageTargets(max_distance=nm_to_meter(10),
                              targets=[Targets.All.GroundUnits.GroundVehicles
                                       ]))
            group.points[0].tasks.append(
                OptReactOnThreat(OptReactOnThreat.Values.EvadeFire))
            group.points[0].tasks.append(
                OptROE(OptROE.Values.OpenFireWeaponFree))
            group.points[0].tasks.append(
                OptRTBOnOutOfAmmo(OptRTBOnOutOfAmmo.Values.Unguided))
            group.points[0].tasks.append(OptRestrictJettison(True))
        elif flight_type in [FlightType.SEAD, FlightType.DEAD]:
            group.task = SEAD.name
            self._setup_group(group, SEAD, flight, dynamic_runways)
            group.points[0].tasks.clear()
            group.points[0].tasks.append(NoTask())
            group.points[0].tasks.append(
                OptReactOnThreat(OptReactOnThreat.Values.EvadeFire))
            group.points[0].tasks.append(OptROE(OptROE.Values.OpenFire))
            group.points[0].tasks.append(OptRestrictJettison(True))
            group.points[0].tasks.append(
                OptRTBOnOutOfAmmo(OptRTBOnOutOfAmmo.Values.ASM))
        elif flight_type in [FlightType.STRIKE]:
            group.task = PinpointStrike.name
            self._setup_group(group, GroundAttack, flight, dynamic_runways)
            group.points[0].tasks.clear()
            group.points[0].tasks.append(
                OptReactOnThreat(OptReactOnThreat.Values.EvadeFire))
            group.points[0].tasks.append(OptROE(OptROE.Values.OpenFire))
            group.points[0].tasks.append(OptRestrictJettison(True))
        elif flight_type in [FlightType.ANTISHIP]:
            group.task = AntishipStrike.name
            self._setup_group(group, AntishipStrike, flight, dynamic_runways)
            group.points[0].tasks.clear()
            group.points[0].tasks.append(
                OptReactOnThreat(OptReactOnThreat.Values.EvadeFire))
            group.points[0].tasks.append(OptROE(OptROE.Values.OpenFire))
            group.points[0].tasks.append(OptRestrictJettison(True))

        group.points[0].tasks.append(OptRTBOnBingoFuel(True))
        group.points[0].tasks.append(OptRestrictAfterburner(True))

        if hasattr(flight.unit_type, 'eplrs'):
            if flight.unit_type.eplrs:
                group.points[0].tasks.append(EPLRS(group.id))

        for i, point in enumerate(flight.points):
            if not point.only_for_player or (point.only_for_player
                                             and flight.client_count > 0):
                pt = group.add_waypoint(Point(point.x, point.y), point.alt)
                if point.waypoint_type == FlightWaypointType.PATROL_TRACK:
                    action = ControlledTask(
                        OrbitAction(
                            altitude=pt.alt,
                            pattern=OrbitAction.OrbitPattern.RaceTrack))
                    action.stop_after_duration(CAP_DURATION * 60)
                    #for tgt in point.targets:
                    #    if hasattr(tgt, "position"):
                    #        engagetgt = EngageTargetsInZone(tgt.position, radius=CAP_DEFAULT_ENGAGE_DISTANCE, targets=[Targets.All.Air])
                    #        pt.tasks.append(engagetgt)
                elif point.waypoint_type == FlightWaypointType.LANDING_POINT:
                    pt.type = "Land"
                    pt.action = PointAction.Landing
                elif point.waypoint_type == FlightWaypointType.INGRESS_STRIKE:

                    if group.units[0].unit_type == B_17G:
                        if len(point.targets) > 0:
                            bcenter = Point(0, 0)
                            for j, t in enumerate(point.targets):
                                bcenter.x += t.position.x
                                bcenter.y += t.position.y
                            bcenter.x = bcenter.x / len(point.targets)
                            bcenter.y = bcenter.y / len(point.targets)
                            bombing = Bombing(bcenter)
                            bombing.params["expend"] = "All"
                            bombing.params["attackQtyLimit"] = False
                            bombing.params["directionEnabled"] = False
                            bombing.params["altitudeEnabled"] = False
                            bombing.params["weaponType"] = 2032
                            bombing.params["groupAttack"] = True
                            pt.tasks.append(bombing)
                    else:
                        for j, t in enumerate(point.targets):
                            print(t.position)
                            pt.tasks.append(Bombing(t.position))
                            if group.units[0].unit_type == JF_17 and j < 4:
                                group.add_nav_target_point(
                                    t.position, "PP" + str(j + 1))
                            if group.units[0].unit_type == F_14B and j == 0:
                                group.add_nav_target_point(t.position, "ST")
                            if group.units[0].unit_type == AJS37 and j < 9:
                                group.add_nav_target_point(
                                    t.position, "M" + str(j + 1))
                elif point.waypoint_type == FlightWaypointType.INGRESS_SEAD:

                    tgroup = self.m.find_group(
                        point.targetGroup.group_identifier)
                    if tgroup is not None:
                        task = AttackGroup(tgroup.id)
                        task.params["expend"] = "All"
                        task.params["attackQtyLimit"] = False
                        task.params["directionEnabled"] = False
                        task.params["altitudeEnabled"] = False
                        task.params["weaponType"] = 268402702  # Guided Weapons
                        task.params["groupAttack"] = True
                        pt.tasks.append(task)

                    for j, t in enumerate(point.targets):
                        if group.units[0].unit_type == JF_17 and j < 4:
                            group.add_nav_target_point(t.position,
                                                       "PP" + str(j + 1))
                        if group.units[0].unit_type == F_14B and j == 0:
                            group.add_nav_target_point(t.position, "ST")
                        if group.units[0].unit_type == AJS37 and j < 9:
                            group.add_nav_target_point(t.position,
                                                       "M" + str(j + 1))

                if pt is not None:
                    pt.alt_type = point.alt_type
                    pt.name = String(point.name)

        self._setup_custom_payload(flight, group)
Beispiel #10
0
    cap_min_distance_from_cp: int
    cap_max_distance_from_cp: int

    cas_duration: timedelta


MODERN_DOCTRINE = Doctrine(
    cap=True,
    cas=True,
    sead=True,
    strike=True,
    antiship=True,
    strike_max_range=1500000,
    sead_max_range=1500000,
    rendezvous_altitude=feet_to_meter(25000),
    hold_distance=nm_to_meter(15),
    push_distance=nm_to_meter(20),
    join_distance=nm_to_meter(20),
    split_distance=nm_to_meter(20),
    ingress_egress_distance=nm_to_meter(45),
    ingress_altitude=feet_to_meter(20000),
    egress_altitude=feet_to_meter(20000),
    min_patrol_altitude=feet_to_meter(15000),
    max_patrol_altitude=feet_to_meter(33000),
    pattern_altitude=feet_to_meter(5000),
    cap_duration=timedelta(minutes=30),
    cap_min_track_length=nm_to_meter(15),
    cap_max_track_length=nm_to_meter(40),
    cap_min_distance_from_cp=nm_to_meter(10),
    cap_max_distance_from_cp=nm_to_meter(40),
    cas_duration=timedelta(minutes=30),
Beispiel #11
0
MODERN_DOCTRINE = {
    "GENERATORS": {
        "CAS": True,
        "CAP": True,
        "SEAD": True,
        "STRIKE": True,
        "ANTISHIP": True,
    },
    "STRIKE_MAX_RANGE": 1500000,
    "SEAD_MAX_RANGE": 1500000,
    "CAP_EVERY_X_MINUTES": 20,
    "CAS_EVERY_X_MINUTES": 30,
    "SEAD_EVERY_X_MINUTES": 40,
    "STRIKE_EVERY_X_MINUTES": 40,
    "INGRESS_EGRESS_DISTANCE": nm_to_meter(45),
    "INGRESS_ALT": feet_to_meter(20000),
    "EGRESS_ALT": feet_to_meter(20000),
    "PATROL_ALT_RANGE": (feet_to_meter(15000), feet_to_meter(33000)),
    "PATTERN_ALTITUDE": feet_to_meter(5000),
    "CAP_PATTERN_LENGTH": (nm_to_meter(15), nm_to_meter(40)),
    "FRONTLINE_CAP_DISTANCE_FROM_FRONTLINE": (nm_to_meter(6), nm_to_meter(15)),
    "CAP_DISTANCE_FROM_CP": (nm_to_meter(10), nm_to_meter(40)),
    "MAX_NUMBER_OF_INTERCEPTION_GROUP": 3,
}

COLDWAR_DOCTRINE = {
    "GENERATORS": {
        "CAS": True,
        "CAP": True,
        "SEAD": True,
Beispiel #12
0
class CoalitionMissionPlanner:
    """Coalition flight planning AI.

    This class is responsible for automatically planning missions for the
    coalition at the start of the turn.

    The primary goal of the mission planner is to protect existing friendly
    assets. Missions will be planned with the following priorities:

    1. CAP for airfields/fleets in close proximity to the enemy to prevent heavy
       losses of friendly aircraft.
    2. CAP for front line areas to protect ground and CAS units.
    3. DEAD to reduce necessity of SEAD for future missions.
    4. CAS to protect friendly ground units.
    5. Strike missions to reduce the enemy's resources.

    TODO: Anti-ship and airfield strikes to reduce enemy sortie rates.
    TODO: BAI to prevent enemy forces from reaching the front line.
    TODO: Should fleets always have a CAP?

    TODO: Stance and doctrine-specific planning behavior.
    """

    # TODO: Merge into doctrine, also limit by aircraft.
    MAX_CAP_RANGE = nm_to_meter(100)
    MAX_CAS_RANGE = nm_to_meter(50)
    MAX_SEAD_RANGE = nm_to_meter(150)
    MAX_STRIKE_RANGE = nm_to_meter(150)

    def __init__(self, game: Game, is_player: bool) -> None:
        self.game = game
        self.is_player = is_player
        self.objective_finder = ObjectiveFinder(self.game, self.is_player)
        self.ato = self.game.blue_ato if is_player else self.game.red_ato

    def propose_missions(self) -> Iterator[ProposedMission]:
        """Identifies and iterates over potential mission in priority order."""
        # Find friendly CPs within 100 nmi from an enemy airfield, plan CAP.
        for cp in self.objective_finder.vulnerable_control_points():
            yield ProposedMission(cp, [
                ProposedFlight(FlightType.BARCAP, 2, self.MAX_CAP_RANGE),
            ])

        # Find front lines, plan CAP.
        for front_line in self.objective_finder.front_lines():
            yield ProposedMission(front_line, [
                ProposedFlight(FlightType.TARCAP, 2, self.MAX_CAP_RANGE),
                ProposedFlight(FlightType.CAS, 2, self.MAX_CAS_RANGE),
            ])

        # Find enemy SAM sites with ranges that cover friendly CPs, front lines,
        # or objects, plan DEAD.
        # Find enemy SAM sites with ranges that extend to within 50 nmi of
        # friendly CPs, front, lines, or objects, plan DEAD.
        for sam in self.objective_finder.threatening_sams():
            yield ProposedMission(
                sam,
                [
                    ProposedFlight(FlightType.DEAD, 2, self.MAX_SEAD_RANGE),
                    # TODO: Max escort range.
                    ProposedFlight(FlightType.ESCORT, 2, self.MAX_SEAD_RANGE),
                ])

        # Plan strike missions.
        for target in self.objective_finder.strike_targets():
            yield ProposedMission(
                target,
                [
                    ProposedFlight(FlightType.STRIKE, 2,
                                   self.MAX_STRIKE_RANGE),
                    # TODO: Max escort range.
                    ProposedFlight(FlightType.SEAD, 2, self.MAX_STRIKE_RANGE),
                    ProposedFlight(FlightType.ESCORT, 2,
                                   self.MAX_STRIKE_RANGE),
                ])

    def plan_missions(self) -> None:
        """Identifies and plans mission for the turn."""
        for proposed_mission in self.propose_missions():
            self.plan_mission(proposed_mission)

        self.stagger_missions()

        for cp in self.objective_finder.friendly_control_points():
            inventory = self.game.aircraft_inventory.for_control_point(cp)
            for aircraft, available in inventory.all_aircraft:
                self.message("Unused aircraft",
                             f"{available} {aircraft.id} from {cp}")

    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 stagger_missions(self) -> None:
        def start_time_generator(count: int, earliest: int, latest: int,
                                 margin: int) -> Iterator[timedelta]:
            interval = (latest - earliest) // count
            for time in range(earliest, latest, interval):
                error = random.randint(-margin, margin)
                yield timedelta(minutes=max(0, time + error))

        dca_types = (FlightType.BARCAP, FlightType.INTERCEPTION)

        non_dca_packages = [
            p for p in self.ato.packages if p.primary_task not in dca_types
        ]

        start_time = start_time_generator(count=len(non_dca_packages),
                                          earliest=5,
                                          latest=90,
                                          margin=5)
        for package in self.ato.packages:
            tot = TotEstimator(package).earliest_tot()
            if package.primary_task in dca_types:
                # All CAP missions should be on station ASAP.
                package.time_over_target = tot
            else:
                # But other packages should be spread out a bit. Note that take
                # times are delayed, but all aircraft will become active at
                # mission start. This makes it more worthwhile to attack enemy
                # airfields to hit grounded aircraft, since they're more likely
                # to be present. Runway and air started aircraft will be
                # delayed until their takeoff time by AirConflictGenerator.
                package.time_over_target = next(start_time) + tot

    def message(self, title, text) -> None:
        """Emits a planning message to the player.

        If the mission planner belongs to the players coalition, this emits a
        message to the info panel.
        """
        if self.is_player:
            self.game.informations.append(
                Information(title, text, self.game.turn))
        else:
            logging.info(f"{title}: {text}")
Beispiel #13
0
class ObjectiveFinder:
    """Identifies potential objectives for the mission planner."""

    # TODO: Merge into doctrine.
    AIRFIELD_THREAT_RANGE = nm_to_meter(150)
    SAM_THREAT_RANGE = nm_to_meter(100)

    def __init__(self, game: Game, is_player: bool) -> None:
        self.game = game
        self.is_player = is_player

    def enemy_sams(self) -> Iterator[TheaterGroundObject]:
        """Iterates over all enemy SAM sites."""
        # Control points might have the same ground object several times, for
        # some reason.
        found_targets: Set[str] = set()
        for cp in self.enemy_control_points():
            for ground_object in cp.ground_objects:
                if not isinstance(ground_object, SamGroundObject):
                    continue

                if ground_object.is_dead:
                    continue

                if ground_object.name in found_targets:
                    continue

                if not self.object_has_radar(ground_object):
                    continue

                # TODO: Yield in order of most threatening.
                # Need to sort in order of how close their defensive range comes
                # to friendly assets. To do that we need to add effective range
                # information to the database.
                yield ground_object
                found_targets.add(ground_object.name)

    def threatening_sams(self) -> Iterator[TheaterGroundObject]:
        """Iterates over enemy SAMs in threat range of friendly control points.

        SAM sites are sorted by their closest proximity to any friendly control
        point (airfield or fleet).
        """
        sams: List[Tuple[TheaterGroundObject, int]] = []
        for sam in self.enemy_sams():
            ranges: List[int] = []
            for cp in self.friendly_control_points():
                ranges.append(sam.distance_to(cp))
            sams.append((sam, min(ranges)))

        sams = sorted(sams, key=operator.itemgetter(1))
        for sam, _range in sams:
            yield sam

    def strike_targets(self) -> Iterator[TheaterGroundObject]:
        """Iterates over enemy strike targets.

        Targets are sorted by their closest proximity to any friendly control
        point (airfield or fleet).
        """
        targets: List[Tuple[TheaterGroundObject, int]] = []
        # Control points might have the same ground object several times, for
        # some reason.
        found_targets: Set[str] = set()
        for enemy_cp in self.enemy_control_points():
            for ground_object in enemy_cp.ground_objects:
                if ground_object.is_dead:
                    continue
                if ground_object.name in found_targets:
                    continue
                ranges: List[int] = []
                for friendly_cp in self.friendly_control_points():
                    ranges.append(ground_object.distance_to(friendly_cp))
                targets.append((ground_object, min(ranges)))
                found_targets.add(ground_object.name)
        targets = sorted(targets, key=operator.itemgetter(1))
        for target, _range in targets:
            yield target

    @staticmethod
    def object_has_radar(ground_object: TheaterGroundObject) -> bool:
        """Returns True if the ground object contains a unit with radar."""
        for group in ground_object.groups:
            for unit in group.units:
                if db.unit_type_from_name(unit.type) in UNITS_WITH_RADAR:
                    return True
        return False

    def front_lines(self) -> Iterator[FrontLine]:
        """Iterates over all active front lines in the theater."""
        for cp in self.friendly_control_points():
            for connected in cp.connected_points:
                if connected.is_friendly(self.is_player):
                    continue

                if Conflict.has_frontline_between(cp, connected):
                    yield FrontLine(cp, connected)

    def vulnerable_control_points(self) -> Iterator[ControlPoint]:
        """Iterates over friendly CPs that are vulnerable to enemy CPs.

        Vulnerability is defined as any enemy CP within threat range of of the
        CP.
        """
        for cp in self.friendly_control_points():
            airfields_in_proximity = self.closest_airfields_to(cp)
            airfields_in_threat_range = airfields_in_proximity.airfields_within(
                self.AIRFIELD_THREAT_RANGE)
            for airfield in airfields_in_threat_range:
                if not airfield.is_friendly(self.is_player):
                    yield cp
                    break

    def friendly_control_points(self) -> Iterator[ControlPoint]:
        """Iterates over all friendly control points."""
        return (c for c in self.game.theater.controlpoints
                if c.is_friendly(self.is_player))

    def enemy_control_points(self) -> Iterator[ControlPoint]:
        """Iterates over all enemy control points."""
        return (c for c in self.game.theater.controlpoints
                if not c.is_friendly(self.is_player))

    def all_possible_targets(self) -> Iterator[MissionTarget]:
        """Iterates over all possible mission targets in the theater.

        Valid mission targets are control points (airfields and carriers), front
        lines, and ground objects (SAM sites, factories, resource extraction
        sites, etc).
        """
        for cp in self.game.theater.controlpoints:
            yield cp
            yield from cp.ground_objects
        yield from self.front_lines()

    @staticmethod
    def closest_airfields_to(location: MissionTarget) -> ClosestAirfields:
        """Returns the closest airfields to the given location."""
        return ObjectiveDistanceCache.get_closest_airfields(location)
 def _hold_point(self, flight: Flight) -> Point:
     heading = flight.from_cp.position.heading_between_point(
         self.package.target.position)
     return flight.from_cp.position.point_from_heading(
         heading, nm_to_meter(15))