예제 #1
0
    def test_teleport(self):
        walk_max = self.bot.config.walk_max
        walk_min = self.bot.config.walk_min
        precision = 0.0
        speed = float("inf")

        self.bot.config.walk_max = 4
        self.bot.config.walk_min = 2

        pw = PolylineWalker(self.bot, ex_dest[0], ex_dest[1], precision=precision)
        self.assertEqual(pw.dest_lat, ex_dest[0], 'dest_lat did not match')
        self.assertEqual(pw.dest_lng, ex_dest[1], 'dest_lng did not match')

        finishedWalking = pw.step(speed=speed)
        self.assertTrue(finishedWalking, 'step should return True')
        distance = Geodesic.WGS84.Inverse(ex_dest[0], ex_dest[1], self.bot.position[0], self.bot.position[1])["s12"]
        self.assertTrue(0.0 <= distance <= (pw.precision + pw.epsilon))
        self.polyline._last_pos = self.polyline.destination
        self.assertTrue(abs(self.polyline.get_alt() - self.bot.position[2]) <= 1)

        self.bot.config.walk_max = walk_max
        self.bot.config.walk_min = walk_min
예제 #2
0
    def test_stay_put(self):
        altitude = 429.5
        self.bot.position = [47.1706378, 8.5167405, altitude]
        walk_max = self.bot.config.walk_max
        walk_min = self.bot.config.walk_min
        precision = 0.0
        speed = 0.0

        self.bot.config.walk_max = 4
        self.bot.config.walk_min = 2

        pw = PolylineWalker(self.bot, ex_dest[0], ex_dest[1], precision=precision)
        self.assertEqual(pw.dest_lat, ex_dest[0], 'dest_lat did not match')
        self.assertEqual(pw.dest_lng, ex_dest[1], 'dest_lng did not match')

        finishedWalking = pw.step(speed=speed)
        self.assertFalse(finishedWalking, 'step should return False')
        distance = Geodesic.WGS84.Inverse(ex_orig[0], ex_orig[1], self.bot.position[0], self.bot.position[1])["s12"]
        self.assertTrue(0.0 <= distance <= (pw.precision + pw.epsilon))
        self.assertTrue(altitude - 1 <= self.bot.position[2] <= altitude + 1)

        self.bot.config.walk_max = walk_max
        self.bot.config.walk_min = walk_min
예제 #3
0
class CampFort(BaseTask):
    SUPPORTED_TASK_API_VERSION = 1

    def __init__(self, bot, config):
        super(CampFort, self).__init__(bot, config)

    def initialize(self):
        self.clusters = None
        self.cluster = None
        self.walker = None
        self.stay_until = 0
        self.move_until = 0
        self.no_log_until = 0

        self.config_max_distance = self.config.get("max_distance", 2000)
        self.config_min_forts_count = self.config.get("min_forts_count", 2)
        self.config_min_lured_forts_count = self.config.get(
            "min_lured_forts_count", 1)
        self.config_camping_time = self.config.get("camping_time", 1800)
        self.config_moving_time = self.config.get("moving_time", 600)

    def work(self):
        if not self.enabled:
            return WorkerResult.SUCCESS

        now = time.time()

        if now < self.move_until:
            return WorkerResult.SUCCESS

        if 0 < self.stay_until < now:
            self.cluster = None
            self.stay_until = 0
            self.move_until = now + self.config_moving_time

            if self.config_moving_time > 0:
                return WorkerResult.SUCCESS

        # Let's make sure we have balls before we sit at a lure!
        # See also catch_pokemon.py
        if self.get_pokeball_count() <= 0:
            self.emit_event(
                'refuse_to_sit',
                formatted='No pokeballs left, refuse to sit at lure!',
            )
            # Move away from lures for a time
            self.cluster = None
            self.stay_until = 0
            self.move_until = now + max(self.config_moving_time,
                                        NO_BALLS_MOVING_TIME)

            return WorkerResult.SUCCESS

        forts = self.get_forts()

        if self.cluster is None:
            if self.clusters is None:
                self.clusters = self.get_clusters(forts.values())

            available_clusters = self.get_available_clusters(forts)

            if len(available_clusters) > 0:
                self.cluster = available_clusters[0]
                self.walker = PolylineWalker(self.bot,
                                             self.cluster["center"][0],
                                             self.cluster["center"][1])

                self.no_log_until = now + LOG_TIME_INTERVAL
                self.emit_event(
                    "new_destination",
                    formatted=
                    'New destination at {distance:.2f} meters: {size} forts, {lured} lured'
                    .format(**self.cluster))
            else:
                return WorkerResult.SUCCESS

        self.update_cluster_distance(self.cluster)
        self.update_cluster_lured(self.cluster, forts)

        if self.stay_until >= now:
            if self.no_log_until < now:
                self.no_log_until = now + LOG_TIME_INTERVAL
                self.emit_event(
                    "staying_at_destination",
                    formatted=
                    'Staying at destination: {size} forts, {lured} lured'.
                    format(**self.cluster))

            if self.cluster["lured"] == 0:
                self.stay_until -= NO_LURED_TIME_MALUS

            self.walker.step(speed=0)
        elif self.walker.step():
            self.stay_until = now + self.config_camping_time
            self.emit_event(
                "arrived_at_destination",
                formatted="Arrived at destination: {size} forts, {lured} lured"
                .format(**self.cluster))
        elif self.no_log_until < now:
            if self.cluster["lured"] == 0:
                self.cluster = None
                self.emit_event("reset_destination",
                                formatted="Lures gone! Resetting destination!")
            else:
                self.no_log_until = now + LOG_TIME_INTERVAL
                self.emit_event(
                    "moving_to_destination",
                    formatted=
                    "Moving to destination at {distance:.2f} meters: {size} forts, {lured} lured"
                    .format(**self.cluster))

        return WorkerResult.RUNNING

    def get_pokeball_count(self):
        return sum([
            inventory.items().get(ball.value).count for ball in
            [Item.ITEM_POKE_BALL, Item.ITEM_GREAT_BALL, Item.ITEM_ULTRA_BALL]
        ])

    def get_forts(self):
        radius = self.config_max_distance + Constants.MAX_DISTANCE_FORT_IS_REACHABLE

        forts = [
            f for f in self.bot.cell["forts"]
            if ("latitude" in f) and ("type" in f)
        ]
        forts = [
            f for f in forts
            if self.get_distance(self.bot.start_position, f) <= radius
        ]

        return {f["id"]: f for f in forts}

    def get_available_clusters(self, forts):
        for cluster in self.clusters:
            self.update_cluster_distance(cluster)
            self.update_cluster_lured(cluster, forts)

        available_clusters = [
            c for c in self.clusters
            if c["lured"] >= self.config_min_lured_forts_count
        ]
        available_clusters = [
            c for c in available_clusters
            if c["size"] >= self.config_min_forts_count
        ]
        available_clusters.sort(key=lambda c: self.get_cluster_key(c),
                                reverse=True)

        return available_clusters

    def get_clusters(self, forts):
        clusters = []
        points = self.get_all_snap_points(forts)

        for c1, c2, fort1, fort2 in points:
            cluster_1 = self.get_cluster(forts, c1)
            cluster_2 = self.get_cluster(forts, c2)

            self.update_cluster_distance(cluster_1)
            self.update_cluster_distance(cluster_2)

            key_1 = self.get_cluster_key(cluster_1)
            key_2 = self.get_cluster_key(cluster_2)

            radius = Constants.MAX_DISTANCE_FORT_IS_REACHABLE

            if key_1 >= key_2:
                cluster = cluster_1

                while True:
                    new_circle, _ = self.get_enclosing_circles(
                        fort1, fort2, radius - 1)

                    if not new_circle:
                        break

                    new_cluster = self.get_cluster(cluster["forts"],
                                                   new_circle)

                    if len(new_cluster["forts"]) < len(cluster["forts"]):
                        break

                    cluster = new_cluster
                    radius -= 1
            else:
                cluster = cluster_2

                while True:
                    _, new_circle = self.get_enclosing_circles(
                        fort1, fort2, radius - 1)

                    if not new_circle:
                        break

                    new_cluster = self.get_cluster(cluster["forts"],
                                                   new_circle)

                    if len(new_cluster["forts"]) < len(cluster["forts"]):
                        break

                    cluster = new_cluster
                    radius -= 1

            clusters.append(cluster)

        return clusters

    def get_all_snap_points(self, forts):
        points = []
        radius = Constants.MAX_DISTANCE_FORT_IS_REACHABLE

        for i in range(0, len(forts)):
            for j in range(i + 1, len(forts)):
                c1, c2 = self.get_enclosing_circles(forts[i], forts[j], radius)

                if c1 and c2:
                    points.append((c1, c2, forts[i], forts[j]))

        return points

    def get_enclosing_circles(self, fort1, fort2, radius):
        x1, y1 = coord2merc(fort1["latitude"], fort1["longitude"])
        x2, y2 = coord2merc(fort2["latitude"], fort2["longitude"])
        dx = x2 - x1
        dy = y2 - y1
        d = math.sqrt(dx**2 + dy**2)

        if (d == 0) or (d > 2 * radius):
            return None, None

        cx, cy = (x1 + x2) / 2, (y1 + y2) / 2
        cd = math.sqrt(radius**2 - (d / 2)**2)

        c1 = merc2coord((cx - cd * dy / d, cy + cd * dx / d)) + (radius, )
        c2 = merc2coord((cx + cd * dy / d, cy - cd * dx / d)) + (radius, )

        return c1, c2

    def get_cluster(self, forts, circle):
        forts_in_circle = [
            f for f in forts if self.get_distance(circle, f) <= circle[2]
        ]

        cluster = {
            "center": (circle[0], circle[1]),
            "distance":
            0,
            "forts":
            forts_in_circle,
            "size":
            len(forts_in_circle),
            "lured":
            sum(1 for f in forts_in_circle
                if f.get("active_fort_modifier", None) is not None)
        }

        return cluster

    def get_cluster_key(self, cluster):
        return (cluster["lured"], cluster["size"], -cluster["distance"])

    def update_cluster_distance(self, cluster):
        cluster["distance"] = great_circle(self.bot.position,
                                           cluster["center"]).meters

    def update_cluster_lured(self, cluster, forts):
        cluster["lured"] = sum(1 for f in cluster["forts"] if forts.get(
            f["id"], {}).get("active_fort_modifier", None) is not None)

    def get_distance(self, location, fort):
        return great_circle(location,
                            (fort["latitude"], fort["longitude"])).meters
예제 #4
0
class CampFort(BaseTask):
    SUPPORTED_TASK_API_VERSION = 1

    def __init__(self, bot, config):
        super(CampFort, self).__init__(bot, config)

    def initialize(self):
        self.clusters = None
        self.cluster = None
        self.walker = None
        self.bot.camping_forts = False
        self.stay_until = 0
        self.move_until = 0
        self.no_log_until = 0
        self.no_recheck_cluster_until = 0

        self.config_max_distance = self.config.get("max_distance", 2000)
        self.config_min_forts_count = self.config.get("min_forts_count", 2)
        self.config_min_lured_forts_count = self.config.get("min_lured_forts_count", 1)
        self.config_camping_time = self.config.get("camping_time", 1800)
        self.config_moving_time = self.config.get("moving_time", 600)

    def work(self):
        if not self.enabled:
            return WorkerResult.SUCCESS

        if self.bot.catch_disabled:
            if not hasattr(self.bot,"camper_disabled_global_warning") or \
                        (hasattr(self.bot,"camper_disabled_global_warning") and not self.bot.camper_disabled_global_warning):
                self.logger.info("All catching tasks are currently disabled until {}. Camping of lured forts disabled till then.".format(self.bot.catch_resume_at.strftime("%H:%M:%S")))
            self.bot.camper_disabled_global_warning = True
            return WorkerResult.SUCCESS
        else:
            self.bot.camper_disabled_global_warning = False

        if self.bot.softban:
            if not hasattr(self.bot, "camper_softban_global_warning") or \
                        (hasattr(self.bot, "camper_softban_global_warning") and not self.bot.camper_softban_global_warning):
                self.logger.info("Possible softban! Not camping forts till fixed.")
            self.bot.camper_softban_global_warning = True
            return WorkerResult.SUCCESS
        else:
            self.bot.softban_global_warning = False

        now = time.time()

        if now < self.move_until:
            return WorkerResult.SUCCESS

        if 0 < self.stay_until < now:
            self.cluster = None
            self.stay_until = 0
            self.move_until = now + self.config_moving_time

            if self.config_moving_time > 0:
                return WorkerResult.SUCCESS

        # Let's make sure we have balls before we sit at a lure!
        # See also catch_pokemon.py
        if self.get_pokeball_count() <= 0:
            self.emit_event(
                'refuse_to_sit',
                formatted='No pokeballs left, refuse to sit at lure!',
            )
            # Move away from lures for a time
            self.cluster = None
            self.stay_until = 0
            self.move_until = now + max(self.config_moving_time, NO_BALLS_MOVING_TIME)

            return WorkerResult.SUCCESS

        forts = self.get_forts()

        if self.cluster is None:
            if self.clusters is None:
                self.clusters = self.get_clusters(forts.values())
            # self.logger.info("Forts: {}".format(len(forts)))
            # self.logger.info("Checking {} clusters for availiblity....".format(len(self.clusters)))
            available_clusters = self.get_available_clusters(forts)

            if len(available_clusters) > 0:
                self.cluster = available_clusters[0]
                self.walker = PolylineWalker(self.bot, self.cluster["center"][0], self.cluster["center"][1])

                self.no_log_until = now + LOG_TIME_INTERVAL
                self.no_recheck_cluster_until = now + NO_BALLS_MOVING_TIME
                self.emit_event("new_destination",
                                formatted='New destination at {distance:.2f} meters: {size} forts, {lured} lured'.format(**self.cluster))
            else:
                # self.logger.info("No clusters found.")
                self.cluster = None
                self.clusters = None
                return WorkerResult.SUCCESS

        # We can check if the cluster is still the best
        elif self.no_recheck_cluster_until < now:
            self.clusters = self.get_clusters(forts.values())
            available_clusters = self.get_available_clusters(forts)
            if len(available_clusters) > 0:
                if self.cluster is not available_clusters[0]:
                    self.cluster = available_clusters[0]
                    self.stay_until = 0
                    self.emit_event("new_destination",
                                    formatted='Better destination found at {distance:.2f} meters: {size} forts, {lured} lured'.format(**self.cluster))
            self.no_recheck_cluster_until = now + NO_BALLS_MOVING_TIME

        self.update_cluster_distance(self.cluster)
        self.update_cluster_lured(self.cluster, forts)

        if self.stay_until >= now:
            if self.no_log_until < now:
                self.no_log_until = now + LOG_TIME_INTERVAL
                self.bot.camping_forts = True
                self.emit_event("staying_at_destination",
                                formatted='Staying at destination: {size} forts, {lured} lured'.format(**self.cluster))

            if self.cluster["lured"] == 0:
                self.bot.camping_forts = False # Allow hunter to move
                self.stay_until -= NO_LURED_TIME_MALUS

            self.walker.step(speed=0)
        elif self.walker.step():
            self.stay_until = now + self.config_camping_time
            self.bot.camping_forts = True
            self.emit_event("arrived_at_destination",
                            formatted="Arrived at destination: {size} forts, {lured} lured.".format(**self.cluster))
        elif self.no_log_until < now:
            if self.cluster["lured"] == 0:
                self.cluster = None
                self.bot.camping_forts = False
                self.emit_event("reset_destination",
                                formatted="Lures gone! Resetting destination!")
            else:
                self.no_log_until = now + LOG_TIME_INTERVAL
                self.emit_event("moving_to_destination",
                                formatted="Moving to destination at {distance:.2f} meters: {size} forts, {lured} lured".format(**self.cluster))

        return WorkerResult.RUNNING

    def get_pokeball_count(self):
        return sum([inventory.items().get(ball.value).count for ball in [Item.ITEM_POKE_BALL, Item.ITEM_GREAT_BALL, Item.ITEM_ULTRA_BALL]])

    def get_forts(self):
        radius = self.config_max_distance + Constants.MAX_DISTANCE_FORT_IS_REACHABLE

        forts = [f for f in self.bot.cell["forts"] if ("latitude" in f) and ("type" in f)]
        forts = [f for f in forts if self.get_distance(self.bot.start_position, f) <= radius]

        return {f["id"]: f for f in forts}

    def get_available_clusters(self, forts):
        for cluster in self.clusters:
            self.update_cluster_distance(cluster)
            self.update_cluster_lured(cluster, forts)

        available_clusters = [c for c in self.clusters if c["lured"] >= self.config_min_lured_forts_count]
        available_clusters = [c for c in available_clusters if c["size"] >= self.config_min_forts_count]
        available_clusters.sort(key=lambda c: self.get_cluster_key(c), reverse=True)

        return available_clusters

    def get_clusters(self, forts):
        clusters = []
        points = self.get_all_snap_points(forts)

        for c1, c2, fort1, fort2 in points:
            cluster_1 = self.get_cluster(forts, c1)
            cluster_2 = self.get_cluster(forts, c2)

            self.update_cluster_distance(cluster_1)
            self.update_cluster_distance(cluster_2)

            key_1 = self.get_cluster_key(cluster_1)
            key_2 = self.get_cluster_key(cluster_2)

            radius = Constants.MAX_DISTANCE_FORT_IS_REACHABLE

            if key_1 >= key_2:
                cluster = cluster_1

                while True:
                    new_circle, _ = self.get_enclosing_circles(fort1, fort2, radius - 1)

                    if not new_circle:
                        break

                    new_cluster = self.get_cluster(cluster["forts"], new_circle)

                    if len(new_cluster["forts"]) < len(cluster["forts"]):
                        break

                    cluster = new_cluster
                    radius -= 1
            else:
                cluster = cluster_2

                while True:
                    _, new_circle = self.get_enclosing_circles(fort1, fort2, radius - 1)

                    if not new_circle:
                        break

                    new_cluster = self.get_cluster(cluster["forts"], new_circle)

                    if len(new_cluster["forts"]) < len(cluster["forts"]):
                        break

                    cluster = new_cluster
                    radius -= 1

            clusters.append(cluster)

        return clusters

    def get_all_snap_points(self, forts):
        points = []
        radius = Constants.MAX_DISTANCE_FORT_IS_REACHABLE

        for i in range(0, len(forts)):
            for j in range(i + 1, len(forts)):
                c1, c2 = self.get_enclosing_circles(forts[i], forts[j], radius)

                if c1 and c2:
                    points.append((c1, c2, forts[i], forts[j]))

        return points

    def get_enclosing_circles(self, fort1, fort2, radius):
        x1, y1 = coord2merc(fort1["latitude"], fort1["longitude"])
        x2, y2 = coord2merc(fort2["latitude"], fort2["longitude"])
        dx = x2 - x1
        dy = y2 - y1
        d = math.sqrt(dx ** 2 + dy ** 2)

        if (d == 0) or (d > 2 * radius):
            return None, None

        cx, cy = (x1 + x2) / 2, (y1 + y2) / 2
        cd = math.sqrt(radius ** 2 - (d / 2) ** 2)

        c1 = merc2coord((cx - cd * dy / d, cy + cd * dx / d)) + (radius,)
        c2 = merc2coord((cx + cd * dy / d, cy - cd * dx / d)) + (radius,)

        return c1, c2

    def get_cluster(self, forts, circle):
        forts_in_circle = [f for f in forts if self.get_distance(circle, f) <= circle[2]]

        cluster = {"center": (circle[0], circle[1]),
                   "distance": 0,
                   "forts": forts_in_circle,
                   "size": len(forts_in_circle),
                   "lured": sum(1 for f in forts_in_circle if f.get("active_fort_modifier", None) is not None)}

        return cluster

    def get_cluster_key(self, cluster):
        return (cluster["lured"], cluster["size"], -cluster["distance"])

    def update_cluster_distance(self, cluster):
        cluster["distance"] = great_circle(self.bot.position, cluster["center"]).meters

    def update_cluster_lured(self, cluster, forts):
        cluster["lured"] = sum(1 for f in cluster["forts"] if forts.get(f["id"], {}).get("active_fort_modifier", None) is not None)

    def get_distance(self, location, fort):
        return great_circle(location, (fort["latitude"], fort["longitude"])).meters
예제 #5
0
class CampFort(BaseTask):
    SUPPORTED_TASK_API_VERSION = 1

    def __init__(self, bot, config):
        super(CampFort, self).__init__(bot, config)

    def initialize(self):
        self.destination = None
        self.stay_until = 0
        self.move_until = 0
        self.last_position_update = 0
        self.walker = None

        self.config_max_distance = self.config.get("max_distance", 2000)
        self.config_min_forts_count = self.config.get("min_forts_count", 2)
        self.config_min_lured_forts_count = self.config.get("min_lured_forts_count", 1)
        self.config_camping_time = self.config.get("camping_time", 1800)
        self.config_moving_time = self.config.get("moving_time", 600)

    def work(self):
        if not self.enabled:
            return WorkerResult.SUCCESS

        now = time.time()

        if now < self.move_until:
            return WorkerResult.SUCCESS

        if 0 < self.stay_until < now:
            self.destination = None
            self.stay_until = 0
            self.move_until = now + self.config_moving_time

            if self.config_moving_time > 0:
                return WorkerResult.SUCCESS

        if self.destination is None:
            forts = self.get_forts()
            forts_clusters = self.get_forts_clusters(forts)

            if len(forts_clusters) > 0:
                self.destination = forts_clusters[0]
                self.logger.info("New destination at %s meters: %s forts, %s lured.", int(self.destination[4]), self.destination[3], self.destination[2])
            else:
                # forts = [f for f in forts if f.get("cooldown_complete_timestamp_ms", 0) < now * 1000]
                # fort = min(forts, key=lambda f: self.dist(self.bot.position, f))
                # self.destination = (fort["latitude"], fort["longitude"])
                return WorkerResult.SUCCESS

        if (now - self.last_position_update) < 1:
            return WorkerResult.RUNNING
        else:
            self.last_position_update = now

        if self.stay_until >= now:
            lat = self.destination[0] + random_lat_long_delta() / 5
            lon = self.destination[1] + random_lat_long_delta() / 5
            alt = self.walker.pol_alt + random_alt_delta() / 2
            self.bot.api.set_position(lat, lon, alt)
        else:
            self.walker = PolylineWalker(self.bot, self.destination[0], self.destination[1])
            self.walker.step()

            dst = distance(self.bot.position[0], self.bot.position[1], self.destination[0], self.destination[1])

            if dst < 1:
                forts = self.get_forts()
                circle = (self.destination[0], self.destination[1], Constants.MAX_DISTANCE_FORT_IS_REACHABLE)
                cluster = self.get_cluster(forts, circle)

                self.logger.info("Arrived at destination: %s forts, %s lured.", cluster[3], cluster[2])
                self.stay_until = now + self.config_camping_time

        return WorkerResult.RUNNING

    def get_forts(self):
        radius = self.config_max_distance + Constants.MAX_DISTANCE_FORT_IS_REACHABLE

        forts = [f for f in self.bot.cell["forts"] if ("latitude" in f) and ("type" in f)]
        forts = [f for f in forts if self.dist(self.bot.start_position, f) <= radius]

        return forts

    def get_forts_clusters(self, forts):
        clusters = []
        points = self.get_all_snap_points(forts)

        for c1, c2, fort1, fort2 in points:
            cluster_1 = self.get_cluster(forts, c1)
            cluster_2 = self.get_cluster(forts, c2)
            cluster_key_1 = self.get_cluster_key(cluster_1)
            cluster_key_2 = self.get_cluster_key(cluster_2)
            radius = Constants.MAX_DISTANCE_FORT_IS_REACHABLE

            if cluster_key_1 >= cluster_key_2:
                cluster = cluster_1

                while True:
                    new_circle, _ = self.get_enclosing_circles(fort1, fort2, radius - 1)

                    if not new_circle:
                        break

                    new_cluster = self.get_cluster(forts, new_circle)

                    if new_cluster[3] < cluster[3]:
                        break

                    cluster = new_cluster
                    radius -= 1
            else:
                cluster = cluster_2

                while True:
                    _, new_circle = self.get_enclosing_circles(fort1, fort2, radius - 1)

                    if not new_circle:
                        break

                    new_cluster = self.get_cluster(forts, new_circle)

                    if new_cluster[3] < cluster[3]:
                        break

                    cluster = new_cluster
                    radius -= 1

            clusters.append(cluster)

        clusters = [c for c in clusters if c[2] >= self.config_min_lured_forts_count]
        clusters = [c for c in clusters if c[3] >= self.config_min_forts_count]
        clusters.sort(key=lambda c: self.get_cluster_key(c), reverse=True)

        return clusters

    def get_all_snap_points(self, forts):
        points = []
        radius = Constants.MAX_DISTANCE_FORT_IS_REACHABLE

        for i in range(0, len(forts)):
            for j in range(i + 1, len(forts)):
                c1, c2 = self.get_enclosing_circles(forts[i], forts[j], radius)

                if c1 and c2:
                    points.append((c1, c2, forts[i], forts[j]))

        return points

    def get_enclosing_circles(self, fort1, fort2, radius):
        # This is an approximation which is good enough for us
        # since we are dealing with small distances
        x1, y1 = coord2merc(fort1["latitude"], fort1["longitude"])
        x2, y2 = coord2merc(fort2["latitude"], fort2["longitude"])
        dx = x2 - x1
        dy = y2 - y1
        d = math.sqrt(dx ** 2 + dy ** 2)

        if (d == 0) or (d > 2 * radius):
            return None, None

        cx, cy = (x1 + x2) / 2, (y1 + y2) / 2
        cd = math.sqrt(radius ** 2 - (d / 2) ** 2)

        c1 = merc2coord((cx - cd * dy / d, cy + cd * dx / d)) + (radius,)
        c2 = merc2coord((cx + cd * dy / d, cy - cd * dx / d)) + (radius,)

        return c1, c2

    def get_cluster(self, forts, circle):
        forts_in_circle = [f for f in forts if self.dist(circle, f) <= circle[2]]
        count = len(forts_in_circle)
        lured = len([f for f in forts_in_circle if "active_fort_modifier" in f])
        dst = distance(self.bot.position[0], self.bot.position[1], circle[0], circle[1])

        return (circle[0], circle[1], lured, count, dst)

    def get_cluster_key(self, cluster):
        return (cluster[2], cluster[3], -cluster[4])

    def dist(self, location, fort):
        return distance(location[0], location[1], fort["latitude"], fort["longitude"])