diff --git a/docs/configuration_files.md b/docs/configuration_files.md index 48d798bcfd..dacf3a507e 100644 --- a/docs/configuration_files.md +++ b/docs/configuration_files.md @@ -62,6 +62,7 @@ - [CompleteTutorial](#completetutorial) - [BuddyPokemon](#buddypokemon) - [PokemonHunter](#pokemonhunter) +- [BadPokemon](#badpokemon) # Configuration files @@ -172,6 +173,8 @@ The behaviors of the bot are configured via the `tasks` key in the `config.json` [[back to top](#table-of-contents)] * CatchPokemon * `enabled`: Default "true" | Enable/Disable the task. + * `always_catch_family_of_vip`: Default "false" | Always catch family members of a VIP, even if locked on a target. + * `always_catch_trash`: Default "false" | Always catch trash Pokemon (12 candy evolve), even if locked on a target. * `treat_unseen_as_vip`: Default `"true"` | If true, treat new to dex as VIP * `catch_visible_pokemon`: Default "true" | If enabled, attempts to catch "visible" pokemon that are reachable * `catch_lured_pokemon`: Default "true" | If enabled, attempts to catch "lured" pokemon that are reachable @@ -1421,6 +1424,11 @@ Hunts down nearby Pokemon. Searches for Pokemon to complete the Pokedex, or if a * `treat_family_of_vip_as_vip`: `Default: false`. Should we see family of an VIP as a VIP (locking onto it if enabled) * `hunt_for_trash_to_fill_bag`: `Default: false`. Should we try to fill the bag with trash if a set amount of slots is left? * `trash_hunt_open_slots`: `Default: 25`. The amount of slots for the previous setting +* `run_to_vip`: `Default: false`. Run to a VIP Pokemon? Running sets the speed of the walker to the walk_max value! + +### Hunting family members of VIPs +If enabled (`target_family_of_vip` = true), the hunter will also hunt down family members of a VIP. For example, if you marked Gyarados as a VIP Pokemon then the hunter will now also hunt down Magikarps. +When on the hunt for a family member of a VIP, and `treat_family_of_vip_as_vip` is false, the hunter will keep a look out for "real" VIPs. So when hunting for a Magikarp, if a Snorlax shows up in the sightings, the hunter will target the Snorlax. ### Hunting for trash If enabled the hunter will start hunting down Pidgeys, Weedles and Caterpies when a set amount of slots (defaults to 25) are left in the bag to fill. The idea is simple; we are about to start evolving Pokemon. So the priority of the hunter shiftes. BUT when a VIP Pokemon is nearby, the Hunter will always start chasing that VIP first. @@ -1448,3 +1456,33 @@ Also hunting for trash does NOT lock the target, catching all Pokemon it find on } } ``` +## BadPokemon +[[back to top](#table-of-contents)] + +### Description +[[back to top](#table-of-contents)] + +If you have any Pokemon that Niantic has marked as bad (red slashes) this will notify you. If you set the option, it will also transfer those Pokemon. + +### Options +[[back to top](#table-of-contents)] + +* `transfer`: `Default: False`. Must we transfer the bad Pokemon? +* `bulktransfer_enabled`: `Default: True`. If we do transfer the bad Pokemon, may we do so in a batch? +* `action_wait_min`: `Default: 3`. Wait time min +* `action_wait_min`: `Default: 5`. Wait time max +* `min_interval`: `Default: 120`. Run once every X seconds + + +### Sample configuration +[[back to top](#table-of-contents)] +```json +{ + "type": "BadPokemon", + "config": { + "enabled": true, + "transfer": true, + "min_interval": 240 + } +} +``` diff --git a/pokemongo_bot/__init__.py b/pokemongo_bot/__init__.py index b19ac28cf0..08b7b208ca 100644 --- a/pokemongo_bot/__init__.py +++ b/pokemongo_bot/__init__.py @@ -72,6 +72,15 @@ def player_data(self): """ return self._player + @property + def inbox(self): + """ + Returns the inbox data as received from the API. + :return: The inbox data. + :rtype: dict + """ + return self._inbox + @property def stardust(self): dust = filter(lambda y: y['name'] == 'STARDUST', self._player['currencies'])[0] @@ -352,6 +361,13 @@ def _register_events(self): 'longitude' ) ) + self.event_manager.register_event( + 'moving_to_hunter_target', + parameters=( + 'target_name', + 'distance' + ) + ) self.event_manager.register_event( 'moving_to_fort', parameters=( @@ -777,6 +793,16 @@ def _register_events(self): self.event_manager.register_event('catch_limit_on') self.event_manager.register_event('catch_limit_off') + self.event_manager.register_event( + 'pokemon_knock_out_gym', + parameters=('pokemon', 'gym_name', 'notification_date', 'awarded_coins', 'awarded_coins_today') + ) + + self.event_manager.register_event( + 'pokemon_hungy', + parameters=('pokemon', 'gym_name', 'notification_date') + ) + def tick(self): self.health_record.heartbeat() @@ -1042,20 +1068,14 @@ def login(self): formatted="Niantic Official API Version: {}".format(officalAPI) ) - link = "https://pokehash.buddyauth.com/api/hash/versions" - f = urllib2.urlopen(link) - myfile = f.read() - f.close() - bossland_hash_endpoint = myfile.split(",") - total_entry = int(len(bossland_hash_endpoint)) - last_bossland_entry = bossland_hash_endpoint[total_entry-1] - bossland_lastestAPI = last_bossland_entry.split(":")[0].replace('\"','') - hashingAPI_temp = 0 + PGoAPI_version = PGoApi.get_api_version() + PGoAPI_version_str = str(PGoAPI_version) + PGoAPI_version_str = "0."+ PGoAPI_version_str[0:2] + "." + PGoAPI_version_str[-1] self.event_manager.emit( 'security_check', sender=self, level='info', - formatted="Latest Bossland Hashing API Version: {}".format(bossland_lastestAPI) + formatted="Bot is currently running on API {}".format(PGoAPI_version_str) ) if self.config.check_niantic_api is True: @@ -1067,28 +1087,13 @@ def login(self): formatted="Warning: Bot is running on legacy API" ) else: - PGoAPI_hash_endpoint = HashServer.endpoint.split("com/",1)[1] - PGoAPI_hash_version = [] - # Check if PGoAPI hashing is in Bossland versioning - bossland_hash_data = json.loads(myfile) - - for version, endpoint in bossland_hash_data.items(): - if endpoint == PGoAPI_hash_endpoint: - # Version should always be in this format x.xx.x - # Check total len, if less than 4, pack a zero behind - if len(version.replace('.','')) < 4: - version = version + ".0" - hashingAPI_temp = int(version.replace('.','')) - # iOS versioning is always more than 1.19.0 - if hashingAPI_temp < 1190: - PGoAPI_hash_version.append(version) - # assuming andorid versioning is always last entry - PGoAPI_hash_version.sort(reverse=True) - # covert official api version & hashing api version to numbers officialAPI_int = int(officalAPI.replace('.','')) - hashingAPI_int = int(PGoAPI_hash_version[0].replace('.','')) + + PGoAPI_version_tmp = str(PGoAPI_version) + PGoAPI_version_tmp = PGoAPI_version_tmp[0:2] + PGoAPI_version_tmp[-1] + PGoAPI_version_int = int(PGoAPI_version_tmp) - if hashingAPI_int < officialAPI_int: + if PGoAPI_version_int < officialAPI_int: self.event_manager.emit( 'security_check', sender=self, @@ -1101,7 +1106,7 @@ def login(self): 'security_check', sender=self, level='info', - formatted="Current PGoAPI is using API Version: {}. Niantic API Check Pass".format(PGoAPI_hash_version[0]) + formatted="Current PGoAPI is using {} API. Niantic API Check Pass".format(PGoAPI_version_str) ) self.heartbeat() @@ -1243,34 +1248,35 @@ def _print_character_info(self): # Items Output self.logger.info( 'PokeBalls: ' + str(items_inventory.get(1).count) + - ' | GreatBalls: ' + str(items_inventory.get(2).count) + - ' | UltraBalls: ' + str(items_inventory.get(3).count) + - ' | MasterBalls: ' + str(items_inventory.get(4).count)) + ' | Great Balls: ' + str(items_inventory.get(2).count) + + ' | Ultra Balls: ' + str(items_inventory.get(3).count) + + ' | Master Balls: ' + str(items_inventory.get(4).count)) self.logger.info( 'RazzBerries: ' + str(items_inventory.get(701).count) + ' | Nanab Berries: ' + str(items_inventory.get(703).count) + - ' | Pinap Berries: ' + str(items_inventory.get(705).count)) + ' | Pinap Berries: ' + str(items_inventory.get(705).count) + + ' | Golden RazzBerries: ' + str(items_inventory.get(706).count) + + ' | Golden Nanab Berries: ' + str(items_inventory.get(707).count) + + ' | Golden Pinap Berries: ' + str(items_inventory.get(708).count)) self.logger.info( 'LuckyEgg: ' + str(items_inventory.get(301).count) + - ' | Incubator: ' + str(items_inventory.get(902).count) + - ' | TroyDisk: ' + str(items_inventory.get(501).count)) + ' | Incubator: ' + str(items_inventory.get(902).count)) self.logger.info( 'Potion: ' + str(items_inventory.get(101).count) + - ' | SuperPotion: ' + str(items_inventory.get(102).count) + - ' | HyperPotion: ' + str(items_inventory.get(103).count) + - ' | MaxPotion: ' + str(items_inventory.get(104).count)) + ' | Super Potion: ' + str(items_inventory.get(102).count) + + ' | Hyper Potion: ' + str(items_inventory.get(103).count) + + ' | Max Potion: ' + str(items_inventory.get(104).count)) self.logger.info( 'Incense: ' + str(items_inventory.get(401).count) + - ' | IncenseSpicy: ' + str(items_inventory.get(402).count) + - ' | IncenseCool: ' + str(items_inventory.get(403).count)) + ' | Lure Module: ' + str(items_inventory.get(501).count)) self.logger.info( 'Revive: ' + str(items_inventory.get(201).count) + - ' | MaxRevive: ' + str(items_inventory.get(202).count)) + ' | Max Revive: ' + str(items_inventory.get(202).count)) self.logger.info( 'Sun Stone: ' + str(items_inventory.get(1101).count) + @@ -1278,6 +1284,14 @@ def _print_character_info(self): ' | Metal Coat: ' + str(items_inventory.get(1103).count) + ' | Dragon Scale: ' + str(items_inventory.get(1104).count) + ' | Upgrade: ' + str(items_inventory.get(1105).count)) + + self.logger.info( + 'Fast TM: ' + str(items_inventory.get(1201).count) + + ' | Charge TM: ' + str(items_inventory.get(1202).count) + + ' | Rare Candy: ' + str(items_inventory.get(1301).count) + + ' | Free Raid Pass: ' + str(items_inventory.get(1401).count) + + ' | Premium Raid Pass: ' + str(items_inventory.get(1402).count) + + ' | Legendary Raid Pass: ' + str(items_inventory.get(1403).count)) if warn: self.logger.info('') @@ -1548,10 +1562,13 @@ def heartbeat(self): if timeout >= now * 1000} if now - self.last_heartbeat >= self.heartbeat_threshold and not self.hb_locked: + previous_heartbeat = self.last_heartbeat self.last_heartbeat = now request = self.api.create_request() request.get_player() request.check_awarded_badges() + request.get_inbox() + responses = None try: responses = request.call() except NotLoggedInException: @@ -1572,6 +1589,64 @@ def heartbeat(self): formatted='player_data: {player_data}', data={'player_data': self._player} ) + if responses['responses']['GET_INBOX']['result'] == 1: + self._inbox = responses['responses']['GET_INBOX']['inbox'] + # self.logger.info("Got inbox messages?") + # self.logger.info("Inbox: %s" % responses['responses']['GET_INBOX']) + if 'notifications' in self._inbox: + for notification in self._inbox['notifications']: + notification_date = datetime.datetime.fromtimestamp(int(notification['create_timestamp_ms']) / 1e3) + if previous_heartbeat > (int(notification['create_timestamp_ms']) / 1e3): + # Skipp old notifications! + continue + + if notification['category'] == 'pokemon_hungry': + gym_name = pokemon = 'Unknown' + for variable in notification['variables']: + if variable['name'] == 'GYM_NAME': + gym_name = variable['literal'] + if variable['name'] == 'POKEMON_NICKNAME': + pokemon = variable['literal'] + + self.event_manager.emit( + 'pokemon_hungy', + sender=self, + level='info', + formatted='{pokemon} in the Gym {gym_name} is hungy and want a candy! {notification_date}', + data={ + 'pokemon': pokemon, + 'gym_name': gym_name, + 'notification_date': notification_date.strftime('%Y-%m-%d %H:%M:%S.%f') + } + ) + + if notification['category'] == 'gym_removal': + gym_name = pokemon = 'Unknown' + for variable in notification['variables']: + if variable['name'] == 'GYM_NAME': + gym_name = variable['literal'] + if variable['name'] == 'POKEMON_NICKNAME': + pokemon = variable['literal'] + if variable['name'] == 'POKECOIN_AWARDED': + coins_awared = variable['literal'] + if variable['name'] == 'POKECOIN_AWARDED_TODAY': + coins_awared_today = variable['literal'] + + self.event_manager.emit( + 'pokemon_knock_out_gym', + sender=self, + level='info', + formatted='{pokemon} has been knocked out the Gym {gym_name} at {notification_date}. Awarded coins: {awarded_coins} | Today awared: {awarded_coins_today}', + data={ + 'pokemon': pokemon, + 'gym_name': gym_name, + 'notification_date': notification_date.strftime('%Y-%m-%d %H:%M:%S.%f'), + 'awarded_coins': coins_awared, + 'awarded_coins_today': coins_awared_today + } + ) + + if responses['responses']['CHECK_AWARDED_BADGES']['success'] == True: # store awarded_badges reponse to be used in a task or part of heartbeat self._awarded_badges = responses['responses']['CHECK_AWARDED_BADGES'] diff --git a/pokemongo_bot/cell_workers/__init__.py b/pokemongo_bot/cell_workers/__init__.py index 551a3441ce..a08e45da4a 100644 --- a/pokemongo_bot/cell_workers/__init__.py +++ b/pokemongo_bot/cell_workers/__init__.py @@ -32,4 +32,5 @@ from .discord_task import DiscordTask from .buddy_pokemon import BuddyPokemon from .catch_limiter import CatchLimiter -from .update_hash_stats import UpdateHashStats \ No newline at end of file +from .update_hash_stats import UpdateHashStats +from .bad_pokemon import BadPokemon diff --git a/pokemongo_bot/cell_workers/bad_pokemon.py b/pokemongo_bot/cell_workers/bad_pokemon.py new file mode 100644 index 0000000000..6c0ae47694 --- /dev/null +++ b/pokemongo_bot/cell_workers/bad_pokemon.py @@ -0,0 +1,142 @@ +from pokemongo_bot import inventory +from pokemongo_bot.inventory import Pokemon +from pokemongo_bot.inventory import Pokemons +from pokemongo_bot.human_behaviour import sleep, action_delay +from pokemongo_bot.base_task import BaseTask +from pokemongo_bot.worker_result import WorkerResult +from datetime import datetime, timedelta + +class BadPokemon(BaseTask): + SUPPORTED_TASK_API_VERSION = 1 + + def __init__(self, bot, config): + super(BadPokemon, self).__init__(bot, config) + + def initialize(self): + self.config_transfer = self.config.get('transfer', False) + self.config_bulktransfer_enabled = self.config.get('bulktransfer_enabled', True) + self.config_action_wait_min = self.config.get("action_wait_min", 3) + self.config_action_wait_max = self.config.get("action_wait_max", 5) + self.min_interval = self.config.get('min_interval', 120) + self.config_max_bulktransfer = self.config.get('max_bulktransfer', 100) + self.next_update = None + + def work(self): + bad_pokemons = [p for p in inventory.pokemons().all() if p.is_bad] + + if len(bad_pokemons) > 0: + if self._should_print(): + self.logger.warning("You have %s bad (slashed) Pokemon!" % len(bad_pokemons)) + self._compute_next_update() + sleep(1) + if self.config_transfer: + self.transfer_pokemon(bad_pokemons) + + return WorkerResult.SUCCESS + + def _should_print(self): + return self.next_update is None or datetime.now() >= self.next_update + + def _compute_next_update(self): + self.next_update = datetime.now() + timedelta(seconds=self.min_interval) + + def transfer_pokemon(self, pokemons, skip_delay=False): + error_codes = { + 0: 'UNSET', + 1: 'SUCCESS', + 2: 'POKEMON_DEPLOYED', + 3: 'FAILED', + 4: 'ERROR_POKEMON_IS_EGG', + 5: 'ERROR_POKEMON_IS_BUDDY' + } + if self.config_bulktransfer_enabled and len(pokemons) > 1: + while len(pokemons) > 0: + action_delay(self.config_action_wait_min, self.config_action_wait_max) + pokemon_ids = [] + count = 0 + transfered = [] + while len(pokemons) > 0 and count < self.config_max_bulktransfer: + pokemon = pokemons.pop() + transfered.append(pokemon) + pokemon_ids.append(pokemon.unique_id) + count = count + 1 + try: + if self.config_transfer: + request = self.bot.api.create_request() + request.release_pokemon(pokemon_ids=pokemon_ids) + response_dict = request.call() + + result = response_dict['responses']['RELEASE_POKEMON']['result'] + if result != 1: + self.logger.error(u'Error while transfer pokemon: {}'.format(error_codes[result])) + return False + except Exception: + return False + + for pokemon in transfered: + candy = inventory.candies().get(pokemon.pokemon_id) + + if self.config_transfer and (not self.bot.config.test): + candy.add(1) + + self.emit_event("pokemon_release", + formatted="Exchanged {pokemon} [IV {iv}] [CP {cp}] [{candy} candies]", + data={"pokemon": pokemon.name, + "iv": pokemon.iv, + "cp": pokemon.cp, + "candy": candy.quantity}) + + if self.config_transfer: + inventory.pokemons().remove(pokemon.unique_id) + + with self.bot.database as db: + cursor = db.cursor() + cursor.execute("SELECT COUNT(name) FROM sqlite_master WHERE type='table' AND name='transfer_log'") + + db_result = cursor.fetchone() + + if db_result[0] == 1: + db.execute("INSERT INTO transfer_log (pokemon, iv, cp) VALUES (?, ?, ?)", (pokemon.name, pokemon.iv, pokemon.cp)) + + else: + for pokemon in pokemons: + if self.config_transfer and (not self.bot.config.test): + request = self.bot.api.create_request() + request.release_pokemon(pokemon_id=pokemon.unique_id) + response_dict = request.call() + else: + response_dict = {"responses": {"RELEASE_POKEMON": {"candy_awarded": 0}}} + + if not response_dict: + return False + + candy_awarded = response_dict.get("responses", {}).get("RELEASE_POKEMON", {}).get("candy_awarded", 0) + candy = inventory.candies().get(pokemon.pokemon_id) + + if self.config_transfer and (not self.bot.config.test): + candy.add(candy_awarded) + + self.emit_event("pokemon_release", + formatted="Exchanged {pokemon} [IV {iv}] [CP {cp}] [{candy} candies]", + data={"pokemon": pokemon.name, + "iv": pokemon.iv, + "cp": pokemon.cp, + "candy": candy.quantity}) + + if self.config_transfer and (not self.bot.config.test): + inventory.pokemons().remove(pokemon.unique_id) + + with self.bot.database as db: + cursor = db.cursor() + cursor.execute("SELECT COUNT(name) FROM sqlite_master WHERE type='table' AND name='transfer_log'") + + db_result = cursor.fetchone() + + if db_result[0] == 1: + db.execute("INSERT INTO transfer_log (pokemon, iv, cp) VALUES (?, ?, ?)", (pokemon.name, pokemon.iv, pokemon.cp)) + if not skip_delay: + action_delay(self.config_action_wait_min, self.config_action_wait_max) + + return True + + diff --git a/pokemongo_bot/cell_workers/catch_pokemon.py b/pokemongo_bot/cell_workers/catch_pokemon.py index 7f6f2d9cf2..4c49db8154 100644 --- a/pokemongo_bot/cell_workers/catch_pokemon.py +++ b/pokemongo_bot/cell_workers/catch_pokemon.py @@ -71,36 +71,54 @@ def work(self): if hasattr(self.bot, "skipped_pokemon"): # Skip pokemon the catcher told us to ignore - self.pokemon = [ p for p in self.pokemon if p not in self.bot.skipped_pokemon ] + self.pokemon = [p for p in self.pokemon if p not in self.bot.skipped_pokemon] num_pokemon = len(self.pokemon) + always_catch_family_of_vip = self.config.get('always_catch_family_of_vip', False) + always_catch_trash = self.config.get('always_catch_trash', False) + trash_pokemon = ["Caterpie", "Weedle", "Pidgey", "Pidgeotto", "Pidgeot", "Kakuna", "Beedrill", "Metapod", "Butterfree"] if num_pokemon > 0: # try catching mon_to_catch = self.pokemon.pop() - is_vip = hasattr(mon_to_catch, "pokemon_id") and self._is_vip_pokemon(mon_to_catch['pokemon_id']) + is_vip = self._is_vip_pokemon(mon_to_catch) # hasattr(mon_to_catch, "pokemon_id") and self._is_vip_pokemon(mon_to_catch['pokemon_id']) # Always catch VIP Pokemons! if hasattr(self.bot,"hunter_locked_target") and self.bot.hunter_locked_target != None: bounty = self.bot.hunter_locked_target mon_name = Pokemons.name_for(mon_to_catch['pokemon_id']) bounty_name = Pokemons.name_for(bounty['pokemon_id']) - if mon_name != bounty_name and is_vip == False: - # This is not the Pokémon you are looking for... - self.logger.info("[Hunter locked a {}] Ignoring a {}".format(bounty_name, mon_name)) - self.ignored_while_looking.append(mon_to_catch['pokemon_id']) - - if num_pokemon > 1: - return WorkerResult.RUNNING - else: - return WorkerResult.SUCCESS - else: - # We have found a vip or our target... - if bounty_name == mon_name: - self.bot.hunter_locked_target = None - self.logger.info("Found my target {}!".format(bounty_name)) + family_catch = False + trash_catch = False + + if always_catch_trash: + if mon_name in trash_pokemon: + self.logger.info("While on the hunt for {}, I found a {}! I want that Pokemon! Will try to catch...".format(bounty_name, mon_name)) + trash_catch = True + + if always_catch_family_of_vip: + if mon_name != bounty_name: + if self._is_family_of_vip(mon_to_catch['pokemon_id']): + self.logger.info("While on the hunt for {}, I found a {}! I want that Pokemon! Will try to catch...".format(bounty_name, mon_name)) + family_catch = True + + if not family_catch and not trash_catch: + if (mon_name != bounty_name and is_vip is False): + # This is not the Pokémon you are looking for... + self.logger.info("[Hunter locked a {}] Ignoring a {}".format(bounty_name, mon_name)) + self.ignored_while_looking.append(mon_to_catch['pokemon_id']) + + if num_pokemon > 1: + return WorkerResult.RUNNING + else: + return WorkerResult.SUCCESS else: - self.logger.info("While on the hunt for {}, I found a {}! I need that Pokemon! Will try to catch...".format(bounty_name, mon_name)) + # We have found a vip or our target... + if bounty_name == mon_name: + self.bot.hunter_locked_target = None + self.logger.info("Found my target {}!".format(bounty_name)) + else: + self.logger.info("While on the hunt for {}, I found a {}! I want that Pokemon! Will try to catch...".format(bounty_name, mon_name)) try: if self.catch_pokemon(mon_to_catch) == WorkerResult.ERROR: # give up incase something went wrong in our catch worker (ran out of balls, etc) @@ -114,15 +132,23 @@ def work(self): # all pokemon have been processed return WorkerResult.SUCCESS - def _is_vip_pokemon(self, pokemon_id): + def _is_vip_pokemon(self, pokemon): + if 'pokemon_id' not in pokemon: + if not 'name' in pokemon: + return False + pokemon['pokemon_id'] = Pokemons.id_for(pokemon['name']) # having just a name present in the list makes them vip # Not seen pokemons also will become vip if it's not disabled in config - if self.bot.config.vips.get(Pokemons.name_for(pokemon_id)) == {}: + if self.bot.config.vips.get(Pokemons.name_for(pokemon['pokemon_id'])) == {}: return True - if (not inventory.pokedex().seen(pokemon_id)): + if (not inventory.pokedex().seen(pokemon['pokemon_id'])): return True + # If we must treat family of VIP as VIP + if self.config.get('treat_family_of_vip_as_vip', False): + if self._is_family_of_vip(pokemon['pokemon_id']): + return True # If we need the Pokemon for an evolution, catch it. - if any(not inventory.pokedex().seen(fid) for fid in self.get_family_ids(pokemon_id)): + if any(not inventory.pokedex().seen(fid) for fid in self.get_family_ids(pokemon['pokemon_id'])): # self.logger.info('Found a Pokemon whoes family is not yet complete in Pokedex!') return True @@ -267,6 +293,14 @@ def _have_applied_incense(self): return False return False + def _is_family_of_vip(self, pokemon_id): + for fid in self.get_family_ids(pokemon_id): + name = inventory.pokemons().name_for(fid) + if self.bot.config.vips.get(name) == {}: + return True + # No, not a family member of the VIP + return False + def get_family_ids(self, pokemon_id): family_id = inventory.pokemons().data_for(pokemon_id).first_evolution_id ids = [family_id] diff --git a/pokemongo_bot/cell_workers/pokemon_hunter.py b/pokemongo_bot/cell_workers/pokemon_hunter.py index 16abfeea48..8d3ccff6cc 100644 --- a/pokemongo_bot/cell_workers/pokemon_hunter.py +++ b/pokemongo_bot/cell_workers/pokemon_hunter.py @@ -14,13 +14,14 @@ from pokemongo_bot.walkers.polyline_walker import PolylineWalker from pokemongo_bot.walkers.step_walker import StepWalker from pokemongo_bot.worker_result import WorkerResult -from .utils import fort_details +from .utils import fort_details, format_dist, distance import random from random import uniform class PokemonHunter(BaseTask): SUPPORTED_TASK_API_VERSION = 1 + LOOK_AROUND_TIME = 20 def __init__(self, bot, config): super(PokemonHunter, self).__init__(bot, config) @@ -29,15 +30,19 @@ def initialize(self): self.max_pokemon_storage = inventory.get_pokemon_inventory_size() self.notified_second_gen = [] self.destination = None + self.previous_destination = None self.walker = None self.search_cell_id = None self.search_points = [] self.lost_counter = 0 + self.lost_map_counter = 0 self.no_log_until = 0 self.distance_to_target = 0 self.distance_counter = 0 self.recent_tries = [] - self.no_hunt_until = None + # No hunting from the start; give sightings a few secs to load! + self.no_hunt_until = time.time() + 10 + self.no_look_around_until = time.time() + 20 self.hunt_started_at = None self.config_max_distance = self.config.get("max_distance", 2000) @@ -62,6 +67,9 @@ def initialize(self): self.config_hunt_for_trash = self.config.get("hunt_for_trash_to_fill_bag", False) self.config_trash_hunt_open_slots = self.config.get("trash_hunt_open_slots", 25) self.hunting_trash = False + # Allow the bot to run to a VIP? + self.config_run_to_vip = self.config.get("run_to_vip", False) + self.runs_to_vips = 0 def work(self): if not self.enabled: @@ -115,12 +123,22 @@ def work(self): self.hunting_trash = False return WorkerResult.SUCCESS + if hasattr(self.bot,"hunter_locked_target"): + if self.destination is not None and self.bot.hunter_locked_target is not None: + if self.destination is not self.bot.hunter_locked_target: + self.logger.info("Locked on to a different target than destination??") + self.bot.hunter_locked_target = None + if self.destination is not None: if self.destination_caught(): self.logger.info("We found a %(name)s while hunting.", self.destination) - self.recent_tries.append(self.destination['pokemon_id']) + # self.recent_tries.append(self.destination['pokemon_id']) + self.previous_destination = self.destination self.destination = None self.hunting_trash = False + self.bot.hunter_locked_target = None + self.lost_counter = 0 + self.lost_map_counter = 0 if self.config_enable_cooldown: wait = uniform(120, 600) self.no_hunt_until = time.time() + wait @@ -131,9 +149,13 @@ def work(self): if self.destination_vanished(): self.logger.info("Darn, target got away!") - self.recent_tries.append(self.destination['pokemon_id']) + # self.recent_tries.append(self.destination['pokemon_id']) + self.previous_destination = self.destination self.destination = None self.hunting_trash = False + self.bot.hunter_locked_target = None + self.lost_counter = 0 + self.lost_map_counter = 0 if self.config_enable_cooldown: wait = uniform(120, 600) self.no_hunt_until = time.time() + wait @@ -144,30 +166,92 @@ def work(self): now = time.time() pokemons = self.get_nearby_pokemons() + pokemons = filter(lambda x: x["pokemon_id"] not in self.recent_tries, pokemons) + trash_mons = ["Caterpie", "Weedle", "Pidgey", "Pidgeotto", "Pidgeot", "Kakuna", "Beedrill", "Metapod", "Butterfree"] - if self.config_hunt_for_trash and self.hunting_trash is False and (self.destination is None or self._is_vip_pokemon(self.destination) is False ): + if self.destination is not None: + target_mons = filter(lambda x: x["name"] is self.destination["name"], pokemons) + if self.no_log_until < now: + # self.logger.info("Targets on sightings: %s" % len(target_mons)) + if len(pokemons) > 0: + if len(target_mons) < 1: + # Target off sightings; must be getting close + # Drops of at about 120 meters to target... + distance = great_circle(self.bot.position, (self.walker.dest_lat, self.walker.dest_lng)).meters + if (distance > 125 and self.lost_map_counter > 4) or self.lost_map_counter > 10: + # If > 120 meter => must be gone? + # Searching for 10 times, give up... + self.logger.info("It seems %(name)s is no longer there!", self.destination) + self.destination = None + self.hunting_trash = False + self.bot.hunter_locked_target = None + self.lost_map_counter = 0 + self.lost_counter = 0 + if self.config_enable_cooldown: + wait = uniform(120, 600) + self.no_hunt_until = time.time() + wait + self.logger.info("Hunting on cooldown until {}.".format((datetime.now() + timedelta(seconds=wait)).strftime("%H:%M:%S"))) + return WorkerResult.SUCCESS + else: + self.logger.info("Electing new target....") + else: + self.lost_map_counter += 1 + else: + self.lost_map_counter = 0 + else: + self.logger.info("No sightings available at the moment...") + + if self.config_hunt_for_trash and self.hunting_trash is False and (self.destination is None or not self._is_vip_pokemon(self.destination) ): # Okay, we should hunt for trash if the bag is almost full - trash_mons = ["Caterpie", "Weedle", "Pidgey", "Pidgeotto", "Pidgeot", "Kakuna", "Beedrill", "Metapod", "Butterfree"] + pokemons.sort(key=lambda p: p["distance"]) + possible_targets = filter(lambda x: x["name"] in trash_mons, pokemons) if self.pokemon_slots_left() <= self.config_trash_hunt_open_slots: if self.no_log_until < now: - self.logger.info("Less than %s slots left to fill, starting hunt for trash" % elf.config_trash_hunt_open_slots) - for pokemon in pokemons: - if pokemon["name"] in trash_mons: - self.hunting_trash = True - self.destination = pokemon - self.lost_counter = 0 - self.hunt_started_at = datetime.now() - self.logger.info("Hunting for trash at %(distance).2f meters: %(name)s", self.destination) - self.set_target() - # We have a target - return WorkerResult.SUCCESS + self.logger.info("Less than %s slots left to fill, starting hunt for trash" % self.config_trash_hunt_open_slots) + if len(possible_targets) is 0: + self.logger.info("No trash pokemon around...") + for pokemon in possible_targets: + if self.destination is not None: + self.logger.info("Trash hunt takes priority! Changing target...") + self.hunting_trash = True + self.destination = pokemon + self.lost_counter = 0 + self.hunt_started_at = datetime.now() + self.logger.info("Hunting for trash at %(distance).2f meters: %(name)s", self.destination) + self.set_target() + # We have a target + return WorkerResult.RUNNING if self.config_hunt_for_trash and self.hunting_trash: - if self.pokemon_slots_left() > 20: + if self.pokemon_slots_left() > self.config_trash_hunt_open_slots: self.logger.info("No longer trying to fill the bag. Electing new target....") self.hunting_trash = False self.destination = None + # Closer target? + if self.no_log_until < now: + # Don't check every tick! + if self.destination is not None and len(pokemons) > 0: + pokemons.sort(key=lambda p: p["distance"]) + possible_targets = filter(lambda x: x["name"] in trash_mons, pokemons) + # Check for a closer target + self.destination["distance"] = self.get_distance(self.bot.position, self.destination) + for pokemon in possible_targets: + if pokemon is not self.destination: + if round(pokemon["distance"], 2) >= round(self.destination["distance"], 2): + # further away! + break + self.logger.info("Found a closer target: %s < %s" % (pokemon["distance"], self.destination["distance"])) + if self.destination is not None: + self.logger.info("Closer trash hunt takes priority! Changing target...") + self.hunting_trash = True + self.destination = pokemon + self.lost_counter = 0 + self.hunt_started_at = datetime.now() + self.logger.info("New target at %(distance).2f meters: %(name)s", self.destination) + self.set_target() + # We have a target + return WorkerResult.RUNNING if self.destination is None: worth_pokemons = self.get_worth_pokemons(pokemons, self.config_hunt_closest_first) @@ -175,19 +259,48 @@ def work(self): if len(worth_pokemons) > 0: # Pick a random target from the list # random.shuffle(worth_pokemons) - # Priotize closer pokemon - worth_pokemons.sort(key=lambda p: p["distance"]) + if self.config_hunt_closest_first: + # Priotize closer pokemon + worth_pokemons.sort(key=lambda p: p["distance"]) + else: + random.shuffle(worth_pokemons) # Prevents the bot from looping the same Pokemon self.destination = worth_pokemons[0] + + if self.previous_destination is not None: + # Check if we are hunting the same target again... + if self.previous_destination["pokemon_id"] == self.destination["pokemon_id"]: + # Hunting the same pokemon again? + if "fort_id" in self.previous_destination and "fort_id" in self.destination and \ + self.previous_destination["fort_id"] == self.destination["fort_id"]: + # Hunting the same target again? + if len(worth_pokemons) > 1: + self.destination = worth_pokemons[1] + else: + # Checking if it's the same distance... + self.previous_destination["distance"] = self.get_distance(self.bot.start_position, self.previous_destination) + self.destination["distance"] = self.get_distance(self.bot.start_position, self.destination) + if round(self.previous_destination["distance"], 2) == round(self.destination["distance"], 2): + self.logger.info("Likely we are trying the same Pokemon again") + if len(worth_pokemons) > 1: + self.destination = worth_pokemons[1] + + if self.previous_destination == self.destination: + # We already caught that Pokemon! + if len(worth_pokemons) > 1: + self.destination = worth_pokemons[1] + self.set_target() self.lost_counter = 0 self.hunt_started_at = datetime.now() self.logger.info("New destination at %(distance).2f meters: %(name)s", self.destination) - if self._is_vip_pokemon(self.destination): - self.logger.info("This is a VIP Pokemon! Starting hunt.") - if self.config_lock_on_target: - self.bot.hunter_locked_target = self.destination + if self._is_vip_pokemon(self.destination) and self.config_lock_on_target: + self.logger.info("This is a VIP Pokemon! Locking on to target!") + self.bot.hunter_locked_target = self.destination + elif self._is_family_of_vip(self.destination) and self.config_treat_family_of_vip_as_vip and self.config_lock_on_target: + self.logger.info("This Pokemon is family of a VIP! Locking target!") + self.bot.hunter_locked_target = self.destination elif self._is_needed_pokedex(self.destination): self.logger.info("I need a %(name)s to complete the Pokedex! I have %(candies)s candies.", self.destination) if self.config_lock_on_target and not self.config_lock_vip_only: @@ -203,8 +316,10 @@ def work(self): # Show like "Pidgey (12), Zubat(2)" names = Counter((p["name"] for p in pokemons)) sorted(names) # unicode object, no lower? , key=str.lower) - - self.logger.info("There is no nearby pokemon worth hunting down [%s]", ", ".join('{}({})'.format(key, val) for key, val in names.items())) + if len(names) > 0: + self.logger.info("There is no nearby pokemon worth hunting down [%s]", ", ".join('{}({})'.format(key, val) for key, val in names.items())) + else: + self.logger.info("No sightings available at the moment...") self.no_log_until = now + 120 self.destination = None if self.config_enable_cooldown: @@ -217,9 +332,79 @@ def work(self): return WorkerResult.SUCCESS # Make sure a VIP is treated that way - if self.config_lock_on_target and self.destination is not None: - if self._is_vip_pokemon(self.destination) and self.bot.hunter_locked_target is None: + if self.config_lock_on_target and self.bot.hunter_locked_target is None and self.destination is not None: + if self._is_vip_pokemon(self.destination): self.bot.hunter_locked_target = self.destination + #Check if we are treating Family of VIP as VIP + if self.config_treat_family_of_vip_as_vip and self.destination is not None: + if self._is_family_of_vip(self.destination): + # We're hunting for family, so we need to check if we find a VIP + if self.no_log_until < now: + # Not every tick please + possible_targets = filter(lambda p: self._is_vip_pokemon(p), pokemons) + # Update the distance to targets + for p in possible_targets: + p["distance"] = self.get_distance(self.bot.position, p) + possible_targets.sort(key=lambda p: p["distance"]) + if len(possible_targets) > 0: + # Check if it's not the same mon... + if possible_targets[0]["name"] != self.destination["name"]: + self.logger.info("We found a real VIP while hunting for %(name)s", self.destination) + self.destination = possible_targets[0] + self.bot.hunter_locked_target = self.destination + self.lost_counter = 0 + self.hunt_started_at = datetime.now() + self.logger.info("New VIP target at %(distance).2f meters: %(name)s", self.destination) + self.set_target() + # We have a target + return WorkerResult.RUNNING + + # Now we check if there is a VIP target closer by + if self.destination is not None and self.bot.hunter_locked_target is self.destination: + # Hunting a VIP, checking for closer VIP target + if self.no_log_until < now: + # Not every tick please + possible_targets = filter(lambda p: self._is_vip_pokemon(p), pokemons) + # Update the distance to targets + for p in possible_targets: + p["distance"] = self.get_distance(self.bot.position, p) + possible_targets.sort(key=lambda p: p["distance"]) + # Check for a closer target + self.destination["distance"] = self.get_distance(self.bot.position, self.destination) + for pokemon in possible_targets: + if pokemon is not self.destination: + if round(pokemon["distance"], 2) >= round(self.destination["distance"], 2): + # further away! + break + with self.bot.database as conn: + c = conn.cursor() + c.execute( + "SELECT COUNT(pokemon) FROM catch_log where pokemon = '{}' and datetime(dated, 'localtime') > Datetime('{}')".format(pokemon["name"], self.hunt_started_at.strftime("%Y-%m-%d %H:%M:%S"))) + # Now check if there is 1 or more caught + amount = c.fetchone()[0] + if amount > 0: + # We caught this pokemon recently, skip it + continue + if self.config_treat_family_of_vip_as_vip and self._is_family_of_vip(pokemon): + if self._is_vip_pokemon(self.destination): + self.logger.info("Seeing a familymember of a VIP at %(distance).2f meters: %(name)s", pokemon) + self.logger.info("Not hunting down because we are locked to a real VIP: %(name)s", self.destination) + continue + else: + self.logger.info("Closer (is distance) familymember of VIP found!") + + self.logger.info("Found a closer VIP target: %s < %s" % (pokemon["distance"], self.destination["distance"])) + if self.destination is not None: + self.logger.info("Closer VIP hunt takes priority! Changing target...") + self.destination = pokemon + self.bot.hunter_locked_target = self.destination + self.lost_counter = 0 + self.hunt_started_at = datetime.now() + self.logger.info("New VIP target at %(distance).2f meters: %(name)s", self.destination) + self.set_target() + # We have a target + return WorkerResult.RUNNING + # Check if there is a VIP around to hunt if (self.destination is not None and @@ -238,9 +423,8 @@ def work(self): self.set_target() if self.config_lock_on_target: self.bot.hunter_locked_target = self.destination - self.logger.info("Found a VIP Pokemon! Looking for a %(name)s at %(distance).2f.", self.destination) + self.logger.info("Spotted a VIP Pokemon! Looking for a %(name)s at %(distance).2f.", self.destination) return WorkerResult.SUCCESS - pass if self.destination is None: if self.no_log_until < now: @@ -250,6 +434,7 @@ def work(self): if self.config_lock_on_target and not self.config_lock_vip_only: if self.bot.hunter_locked_target == None: self.logger.info("We found a %(name)s while hunting. Aborting the current search.", self.destination) + self.previous_destination = self.destination self.destination = None self.hunting_trash = False if self.config_enable_cooldown: @@ -258,10 +443,31 @@ def work(self): self.logger.info("Hunting on cooldown until {}.".format((datetime.now() + timedelta(seconds=wait)).strftime("%H:%M:%S"))) return WorkerResult.SUCCESS + # Determin if we are allowed to run to a VIP + different_target = False + if self.destination is not None: + if self.previous_destination is None: + self.previous_destination = self.destination + elif self.previous_destination is not self.destination: + different_target = True + self.previous_destination = self.destination + + if self.config_run_to_vip and self._is_vip_pokemon(self.destination): + if self.runs_to_vips > 3: + self.logger.info("Ran to 3 Pokemon in a row. Cooling down...") + self.runs_to_vips = 0 + speed = None + else: + speed = self.bot.config.walk_max + if different_target: + self.runs_to_vips += 1 + else: + speed = None + 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 self.hunting_trash = False - elif self.walker.step(): + elif self.walker.step(speed): if not any(self.destination["encounter_id"] == p["encounter_id"] for p in pokemons): self.lost_counter += 1 else: @@ -278,9 +484,11 @@ def work(self): self.logger.info("Hunting on cooldown until {}.".format((datetime.now() + timedelta(seconds=wait)).strftime("%H:%M:%S"))) 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] + if self.search_points == []: + self.walker = StepWalker(self.bot, self.destination['latitude'], self.destination['longitude']) + else: + 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 if round(distance, 2) == self.distance_to_target: @@ -289,7 +497,13 @@ def work(self): else: self.distance_counter = 0 - if self.distance_counter >= 3: + if self.distance_counter is 3: + # Try another walker + self.logger.info("Having difficulty walking to target, changing walker!") + self.walker = StepWalker(self.bot, self.search_points[0][0], self.search_points[0][1]) + self.distance_counter += 1 + + if self.distance_counter >= 6: # Ignore last 3 if len(self.recent_tries) > 3: self.recent_tries.pop() @@ -305,13 +519,32 @@ def work(self): self.logger.info("Hunting on cooldown until {}.".format((datetime.now() + timedelta(seconds=wait)).strftime("%H:%M:%S"))) return WorkerResult.ERROR else: - self.logger.info("Moving to destination at %s meters: %s", round(distance, 2), self.destination["name"]) + unit = self.bot.config.distance_unit # Unit to use when printing formatted distance + if speed is not None: + self.emit_event( + 'moving_to_hunter_target', + formatted="Running towards VIP target {target_name} - {distance}", + data={ + 'target_name': u"{}".format(self.destination["name"]), + 'distance': format_dist(distance, unit), + } + ) + else: + self.emit_event( + 'moving_to_hunter_target', + formatted="Moving towards target {target_name} - {distance}", + data={ + 'target_name': u"{}".format(self.destination["name"]), + 'distance': format_dist(distance, unit), + } + ) + # self.logger.info("Moving to destination at %s meters: %s", round(distance, 2), self.destination["name"]) # record the new distance... self.distance_to_target = round(distance, 2) if self.config_lock_on_target and not self.config_lock_vip_only: # Just to ensure we stay on target self.bot.hunter_locked_target = self.destination - self.no_log_until = now + 30 + self.no_log_until = now + 5 return WorkerResult.RUNNING @@ -319,10 +552,20 @@ 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 set_target(self): - 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] + if not 's2_cell_id' in self.destination: + # This Pokemon has coords + self.search_points = [] + self.walker = PolylineWalker(self.bot, self.destination["latitude"], self.destination["longitude"]) + self.logger.info("Target must be close by...") + # self.logger.info("destination: %s" % self.destination) + # self.search_points = self.get_search_points(self.bot.cell["s2_cell_id"]) + # self.search_cell_id = self.bot.cell["s2_cell_id"] + # self.search_points = self.search_points[1:] + self.search_points[:1] + else: + 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] if "fort_id" in self.destination: # The Pokemon is hding at a POkestop, so move to that Pokestop! @@ -337,6 +580,15 @@ def set_target(self): fort_name = details.get('name', 'Unknown') self.logger.info("%s is hiding at %s, going there first!" % (self.destination["name"], fort_name)) self.walker = PolylineWalker(self.bot, lat, lng) + else: + nearest_fort = self.get_nearest_fort_on_the_way() + if nearest_fort is not None: + lat = nearest_fort['latitude'] + lng = nearest_fort['longitude'] + details = fort_details(self.bot, nearest_fort['id'], lat, lng) + fort_name = details.get('name', 'Unknown') + self.logger.info("Moving to %s via %s." % (self.destination["name"], fort_name)) + self.walker = PolylineWalker(self.bot, lat, lng) def pokemon_slots_left(self): left = self.max_pokemon_storage - inventory.Pokemons.get_space_used() @@ -347,9 +599,39 @@ def get_nearby_pokemons(self): pokemons = [p for p in self.bot.cell["nearby_pokemons"] if self.get_distance(self.bot.start_position, p) <= radius] + if 'wild_pokemons' in self.bot.cell: + for pokemon in self.bot.cell['wild_pokemons']: + if pokemon['encounter_id'] in map(lambda pokemon: pokemon['encounter_id'], pokemons): + # Already added this Pokemon + continue + if self.get_distance(self.bot.start_position, pokemon) <= radius: + pokemons.append(pokemon) + + if 'catchable_pokemons' in self.bot.cell: + for pokemon in self.bot.cell['catchable_pokemons']: + if pokemon['encounter_id'] in map(lambda pokemon: pokemon['encounter_id'], pokemons): + # Already added this Pokemon + continue + if self.get_distance(self.bot.start_position, pokemon) <= radius: + pokemons.append(pokemon) + for pokemon in pokemons: + if "pokemon_data" in pokemon: + pokemon["pokemon_id"] = pokemon["pokemon_data"]["pokemon_id"] + pokemon["name"] = inventory.pokemons().name_for(pokemon["pokemon_id"]) + + if "name" not in pokemon and "pokemon_id" not in pokemon: + self.logger.warning("Strange result? %s" % pokemon) + # Skip this one! + continue + pokemon["distance"] = self.get_distance(self.bot.position, pokemon) - pokemon["name"] = inventory.pokemons().name_for(pokemon["pokemon_id"]) + + if "name" not in pokemon: + pokemon["name"] = inventory.pokemons().name_for(pokemon["pokemon_id"]) + if "pokemon_id" not in pokemon: + pokemon["pokemon_id"] = inventory.pokemons().id_for(pokemon["name"]) + pokemon["candies"] = inventory.candies().get(pokemon["pokemon_id"]).quantity # Pokemon also has a fort_id of the PokeStop the Pokemon is hiding at. # We should set our first destination at that Pokestop. @@ -364,8 +646,8 @@ def _is_vip_pokemon(self, pokemon): if self.bot.config.vips.get(pokemon["name"]) == {} or (self.config_treat_unseen_as_vip and not inventory.pokedex().seen(pokemon["pokemon_id"])): return True # If we must treat the family of the Pokemon as a VIP, also return true! - if self.config_treat_family_of_vip_as_vip and self._is_family_of_vip(pokemon): - return True + # if self.config_treat_family_of_vip_as_vip and self._is_family_of_vip(pokemon): + # return True def _is_family_of_vip(self, pokemon): for fid in self.get_family_ids(pokemon): @@ -404,6 +686,8 @@ def get_worth_pokemons(self, pokemons, closest_first=False): else: worth_pokemons = [] + worth_pokemons += [p for p in pokemons if not inventory.pokedex().seen(p["pokemon_id"])] + if self.config_hunt_vip: worth_pokemons += [p for p in pokemons if p["name"] in self.bot.config.vips] @@ -483,3 +767,28 @@ def destination_vanished(self): self.logger.info("We lost {} {}(s) since {}".format(amount, self.destination["name"], self.hunt_started_at.strftime("%Y-%m-%d %H:%M:%S"))) return vanished + + def get_nearest_fort_on_the_way(self): + forts = self.bot.get_forts(order_by_distance=True) + + # Remove stops that are still on timeout + forts = filter(lambda x: x["id"] not in self.bot.fort_timeouts, forts) + i = 0 + while i < len(forts): + ratio = float(self.config.get('max_extra_dist_fort', 20)) + dist_self_to_fort = distance(self.bot.position[0], self.bot.position[1], forts[i]['latitude'], + forts[i]['longitude']) + # self.search_points[0][0], self.search_points[0][1] + dist_fort_to_pokemon = distance(self.search_points[0][0], self.search_points[0][1], forts[i]['latitude'], + forts[i]['longitude']) + total_dist = dist_self_to_fort + dist_fort_to_pokemon + dist_self_to_pokemon = distance(self.bot.position[0], self.bot.position[1], self.search_points[0][0], self.search_points[0][1]) + if total_dist < (1 + (ratio / 100)) * dist_self_to_pokemon: + i += 1 + else: + del forts[i] + # Return nearest fort if there are remaining + if len(forts): + return forts[0] + else: + return None diff --git a/pokemongo_bot/event_manager.py b/pokemongo_bot/event_manager.py index ae929dd48f..2be9f02f66 100644 --- a/pokemongo_bot/event_manager.py +++ b/pokemongo_bot/event_manager.py @@ -56,7 +56,7 @@ def emit(self, event, sender=None, level='info', formatted='', data={}): raise EventNotRegisteredException("Event %s not registered..." % event) if self._limit_output: - if (event == self._last_event) and (event in ["moving_to_fort", "moving_to_lured_fort", "position_update"]): + if (event == self._last_event) and (event in ["moving_to_fort", "moving_to_lured_fort", "position_update", "moving_to_hunter_target"]): stdout.write("\033[1A\033[0K\r") stdout.flush() diff --git a/pokemongo_bot/inventory.py b/pokemongo_bot/inventory.py index 0df46966df..52bb00025e 100644 --- a/pokemongo_bot/inventory.py +++ b/pokemongo_bot/inventory.py @@ -1001,6 +1001,7 @@ def __init__(self, data): self.in_fort = 'deployed_fort_id' in data self.is_favorite = data.get('favorite', 0) is 1 self.buddy_candy = data.get('buddy_candy_awarded', 0) + self.is_bad = data.get('is_bad', False) self.buddy_distance_needed = self.static.buddy_distance_needed self.fast_attack = FastAttacks.data_for(data['move_1']) diff --git a/pokemongo_bot/item_list.py b/pokemongo_bot/item_list.py index 73cec29846..41ec2235e8 100644 --- a/pokemongo_bot/item_list.py +++ b/pokemongo_bot/item_list.py @@ -7,6 +7,7 @@ class Item(Enum): ITEM_GREAT_BALL = 2 ITEM_ULTRA_BALL = 3 ITEM_MASTER_BALL = 4 + ITEM_PREMIER_BALL = 5 ITEM_POTION = 101 ITEM_SUPER_POTION = 102 ITEM_HYPER_POTION = 103 @@ -27,8 +28,22 @@ class Item(Enum): ITEM_NANAB_BERRY = 703 ITEM_WEPAR_BERRY = 704 ITEM_PINAP_BERRY = 705 + ITEM_GOLDEN_RAZZ_BERRY = 706 + ITEM_GOLDEN_NANAB_BERRY = 707 + ITEM_GOLDEN_PINAP_BERRY = 708 ITEM_SPECIAL_CAMERA = 801 ITEM_INCUBATOR_BASIC_UNLIMITED = 901 ITEM_INCUBATOR_BASIC = 902 ITEM_POKEMON_STORAGE_UPGRADE = 1001 ITEM_ITEM_STORAGE_UPGRADE = 1002 + ITEM_SUN_STONE = 1101 + ITEM_KINGS_ROCK = 1102 + ITEM_METAL_COAT = 1103 + ITEM_DRAGON_SCALE = 1104 + ITEM_UP_GRADE = 1105 + ITEM_MOVE_REROLL_FAST_ATTACK = 1201 + ITEM_MOVE_REROLL_SPECIAL_ATTACK = 1202 + ITEM_RARE_CANDY = 1301 + ITEM_FREE_RAID_TICKET = 1401 + ITEM_PAID_RAID_TICKET = 1402 + ITEM_LEGENDARY_RAID_TICKET = 1403 \ No newline at end of file diff --git a/pokemongo_bot/walkers/step_walker.py b/pokemongo_bot/walkers/step_walker.py index b558507011..8181a2fcfd 100644 --- a/pokemongo_bot/walkers/step_walker.py +++ b/pokemongo_bot/walkers/step_walker.py @@ -34,6 +34,9 @@ def step(self, speed=None): if speed is None: speed = uniform(self.bot.config.walk_min, self.bot.config.walk_max) + elif speed == self.bot.config.walk_max: + # Keep it more Human like... + speed = uniform(speed - 0.5, speed + 0.5) origin_lat, origin_lng, origin_alt = self.bot.position