Пример #1
0
    def generate_tgo_for_scenery(self, scenery: SceneryGroup) -> None:
        # Special Handling for scenery Objects based on trigger zones
        g = BuildingGroundObject(
            namegen.random_objective_name(),
            scenery.category,
            scenery.position,
            Heading.from_degrees(0),
            self.control_point,
        )
        ground_group = TheaterGroup(
            self.game.next_group_id(),
            scenery.zone_def.name,
            PointWithHeading.from_point(scenery.position,
                                        Heading.from_degrees(0)),
            [],
            g,
        )
        g.groups.append(ground_group)
        # Each nested trigger zone is a target/building/unit for an objective.
        for zone in scenery.zones:
            scenery_unit = SceneryUnit(
                zone.id,
                zone.name,
                dcs.statics.Fortification.White_Flag,
                PointWithHeading.from_point(zone.position,
                                            Heading.from_degrees(0)),
                g,
            )
            scenery_unit.zone = zone
            ground_group.units.append(scenery_unit)

        self.control_point.connected_objectives.append(g)
Пример #2
0
    def _generate_group(
            self,
            side: Country,
            unit_type: GroundUnitType,
            count: int,
            at: Point,
            move_formation: PointAction = PointAction.OffRoad,
            heading: Heading = Heading.from_degrees(0),
    ) -> VehicleGroup:

        if side == self.conflict.attackers_country:
            cp = self.conflict.blue_cp
        else:
            cp = self.conflict.red_cp

        group = self.mission.vehicle_group(
            side,
            namegen.next_unit_name(side, unit_type),
            unit_type.dcs_unit_type,
            position=at,
            group_size=count,
            heading=heading.degrees,
            move_formation=move_formation,
        )

        self.unit_map.add_front_line_units(group, cp, unit_type)

        for c in range(count):
            vehicle: Vehicle = group.units[c]
            vehicle.player_can_drive = True

        return group
Пример #3
0
    def for_pydcs_airport(cls, theater: ConflictTheater,
                          airport: Airport) -> Iterator[RunwayData]:
        for runway in airport.runways:
            runway_number = runway.heading // 10
            runway_side = ["", "L", "R"][runway.leftright]
            runway_name = f"{runway_number:02}{runway_side}"
            yield cls.for_airfield(theater, airport,
                                   Heading.from_degrees(runway.heading),
                                   runway_name)

            # pydcs only exposes one runway per physical runway, so to expose
            # both sides of the runway we need to generate the other.
            heading = Heading.from_degrees(runway.heading).opposite
            runway_number = heading.degrees // 10
            runway_side = ["", "R", "L"][runway.leftright]
            runway_name = f"{runway_number:02}{runway_side}"
            yield cls.for_airfield(theater, airport, heading, runway_name)
Пример #4
0
 def active_runway(
     self,
     theater: ConflictTheater,
     conditions: Conditions,
     dynamic_runways: Dict[str, RunwayData],
 ) -> RunwayData:
     logging.warning("TODO: FOBs have no runways.")
     return RunwayData(
         self.full_name, runway_heading=Heading.from_degrees(0), runway_name=""
     )
Пример #5
0
 def from_template(id: int, dcs_type: Type[DcsUnitType], t: LayoutUnit,
                   go: TheaterGroundObject) -> TheaterUnit:
     return TheaterUnit(
         id,
         t.name,
         dcs_type,
         PointWithHeading.from_point(t.position,
                                     Heading.from_degrees(t.heading)),
         go,
     )
Пример #6
0
 def active_runway(
     self,
     theater: ConflictTheater,
     conditions: Conditions,
     dynamic_runways: Dict[str, RunwayData],
 ) -> RunwayData:
     # TODO: Assign TACAN and ICLS earlier so we don't need this.
     fallback = RunwayData(
         self.full_name, runway_heading=Heading.from_degrees(0), runway_name=""
     )
     return dynamic_runways.get(self.name, fallback)
Пример #7
0
    def build(self) -> PatrollingLayout:
        racetrack_half_distance = nautical_miles(20).meters

        location = self.package.target

        closest_boundary = self.threat_zones.closest_boundary(
            location.position)
        heading_to_threat_boundary = Heading.from_degrees(
            location.position.heading_between_point(closest_boundary))
        distance_to_threat = meters(
            location.position.distance_to_point(closest_boundary))
        orbit_heading = heading_to_threat_boundary

        # Station 70nm outside the threat zone.
        threat_buffer = nautical_miles(70)
        if self.threat_zones.threatened(location.position):
            orbit_distance = distance_to_threat + threat_buffer
        else:
            orbit_distance = distance_to_threat - threat_buffer

        racetrack_center = location.position.point_from_heading(
            orbit_heading.degrees, orbit_distance.meters)

        racetrack_start = racetrack_center.point_from_heading(
            orbit_heading.right.degrees, racetrack_half_distance)

        racetrack_end = racetrack_center.point_from_heading(
            orbit_heading.left.degrees, racetrack_half_distance)

        builder = WaypointBuilder(self.flight, self.coalition)

        tanker_type = self.flight.unit_type
        if tanker_type.patrol_altitude is not None:
            altitude = tanker_type.patrol_altitude
        else:
            altitude = feet(21000)

        racetrack = builder.race_track(racetrack_start, racetrack_end,
                                       altitude)

        return PatrollingLayout(
            departure=builder.takeoff(self.flight.departure),
            nav_to=builder.nav_path(self.flight.departure.position,
                                    racetrack_start, altitude),
            nav_from=builder.nav_path(racetrack_end,
                                      self.flight.arrival.position, altitude),
            patrol_start=racetrack[0],
            patrol_end=racetrack[1],
            arrival=builder.land(self.flight.arrival),
            divert=builder.divert(self.flight.divert),
            bullseye=builder.bullseye(),
        )
Пример #8
0
    def build(self) -> PatrollingLayout:
        package_waypoints = self.package.waypoints
        assert package_waypoints is not None

        racetrack_half_distance = Distance.from_nautical_miles(20).meters

        racetrack_center = package_waypoints.refuel

        split_heading = Heading.from_degrees(
            racetrack_center.heading_between_point(package_waypoints.split))
        home_heading = split_heading.opposite

        racetrack_start = racetrack_center.point_from_heading(
            split_heading.degrees, racetrack_half_distance)

        racetrack_end = racetrack_center.point_from_heading(
            home_heading.degrees, racetrack_half_distance)

        builder = WaypointBuilder(self.flight, self.coalition)

        tanker_type = self.flight.unit_type
        if tanker_type.patrol_altitude is not None:
            altitude = tanker_type.patrol_altitude
        else:
            altitude = feet(21000)

        racetrack = builder.race_track(racetrack_start, racetrack_end,
                                       altitude)

        return PatrollingLayout(
            departure=builder.takeoff(self.flight.departure),
            nav_to=builder.nav_path(self.flight.departure.position,
                                    racetrack_start, altitude),
            nav_from=builder.nav_path(racetrack_end,
                                      self.flight.arrival.position, altitude),
            patrol_start=racetrack[0],
            patrol_end=racetrack[1],
            arrival=builder.land(self.flight.arrival),
            divert=builder.divert(self.flight.divert),
            bullseye=builder.bullseye(),
        )
Пример #9
0
    def build(self) -> SweepLayout:
        assert self.package.waypoints is not None
        target = self.package.target.position
        heading = Heading.from_degrees(
            self.package.waypoints.join.heading_between_point(target)
        )
        start_pos = target.point_from_heading(
            heading.degrees, -self.doctrine.sweep_distance.meters
        )

        builder = WaypointBuilder(self.flight, self.coalition)
        start, end = builder.sweep(start_pos, target, self.doctrine.ingress_altitude)

        hold = builder.hold(self._hold_point())

        refuel = None

        if self.package.waypoints is not None:
            refuel = builder.refuel(self.package.waypoints.refuel)

        return SweepLayout(
            departure=builder.takeoff(self.flight.departure),
            hold=hold,
            nav_to=builder.nav_path(
                hold.position, start.position, self.doctrine.ingress_altitude
            ),
            nav_from=builder.nav_path(
                end.position,
                self.flight.arrival.position,
                self.doctrine.ingress_altitude,
            ),
            sweep_start=start,
            sweep_end=end,
            refuel=refuel,
            arrival=builder.land(self.flight.arrival),
            divert=builder.divert(self.flight.divert),
            bullseye=builder.bullseye(),
        )
Пример #10
0
    def _plan_tank_ifv_action(
        self,
        stance: CombatStance,
        enemy_groups: List[Tuple[VehicleGroup, CombatGroup]],
        dcs_group: VehicleGroup,
        forward_heading: Heading,
        to_cp: ControlPoint,
    ) -> bool:
        """
        Handles adding the DCS tasks for tank and IFV groups for all combat stances.
        Returns True if tasking was added, returns False if the stance was not a combat stance.
        """
        self._set_reform_waypoint(dcs_group, forward_heading)
        if stance == CombatStance.AGGRESSIVE:
            # Attack nearest enemy if any
            # Then move forward OR Attack enemy base if it is not too far away
            target = self.find_nearest_enemy_group(dcs_group, enemy_groups)
            if target is not None:
                rand_offset = Vector2(
                    random.randint(-RANDOM_OFFSET_ATTACK,
                                   RANDOM_OFFSET_ATTACK),
                    random.randint(-RANDOM_OFFSET_ATTACK,
                                   RANDOM_OFFSET_ATTACK),
                )
                target_point = self.conflict.theater.nearest_land_pos(
                    target.points[0].position + rand_offset)
                dcs_group.add_waypoint(target_point)
                dcs_group.points[2].tasks.append(AttackGroup(target.id))

            if (to_cp.position.distance_to_point(dcs_group.points[0].position)
                    <= AGGRESIVE_MOVE_DISTANCE):
                attack_point = self.conflict.theater.nearest_land_pos(
                    to_cp.position.random_point_within(500, 0))
            else:
                # We use an offset heading here because DCS doesn't always
                # force vehicles to move if there's no heading change.
                offset_heading = forward_heading - Heading.from_degrees(2)
                attack_point = self.find_offensive_point(
                    dcs_group, offset_heading, AGGRESIVE_MOVE_DISTANCE)
            dcs_group.add_waypoint(attack_point, PointAction.OffRoad)
        elif stance == CombatStance.BREAKTHROUGH:
            # In breakthrough mode, the units will move forward
            # If the enemy base is close enough, the units will attack the base
            if (to_cp.position.distance_to_point(dcs_group.points[0].position)
                    <= BREAKTHROUGH_OFFENSIVE_DISTANCE):
                attack_point = self.conflict.theater.nearest_land_pos(
                    to_cp.position.random_point_within(500, 0))
            else:
                # We use an offset heading here because DCS doesn't always
                # force vehicles to move if there's no heading change.
                offset_heading = forward_heading - Heading.from_degrees(1)
                attack_point = self.find_offensive_point(
                    dcs_group, offset_heading, BREAKTHROUGH_OFFENSIVE_DISTANCE)
            dcs_group.add_waypoint(attack_point, PointAction.OffRoad)
        elif stance == CombatStance.ELIMINATION:
            # In elimination mode, the units focus on destroying as much enemy groups as possible
            targets = self.find_n_nearest_enemy_groups(dcs_group, enemy_groups,
                                                       3)
            for i, target in enumerate(targets, start=1):
                rand_offset = Vector2(
                    random.randint(-RANDOM_OFFSET_ATTACK,
                                   RANDOM_OFFSET_ATTACK),
                    random.randint(-RANDOM_OFFSET_ATTACK,
                                   RANDOM_OFFSET_ATTACK),
                )
                target_point = self.conflict.theater.nearest_land_pos(
                    target.points[0].position + rand_offset)
                dcs_group.add_waypoint(target_point, PointAction.OffRoad)
                dcs_group.points[i + 1].tasks.append(AttackGroup(target.id))
            if (to_cp.position.distance_to_point(dcs_group.points[0].position)
                    <= AGGRESIVE_MOVE_DISTANCE):
                attack_point = self.conflict.theater.nearest_land_pos(
                    to_cp.position.random_point_within(500, 0))
                dcs_group.add_waypoint(attack_point)

        if stance != CombatStance.RETREAT:
            self.add_morale_trigger(dcs_group, forward_heading)
            return True
        return False
Пример #11
0
    def add_preset_locations(self) -> None:
        for static in self.offshore_strike_targets:
            closest, distance = self.objective_info(static)
            closest.preset_locations.offshore_strike_locations.append(
                PointWithHeading.from_point(
                    static.position, Heading.from_degrees(static.units[0].heading)
                )
            )

        for ship in self.ships:
            closest, distance = self.objective_info(ship, allow_naval=True)
            closest.preset_locations.ships.append(
                PointWithHeading.from_point(
                    ship.position, Heading.from_degrees(ship.units[0].heading)
                )
            )

        for group in self.missile_sites:
            closest, distance = self.objective_info(group)
            closest.preset_locations.missile_sites.append(
                PointWithHeading.from_point(
                    group.position, Heading.from_degrees(group.units[0].heading)
                )
            )

        for group in self.coastal_defenses:
            closest, distance = self.objective_info(group)
            closest.preset_locations.coastal_defenses.append(
                PointWithHeading.from_point(
                    group.position, Heading.from_degrees(group.units[0].heading)
                )
            )

        for group in self.long_range_sams:
            closest, distance = self.objective_info(group)
            closest.preset_locations.long_range_sams.append(
                PointWithHeading.from_point(
                    group.position, Heading.from_degrees(group.units[0].heading)
                )
            )

        for group in self.medium_range_sams:
            closest, distance = self.objective_info(group)
            closest.preset_locations.medium_range_sams.append(
                PointWithHeading.from_point(
                    group.position, Heading.from_degrees(group.units[0].heading)
                )
            )

        for group in self.short_range_sams:
            closest, distance = self.objective_info(group)
            closest.preset_locations.short_range_sams.append(
                PointWithHeading.from_point(
                    group.position, Heading.from_degrees(group.units[0].heading)
                )
            )

        for group in self.aaa:
            closest, distance = self.objective_info(group)
            closest.preset_locations.aaa.append(
                PointWithHeading.from_point(
                    group.position, Heading.from_degrees(group.units[0].heading)
                )
            )

        for group in self.ewrs:
            closest, distance = self.objective_info(group)
            closest.preset_locations.ewrs.append(
                PointWithHeading.from_point(
                    group.position, Heading.from_degrees(group.units[0].heading)
                )
            )

        for group in self.armor_groups:
            closest, distance = self.objective_info(group)
            closest.preset_locations.armor_groups.append(
                PointWithHeading.from_point(
                    group.position, Heading.from_degrees(group.units[0].heading)
                )
            )

        for static in self.helipads:
            closest, distance = self.objective_info(static)
            closest.helipads.append(
                PointWithHeading.from_point(
                    static.position, Heading.from_degrees(static.units[0].heading)
                )
            )

        for static in self.factories:
            closest, distance = self.objective_info(static)
            closest.preset_locations.factories.append(
                PointWithHeading.from_point(
                    static.position, Heading.from_degrees(static.units[0].heading)
                )
            )

        for static in self.ammunition_depots:
            closest, distance = self.objective_info(static)
            closest.preset_locations.ammunition_depots.append(
                PointWithHeading.from_point(
                    static.position, Heading.from_degrees(static.units[0].heading)
                )
            )

        for static in self.strike_targets:
            closest, distance = self.objective_info(static)
            closest.preset_locations.strike_locations.append(
                PointWithHeading.from_point(
                    static.position, Heading.from_degrees(static.units[0].heading)
                )
            )

        for scenery_group in self.scenery:
            closest, distance = self.objective_info(scenery_group)
            closest.preset_locations.scenery.append(scenery_group)
    def generate(self) -> None:
        player_cp = (self.conflict.blue_cp if self.conflict.blue_cp.captured
                     else self.conflict.red_cp)

        country = self.mission.country(self.game.blue.country_name)

        if not self.game.settings.disable_legacy_tanker:
            fallback_tanker_number = 0

            for i, tanker_unit_type in enumerate(
                    self.game.faction_for(player=True).tankers):
                unit_type = tanker_unit_type.dcs_unit_type
                if not issubclass(unit_type, PlaneType):
                    logging.warning(
                        f"Refueling aircraft {unit_type} must be a plane")
                    continue

                # TODO: Make loiter altitude a property of the unit type.
                alt, airspeed = self._get_tanker_params(
                    tanker_unit_type.dcs_unit_type)
                freq = self.radio_registry.alloc_uhf()
                tacan = self.tacan_registry.alloc_for_band(
                    TacanBand.Y, TacanUsage.AirToAir)
                tanker_heading = Heading.from_degrees(
                    self.conflict.red_cp.position.heading_between_point(
                        self.conflict.blue_cp.position) +
                    TANKER_HEADING_OFFSET * i)
                tanker_position = player_cp.position.point_from_heading(
                    tanker_heading.degrees, TANKER_DISTANCE)
                tanker_group = self.mission.refuel_flight(
                    country=country,
                    name=namegen.next_tanker_name(country, tanker_unit_type),
                    airport=None,
                    plane_type=unit_type,
                    position=tanker_position,
                    altitude=alt,
                    race_distance=58000,
                    frequency=freq.mhz,
                    start_type=StartType.Warm,
                    speed=airspeed,
                    tacanchannel=str(tacan),
                )
                tanker_group.set_frequency(freq.mhz)

                callsign = callsign_for_support_unit(tanker_group)
                tacan_callsign = {
                    "Texaco": "TEX",
                    "Arco": "ARC",
                    "Shell": "SHL",
                }.get(callsign)
                if tacan_callsign is None:
                    # The dict above is all the callsigns currently in the game, but
                    # non-Western countries don't use the callsigns and instead just
                    # use numbers. It's possible that none of those nations have
                    # TACAN compatible refueling aircraft, but fallback just in
                    # case.
                    tacan_callsign = f"TK{fallback_tanker_number}"
                    fallback_tanker_number += 1

                if tanker_unit_type != IL_78M:
                    # Override PyDCS tacan channel.
                    tanker_group.points[0].tasks.pop()
                    tanker_group.points[0].tasks.append(
                        ActivateBeaconCommand(
                            tacan.number,
                            tacan.band.value,
                            tacan_callsign,
                            True,
                            tanker_group.units[0].id,
                            True,
                        ))

                tanker_group.points[0].tasks.append(SetInvisibleCommand(True))
                tanker_group.points[0].tasks.append(SetImmortalCommand(True))

                self.air_support.tankers.append(
                    TankerInfo(
                        str(tanker_group.name),
                        callsign,
                        tanker_unit_type.name,
                        freq,
                        tacan,
                        start_time=None,
                        end_time=None,
                        blue=True,
                    ))

        if not self.game.settings.disable_legacy_aewc:
            possible_awacs = [
                a for a in self.game.faction_for(player=True).aircrafts
                if a in AEWC_CAPABLE
            ]

            if not possible_awacs:
                logging.warning("No AWACS for faction")
                return

            awacs_unit = possible_awacs[0]
            freq = self.radio_registry.alloc_uhf()

            unit_type = awacs_unit.dcs_unit_type
            if not issubclass(unit_type, PlaneType):
                logging.warning(f"AWACS aircraft {unit_type} must be a plane")
                return

            awacs_flight = self.mission.awacs_flight(
                country=country,
                name=namegen.next_awacs_name(country),
                plane_type=unit_type,
                altitude=AWACS_ALT,
                airport=None,
                position=self.conflict.position.random_point_within(
                    AWACS_DISTANCE, AWACS_DISTANCE),
                frequency=freq.mhz,
                start_type=StartType.Warm,
            )
            awacs_flight.set_frequency(freq.mhz)

            awacs_flight.points[0].tasks.append(SetInvisibleCommand(True))
            awacs_flight.points[0].tasks.append(SetImmortalCommand(True))

            self.air_support.awacs.append(
                AwacsInfo(
                    group_name=str(awacs_flight.name),
                    callsign=callsign_for_support_unit(awacs_flight),
                    freq=freq,
                    depature_location=None,
                    start_time=None,
                    end_time=None,
                    blue=True,
                ))
Пример #13
0
 def angle_off_headwind(self, runway: RunwayData) -> Heading:
     wind = Heading.from_degrees(
         self.conditions.weather.wind.at_0m.direction)
     ideal_heading = wind.opposite
     return runway.runway_heading.angle_between(ideal_heading)
Пример #14
0
    def cap_racetrack_for_objective(self, location: MissionTarget,
                                    barcap: bool) -> tuple[Point, Point]:
        closest_cache = ObjectiveDistanceCache.get_closest_airfields(location)
        for airfield in closest_cache.operational_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 = Heading.from_degrees(
            location.position.heading_between_point(closest_airfield.position))

        position = ShapelyPoint(self.package.target.position.x,
                                self.package.target.position.y)

        if barcap:
            # BARCAPs should remain far enough back from the enemy that their
            # commit range does not enter the enemy's threat zone. Include a 5nm
            # buffer.
            distance_to_no_fly = (
                meters(position.distance(self.threat_zones.all)) -
                self.doctrine.cap_engagement_range - nautical_miles(5))
            max_track_length = self.doctrine.cap_max_track_length
        else:
            # Other race tracks (TARCAPs, currently) just try to keep some
            # distance from the nearest enemy airbase, but since they are by
            # definition in enemy territory they can't avoid the threat zone
            # without being useless.
            min_distance_from_enemy = nautical_miles(20)
            distance_to_airfield = meters(
                closest_airfield.position.distance_to_point(
                    self.package.target.position))
            distance_to_no_fly = distance_to_airfield - min_distance_from_enemy

            # TARCAPs fly short racetracks because they need to react faster.
            max_track_length = self.doctrine.cap_min_track_length + 0.3 * (
                self.doctrine.cap_max_track_length -
                self.doctrine.cap_min_track_length)

        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.degrees,
            random.randint(int(min_cap_distance.meters),
                           int(max_cap_distance.meters)),
        )

        track_length = random.randint(
            int(self.doctrine.cap_min_track_length.meters),
            int(max_track_length.meters),
        )
        start = end.point_from_heading(heading.opposite.degrees, track_length)
        return start, end
Пример #15
0
 def heading(self) -> Heading:
     return Heading.from_degrees(self.airport.runways[0].heading)
Пример #16
0
 def heading(self) -> Heading:
     return Heading.from_degrees(0)
Пример #17
0
 def heading(self) -> Heading:
     return Heading.from_degrees(0)  # TODO compute heading