diff --git a/.gitignore b/.gitignore index f353185fe6..a7bbee85ec 100644 --- a/.gitignore +++ b/.gitignore @@ -126,9 +126,10 @@ configs/* !configs/config.json.path.example !configs/config.json.map.example !configs/path.example.json +!config.json.campfort.example !config.json.cluster.example +!config.json.hunter.example !config.json.optimizer.example -!config.json.campfort.example !auth.json.example # Virtualenv folders diff --git a/configs/config.json.hunter.example b/configs/config.json.hunter.example new file mode 100644 index 0000000000..d7dc8b94c1 --- /dev/null +++ b/configs/config.json.hunter.example @@ -0,0 +1,25 @@ +{ + "tasks": [ + { + "type": "PokemonHunter", + "config": { + "enabled": true, + "max_distance": 1500, + "hunt_all": false, + "hunt_vip": true, + "hunt_pokedex": true + } + }, + { + "type": "MoveToFort", + "config": { + "enabled": true, + "lure_attraction": true, + "lure_max_distance": 300, + "ignore_item_count": true, + "walker": "PolylineWalker", + "log_interval": 5 + } + } + ] +} diff --git a/pokemongo_bot/__init__.py b/pokemongo_bot/__init__.py index 8387f28cab..5a1436fe02 100644 --- a/pokemongo_bot/__init__.py +++ b/pokemongo_bot/__init__.py @@ -17,6 +17,7 @@ from geopy.geocoders import GoogleV3 from pgoapi import PGoApi from pgoapi.utilities import f2i, get_cell_ids +from s2sphere import Cell, CellId, LatLng import cell_workers from base_task import BaseTask @@ -710,6 +711,7 @@ def get_meta_cell(self): forts = [] wild_pokemons = [] catchable_pokemons = [] + nearby_pokemons = [] for cell in cells: if "forts" in cell and len(cell["forts"]): forts += cell["forts"] @@ -717,20 +719,31 @@ def get_meta_cell(self): wild_pokemons += cell["wild_pokemons"] if "catchable_pokemons" in cell and len(cell["catchable_pokemons"]): catchable_pokemons += cell["catchable_pokemons"] + if "nearby_pokemons" in cell and len(cell["nearby_pokemons"]): + latlng = LatLng.from_point(Cell(CellId(cell["s2_cell_id"])).get_center()) + + for p in cell["nearby_pokemons"]: + p["latitude"] = latlng.lat().degrees + p["longitude"] = latlng.lng().degrees + p["s2_cell_id"] = cell["s2_cell_id"] + + nearby_pokemons += cell["nearby_pokemons"] # If there are forts present in the cells sent from the server or we don't yet have any cell data, return all data retrieved if len(forts) > 1 or not self.cell: return { "forts": forts, "wild_pokemons": wild_pokemons, - "catchable_pokemons": catchable_pokemons + "catchable_pokemons": catchable_pokemons, + "nearby_pokemons": nearby_pokemons } # If there are no forts present in the data from the server, keep our existing fort data and only update the pokemon cells. else: return { "forts": self.cell["forts"], "wild_pokemons": wild_pokemons, - "catchable_pokemons": catchable_pokemons + "catchable_pokemons": catchable_pokemons, + "nearby_pokemons": nearby_pokemons } def update_web_location(self, cells=[], lat=None, lng=None, alt=None): diff --git a/pokemongo_bot/cell_workers/__init__.py b/pokemongo_bot/cell_workers/__init__.py index dfb3dc1d71..43a769d966 100644 --- a/pokemongo_bot/cell_workers/__init__.py +++ b/pokemongo_bot/cell_workers/__init__.py @@ -6,6 +6,7 @@ from move_to_map_pokemon import MoveToMapPokemon from nickname_pokemon import NicknamePokemon from pokemon_catch_worker import PokemonCatchWorker +from pokemon_hunter import PokemonHunter from pokemon_optimizer import PokemonOptimizer from transfer_pokemon import TransferPokemon from recycle_items import RecycleItems diff --git a/pokemongo_bot/cell_workers/pokemon_hunter.py b/pokemongo_bot/cell_workers/pokemon_hunter.py new file mode 100644 index 0000000000..1ff9d1350b --- /dev/null +++ b/pokemongo_bot/cell_workers/pokemon_hunter.py @@ -0,0 +1,156 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import time + +from geopy.distance import great_circle +from s2sphere import Cell, CellId, LatLng + +from pokemongo_bot import inventory +from pokemongo_bot.base_task import BaseTask +from pokemongo_bot.item_list import Item +from pokemongo_bot.walkers.polyline_walker import PolylineWalker +from pokemongo_bot.walkers.step_walker import StepWalker +from pokemongo_bot.worker_result import WorkerResult + + +class PokemonHunter(BaseTask): + SUPPORTED_TASK_API_VERSION = 1 + + def __init__(self, bot, config): + super(PokemonHunter, self).__init__(bot, config) + + def initialize(self): + self.destination = None + self.walker = None + self.search_cell_id = None + self.search_points = [] + self.lost_counter = 0 + self.no_log_until = 0 + + self.config_max_distance = self.config.get("max_distance", 2000) + self.config_hunt_all = self.config.get("hunt_all", False) + self.config_hunt_vip = self.config.get("hunt_vip", True) + self.config_hunt_pokedex = self.config.get("hunt_pokedex", True) + + def work(self): + if not self.enabled: + return WorkerResult.SUCCESS + + if self.get_pokeball_count() <= 0: + self.destination = None + self.last_cell_id = None + return WorkerResult.SUCCESS + + now = time.time() + pokemons = self.get_nearby_pokemons() + + if self.destination is None: + worth_pokemons = self.get_worth_pokemons(pokemons) + + if len(worth_pokemons) > 0: + self.destination = worth_pokemons[0] + self.lost_counter = 0 + + self.logger.info("New destination at %(distance).2f meters: %(name)s", self.destination) + self.no_log_until = now + 60 + + if self.destination["s2_cell_id"] != self.search_cell_id: + self.search_points = self.get_search_points(self.destination["s2_cell_id"]) + self.walker = PolylineWalker(self.bot, self.search_points[0][0], self.search_points[0][1]) + self.search_cell_id = self.destination["s2_cell_id"] + self.search_points = self.search_points[1:] + self.search_points[:1] + else: + if self.no_log_until < now: + self.logger.info("There is no nearby pokemon worth hunting down [%s]", ", ".join(p["name"] for p in pokemons)) + self.no_log_until = now + 120 + + self.last_cell_id = None + + return WorkerResult.SUCCESS + + if any(self.destination["encounter_id"] == p["encounter_id"] for p in self.bot.cell["catchable_pokemons"] + self.bot.cell["wild_pokemons"]): + self.destination = None + elif self.walker.step(): + if not any(self.destination["encounter_id"] == p["encounter_id"] for p in pokemons): + self.lost_counter += 1 + else: + self.lost_counter = 0 + + if self.lost_counter >= 3: + self.destination = None + else: + self.logger.info("Now searching for %(name)s", self.destination) + + self.walker = StepWalker(self.bot, self.search_points[0][0], self.search_points[0][1]) + self.search_points = self.search_points[1:] + self.search_points[:1] + elif self.no_log_until < now: + distance = great_circle(self.bot.position, (self.walker.dest_lat, self.walker.dest_lng)).meters + self.logger.info("Moving to destination at %s meters: %s", round(distance, 2), self.destination["name"]) + self.no_log_until = now + 30 + + 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_nearby_pokemons(self): + radius = self.config_max_distance + + pokemons = [p for p in self.bot.cell["nearby_pokemons"] if self.get_distance(self.bot.start_position, p) <= radius] + + for pokemon in pokemons: + pokemon["distance"] = self.get_distance(self.bot.position, p) + pokemon["name"] = inventory.pokemons().name_for(pokemon["pokemon_id"]) + + pokemons.sort(key=lambda p: p["distance"]) + + return pokemons + + def get_worth_pokemons(self, pokemons): + if self.config_hunt_all: + worth_pokemons = pokemons + else: + worth_pokemons = [] + + if self.config_hunt_vip: + worth_pokemons += [p for p in pokemons if p["name"] in self.bot.config.vips] + + if self.config_hunt_pokedex: + worth_pokemons += [p for p in pokemons if (p not in worth_pokemons) and any(not inventory.pokedex().seen(fid) for fid in self.get_family_ids(p))] + + worth_pokemons.sort(key=lambda p: inventory.candies().get(p["pokemon_id"]).quantity) + + return worth_pokemons + + def get_family_ids(self, pokemon): + family_id = inventory.pokemons().data_for(pokemon["pokemon_id"]).first_evolution_id + ids = [family_id] + ids += inventory.pokemons().data_for(family_id).next_evolutions_all[:] + + return ids + + def get_distance(self, location, pokemon): + return great_circle(location, (pokemon["latitude"], pokemon["longitude"])).meters + + def get_search_points(self, cell_id): + points = [] + + # For cell level 15 + for c in Cell(CellId(cell_id)).subdivide(): + for cc in c.subdivide(): + latlng = LatLng.from_point(cc.get_center()) + point = (latlng.lat().degrees, latlng.lng().degrees) + points.append(point) + + points[0], points[1] = points[1], points[0] + points[14], points[15] = points[15], points[14] + point = points.pop(2) + points.insert(7, point) + point = points.pop(13) + points.insert(8, point) + + closest = min(points, key=lambda p: great_circle(self.bot.position, p).meters) + index = points.index(closest) + + return points[index:] + points[:index]