From f1754e440161e52b62706e2acf8ce8b81a2bbb75 Mon Sep 17 00:00:00 2001 From: henrimeep Date: Wed, 14 Sep 2016 15:13:39 -0300 Subject: [PATCH 1/4] Fixed that ugly ancient initial logs format. Added a global function to retrieve a pokemon's ID by its name (inventory.py). The old snipping system was removed/deprecated and a new one will replace it. This was due to the fact that snipping was first introduced to use with the PokemonGo-Map project, but things have changed and it now offers a lot more flexibility to use third-party sources. All the documentation have been updated, including the example config file. --- README.md | 1 + configs/config.json.map.example | 40 +- docs/configuration_files.md | 76 ++- pokecli.py | 5 +- pokemongo_bot/__init__.py | 41 +- pokemongo_bot/cell_workers/__init__.py | 2 +- .../cell_workers/move_to_map_pokemon.py | 512 ------------------ pokemongo_bot/cell_workers/sniper.py | 353 ++++++++++++ .../event_handlers/logging_handler.py | 7 +- pokemongo_bot/inventory.py | 9 + pokemongo_bot/tree_config_builder.py | 13 +- 11 files changed, 434 insertions(+), 625 deletions(-) delete mode 100644 pokemongo_bot/cell_workers/move_to_map_pokemon.py create mode 100644 pokemongo_bot/cell_workers/sniper.py diff --git a/README.md b/README.md index fa1d93d..c5e642a 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,7 @@ If you do not want any data to be gathered, you can turn off this feature by set * Reaver01 * rarshonsky * earthchie + * YvesHenri * haykuro * 05-032 * sinistance diff --git a/configs/config.json.map.example b/configs/config.json.map.example index 4d52e7e..7f42c71 100644 --- a/configs/config.json.map.example +++ b/configs/config.json.map.example @@ -195,30 +195,24 @@ } }, { - "type": "MoveToMapPokemon", - "config": { + "type": "Sniper", + "config": { "enabled": true, - "address": "http://localhost:5000", - "//NOTE: Change the max_sniping_distance to adjust the max sniping range (m)": {}, - "max_sniping_distance": 10000, - "//NOTE: Change the max_walking_distance to adjust the max walking range when snipe is off (m)": {}, - "max_walking_distance": 500, - "min_ball": 50, - "prioritize_vips": true, - "snipe": true, - "snipe_high_prio_only": true, - "snipe_high_prio_threshold": 400, - "update_map": true, - "mode": "priority", - "map_path": "raw_data", - "walker": "StepWalker", - "max_extra_dist_fort": 10, - "skip_rounds": 5, - "update_map_min_distance_meters": 500, - "update_map_min_time_sec": 120, - "snipe_sleep_sec": 2, - "snipe_max_in_chain": 2, - "debug": false, + "mode": "social", + "url": "", + "order_by": [ "missing", "vip", "threshold" ], + "max_consecutive_catches": 1, + "min_balls_to_teleport_and_catch": 10, + "min_iv_to_ignore_catch_list": 100, + "mappings": { + "latitude": "latitude", + "longitude": "longitude", + "pokemon_iv": "iv", + "pokemon_id": "id", + "pokemon_name": "name", + "encounter_id": "encounter_id", + "spawnpoint_id": "spawn_point_id" + }, "catch": { "==========Legendaries==========": 0, "Aerodactyl": 1000, diff --git a/docs/configuration_files.md b/docs/configuration_files.md index 3c81355..89c9876 100644 --- a/docs/configuration_files.md +++ b/docs/configuration_files.md @@ -647,42 +647,36 @@ If you want to make your bot behave as it did prior to the catch_simulation upda } ``` -## Sniping _(MoveToLocation)_ +## Sniping _(Sniper)_ [[back to top](#table-of-contents)] ### Description [[back to top](#table-of-contents)] -This task will fetch current pokemon spawns from /raw_data of an PokemonGo-Map instance. For information on how to properly setup PokemonGo-Map have a look at the Github page of the project [here](https://github.com/PokemonGoMap/PokemonGo-Map). There is an example config in `config/config.json.map.example` +This task will fetch pokemon informations from either a custom url or from the social feature. You can also use the old PokemonGo-Map project. For information on how to properly setup PokemonGo-Map have a look at the Github page of the project [here](https://github.com/PokemonGoMap/PokemonGo-Map). You can also use [this](https://github.com/YvesHenri/PogoLocationFeeder), which is an adapted version of the application that NecroBot used to snipe. There is an example config in `config/config.json.map.example`. ### Options [[back to top](#table-of-contents)] -* `Address` - Address of the webserver of PokemonGo-Map. ex: `http://localhost:5000` -* `Mode` - Which mode to run sniping on - - `distance` - Will move to the nearest pokemon - - `priority` - Will move to the pokemon with the highest priority assigned (tie breaking by distance) -* `prioritize_vips` - Will prioritize vips in distance and priority mode above all normal pokemon if set to true -* `min_time` - Minimum time the pokemon has to be available before despawn -* `min_ball` - Minimum amount of balls required to run task -* `max_sniping_distance` - Maximum distance the pokemon is allowed to be caught when sniping. (m) -* `max_walking_distance` - Maximum distance the pokemon is allowed to be caught when sniping is turned off. (m) -* `snipe`: +* `url` - Address that returns a JSON with pokemons information. For the PokemonGo-Map, use `http://localhost:5000/raw_data`. This only needs to be specified if mode is `url`. +* `mode` - The mode on which the sniper will fetch the informations. (default: social) + - `social` - Information will come from the social network. Make sure to enable it (enable_social)! + - `url` - Information will come from the given url. +* `max_consecutive_catches` - Number of catch attempts before terminating the task. (default: 1) +* `min_balls_to_teleport_and_catch` - Minimum amount of balls required to run task. (default: 10) +* `min_iv_to_ignore_catch_list` - This will skip the catch list if the value is greater than the target's IV. This only works if JSON response contains an IV value. (default: 100) +* `order_by` - - `True` - Will teleport to target pokemon, encounter it, teleport back then catch it - `False` - Will walk normally to the pokemon -* `update_map` - disable/enable if the map location should be automatically updated to the bots current location -* `catch` - A dictionary of pokemon to catch with an assigned priority (higher => better) -* `snipe_high_prio_only` - Whether to snipe pokemon above a certain threshold. -* `snipe_high_prio_threshold` - The threshold number corresponding with the `catch` dictionary. -* - Any pokemon above this threshold value will be caught by teleporting to its location, and getting back to original location if `snipe` is `True`. -* - Any pokemon under this threshold value will make the bot walk to the Pokemon target whether `snipe` is `True` or `False`. -* `max_extra_dist_fort` : Percentage of extra distance allowed to move to a fort on the way to the targeted Pokemon -* `debug` : Output additional debugging information -* `skip_rounds` : Try to snipe every X rounds -* `update_map_min_distance_meters` : Update map if more than X meters away -* `update_map_min_time_sec` : Update map if older than X seconds -* `snipe_sleep_sec` : Sleep for X seconds after snipes -* `snipe_max_in_chain` : Maximum snipes in chain +* `mappings` - The values below should map each of the JSON response params. For example: different urls will provide different JSON response formats. Map bellow their corresponding values: + - `latitude` - The JSON param that corresponds to the latitude. It will work if a single param is used for both `latitude` and `longitude`, eg.: "coords": "1.2345, 6.7890" (default: latitude) + - `longitude` - The JSON param that corresponds to the longitude. It will work if a single param is used for both `latitude` and `longitude`, eg.: "coords": "1.2345, 6.7890" (default: longitude) + - `pokemon_iv` - The JSON param that corresponds to the pokemon IV. Only certain sources provide this info. NOTE: social does not provide this info! (default: iv) + - `pokemon_id` - The JSON param that corresponds to the pokemon ID. (default: id) + - `pokemon_name` - The JSON param that corresponds to the pokemon name. (default: name) + - `encounter_id` - The JSON param that corresponds to encounter ID. This value is very unlikely to be provided by third-party urls. However, it is safely updated internally. (default: encounter_id) + - `spawnpoint_id` - The JSON param that corresponds to spawnpoint ID. This value is very unlikely to be provided by third-party urls. However, it is safely updated internally. (default: spawn_point_id) +* `catch` - A dictionary of pokemon to catch with an assigned priority (higher => better). #### Example [[back to top](#table-of-contents)] @@ -691,22 +685,24 @@ This task will fetch current pokemon spawns from /raw_data of an PokemonGo-Map i { \\ ... { - "type": "MoveToMapPokemon", + "type": "Sniper", "config": { - "address": "http://localhost:5000", - "//NOTE: Change the max_sniping_distance to adjust the max sniping range (m)": {}, - "max_sniping distance": 10000, - "//NOTE: Change the max_walking_distance to adjust the max walking range when snipe is off (m)": {}, - "max__walking_distance": 500, - "min_time": 60, - "min_ball": 50, - "prioritize_vips": true, - "snipe": true, - "snipe_high_prio_only": true, - "snipe_high_prio_threshold": 400, - "update_map": true, - "mode": "priority", - "max_extra_dist_fort": 10, + "enabled": true, + "mode": "social", + "url": "", + "order_by": [ "missing", "iv", "threshold" ], + "max_consecutive_catches": 1, + "min_balls_to_teleport_and_catch": 10, + "min_iv_to_ignore_catch_list": 100, + "mappings": { + "latitude": "latitude", + "longitude": "longitude", + "pokemon_iv": "iv", + "pokemon_id": "id", + "pokemon_name": "name", + "encounter_id": "encounter_id", + "spawnpoint_id": "spawn_point_id" + }, "catch": { "Aerodactyl": 1000, "Ditto": 900, diff --git a/pokecli.py b/pokecli.py index 98d9239..6a181f5 100644 --- a/pokecli.py +++ b/pokecli.py @@ -75,13 +75,10 @@ print e pass -logging.basicConfig( - level=logging.INFO, - format='%(asctime)s [%(name)10s] [%(levelname)s] %(message)s') +logging.basicConfig(level=logging.INFO, format='[%(asctime)s] %(message)s', datefmt='%Y-%m-%d %H:%M:%S') logger = logging.getLogger('cli') logger.setLevel(logging.INFO) - class SIGINTRecieved(Exception): pass diff --git a/pokemongo_bot/__init__.py b/pokemongo_bot/__init__.py index b6c4211..10f8d3d 100644 --- a/pokemongo_bot/__init__.py +++ b/pokemongo_bot/__init__.py @@ -607,42 +607,6 @@ def _register_events(self): parameters=('old_name',) ) - # Move To map pokemon - self.event_manager.register_event( - 'move_to_map_pokemon_fail', - parameters=('message',) - ) - self.event_manager.register_event( - 'move_to_map_pokemon_updated_map', - parameters=('lat', 'lon') - ) - self.event_manager.register_event( - 'move_to_map_pokemon_teleport_to', - parameters=('poke_name', 'poke_dist', 'poke_lat', 'poke_lon', - 'disappears_in') - ) - self.event_manager.register_event( - 'move_to_map_pokemon_encounter', - parameters=('poke_name', 'poke_dist', 'poke_lat', 'poke_lon', - 'disappears_in') - ) - self.event_manager.register_event( - 'move_to_map_pokemon_move_towards', - parameters=('poke_name', 'poke_dist', 'poke_lat', 'poke_lon', - 'disappears_in') - ) - self.event_manager.register_event( - 'move_to_map_pokemon_teleport_back', - parameters=('last_lat', 'last_lon') - ) - self.event_manager.register_event( - 'moving_to_pokemon_throught_fort', - parameters=('fort_name', 'distance','poke_name','poke_dist') - ) - self.event_manager.register_event( - 'move_to_map_pokemon', - parameters=('message') - ) # cached recent_forts self.event_manager.register_event('loaded_cached_forts') self.event_manager.register_event('cached_fort') @@ -682,6 +646,11 @@ def _register_events(self): parameters=('type', 'incense_count') ) + # Sniper + self.event_manager.register_event('sniper_log', parameters=('message', 'message')) + self.event_manager.register_event('sniper_error', parameters=('message', 'message')) + self.event_manager.register_event('sniper_teleporting', parameters=('latitude', 'longitude', 'name')) + def tick(self): self.health_record.heartbeat() self.cell = self.get_meta_cell() diff --git a/pokemongo_bot/cell_workers/__init__.py b/pokemongo_bot/cell_workers/__init__.py index 43a769d..6b7cf44 100644 --- a/pokemongo_bot/cell_workers/__init__.py +++ b/pokemongo_bot/cell_workers/__init__.py @@ -3,7 +3,7 @@ from evolve_pokemon import EvolvePokemon from incubate_eggs import IncubateEggs from move_to_fort import MoveToFort -from move_to_map_pokemon import MoveToMapPokemon +from sniper import Sniper from nickname_pokemon import NicknamePokemon from pokemon_catch_worker import PokemonCatchWorker from pokemon_hunter import PokemonHunter diff --git a/pokemongo_bot/cell_workers/move_to_map_pokemon.py b/pokemongo_bot/cell_workers/move_to_map_pokemon.py deleted file mode 100644 index 3ed8acf..0000000 --- a/pokemongo_bot/cell_workers/move_to_map_pokemon.py +++ /dev/null @@ -1,512 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Moves a trainer to a Pokemon. - -Events: - move_to_map_pokemon - When a generic message is logged - Returns: - message: Log message. - - move_to_map_pokemon_fail - When the worker fails. - Returns: - message: Failure message. - - move_to_map_pokemon_updated_map - When worker updates the PokemonGo-Map. - Returns: - lat: Latitude - lon: Longitude - - move_to_map_pokemon_teleport_to - When trainer is teleported to a Pokemon. - Returns: - poke_name: Pokemon's name - poke_dist: Distance from the trainer - poke_lat: Latitude of the Pokemon - poke_lon: Longitude of the Pokemon - disappears_in: Number of seconds before the Pokemon disappears - - move_to_map_pokemon_encounter - When a trainer encounters a Pokemon by teleporting or walking. - Returns: - poke_name: Pokemon's name - poke_dist: Distance from the trainer - poke_lat: Latitude of the Pokemon - poke_lon: Longitude of the Pokemon - disappears_in: Number of seconds before the Pokemon disappears - - move_to_map_pokemon_move_towards - When a trainer moves toward a Pokemon. - Returns: - poke_name: Pokemon's name - poke_dist: Distance from the trainer - poke_lat: Latitude of the Pokemon - poke_lon: Longitude of the Pokemon - disappears_in: Number of seconds before the Pokemon disappears - - move_to_map_pokemon_teleport_back - When a trainer teleports back to thier previous location. - Returns: - last_lat: Trainer's last known latitude - last_lon: Trainer's last known longitude - -""" - -from __future__ import unicode_literals - -import os -import time -import json -import requests - -from pokemongo_bot import inventory -from pokemongo_bot.base_dir import _base_dir -from pokemongo_bot.cell_workers.utils import distance, format_dist, format_time, fort_details -from pokemongo_bot.walkers.walker_factory import walker_factory -from pokemongo_bot.worker_result import WorkerResult -from pokemongo_bot.base_task import BaseTask -from pokemongo_bot.cell_workers.pokemon_catch_worker import PokemonCatchWorker -from random import uniform -from pokemongo_bot.constants import Constants - -ULTRABALL_ID = 3 -GREATBALL_ID = 2 -POKEBALL_ID = 1 - - -class MoveToMapPokemon(BaseTask): - """Task for moving a trainer to a Pokemon.""" - SUPPORTED_TASK_API_VERSION = 1 - - def initialize(self): - self.last_map_update = 0 - self.pokemon_data = self.bot.pokemon_list - self.unit = self.bot.config.distance_unit - self.cache = [] - self.min_ball = self.config.get('min_ball', 1) - self.map_path = self.config.get('map_path', 'raw_data') - self.walker = self.config.get('walker', 'StepWalker') - self.snip_enabled = self.config.get('snipe', False) - self.snipe_high_prio_only = self.config.get('snipe_high_prio_only', False) - self.snipe_high_prio_threshold = self.config.get('snipe_high_prio_threshold', 400) - self.by_pass_times = 0 - - data_file = os.path.join(_base_dir, 'map-caught-{}.json'.format(self.bot.config.username)) - if os.path.isfile(data_file): - self.cache = json.load( - open(data_file) - ) - self.alt = uniform(self.bot.config.alt_min, self.bot.config.alt_max) - self.debug = self.config.get('debug', False) - - def pokemons_parser(self, pokemon_list): - pokemons = [] - if not pokemon_list: - return pokemons - - now = int(time.time()) - - for pokemon in pokemon_list: - try: - disappear = int(pokemon.get('expiration_timestamp_ms', 0) / 1000) or int(pokemon.get('disappear_time', 0) / 1000) - - pokemon['encounter_id'] = pokemon.get('encounter_id', '') - pokemon['spawn_point_id'] = pokemon.get('spawn_point_id', '') or pokemon.get('spawnpoint_id', '') - pokemon['iv'] = pokemon.get('iv', 0) - pokemon['disappear_time'] = disappear - pokemon['name'] = self.pokemon_data[pokemon['pokemon_id'] - 1]['Name'] - pokemon['is_vip'] = pokemon['name'] in self.bot.config.vips - except TypeError: - continue - except KeyError: - continue - if now > pokemon['disappear_time']: - continue - - if pokemon['name'] not in self.config['catch'] and not pokemon['is_vip']: - if self.debug: - self._emit_failure("Not catching {}".format(pokemon['name'])) - continue - - if self.is_inspected(pokemon): - if self.debug: - self._emit_log('Skipped {} because it was already catch or does not exist'.format(pokemon['name'])) - continue - - pokemon['priority'] = self.config['catch'].get(pokemon['name'], 0) - pokemon['dist'] = distance( - self.bot.position[0], - self.bot.position[1], - pokemon['latitude'], - pokemon['longitude'] - ) - - # If distance to pokemon greater than the max_sniping_distance, then ignore regardless of "snipe" setting - if pokemon['dist'] > self.config.get('max_sniping_distance', 10000): - continue - - # If distance bigger than walking distance, ignore if sniping is not active - if pokemon['dist'] > self.config.get('max_walking_distance', 1000) and not self.snip_enabled: - continue - - # if pokemon not reachable with mean walking speed (by config) - mean_walk_speed = (self.bot.config.walk_max + self.bot.config.walk_min) / 2 - if pokemon['dist'] > ((pokemon['disappear_time'] - now) * mean_walk_speed) and not self.snip_enabled: - continue - pokemons.append(pokemon) - - return pokemons - - def get_pokemon_from_social(self): - if not hasattr(self.bot, 'mqtt_pokemon_list') or not self.bot.mqtt_pokemon_list: - return [] - - tmp_pokemon_list, self.bot.mqtt_pokemon_list = self.bot.mqtt_pokemon_list, [] - return self.pokemons_parser(tmp_pokemon_list) - - def get_pokemon_from_url(self): - try: - request = requests.get(self.config['address']) - response = request.json() - except requests.exceptions.ConnectionError: - self._emit_failure('Could not get data from {}'.format(self.config['address'])) - return [] - except ValueError: - self._emit_failure('JSON format is not valid') - return [] - - return self.pokemons_parser(response.get('pokemons', [])) - - # TODO: refactor - def is_inspected(self, pokemon): - for caught_pokemon in self.cache: - # Since IDs might be invalid (null/blank) by this time, compare by approximate location - # TODO: make a better comparision - same_latitude = "{0:.4f}".format(pokemon['latitude']) == "{0:.4f}".format(caught_pokemon['latitude']) - same_longitude = "{0:.4f}".format(pokemon['longitude']) == "{0:.4f}".format(caught_pokemon['longitude']) - if same_latitude and same_longitude: - return True - - return False - - # Stores a target so that - # TODO: refactor - def inspect(self, pokemon): - # Make sure it was not caught! - for caught_pokemon in self.cache: - same_latitude = "{0:.4f}".format(pokemon['latitude']) == "{0:.4f}".format(caught_pokemon['latitude']) - same_longitude = "{0:.4f}".format(pokemon['longitude']) == "{0:.4f}".format(caught_pokemon['longitude']) - if same_latitude and same_longitude: - return - - if len(self.cache) >= 200: - self.cache.pop(0) - - self.cache.append(pokemon) - - def snipe(self, pokemon): - # Backup position before anything - last_position = self.bot.position[0:2] - - # Teleport, so that we can see nearby stuff - # self.bot.heartbeat() was moved to thread, if you do want to call it, you need sleep 10s. - self.bot.hb_locked = True - self._teleport_to(pokemon) - - # Simulate kind of a lag after teleporting/moving to a long distance - time.sleep(2) - - # If social is enabled, trust it - exists = self.bot.config.enable_social - verify = not self.bot.config.enable_social - - # If social is disabled, we will have to make sure the target still exists - if verify: - nearby_pokemons = [] - nearby_stuff = self.bot.get_meta_cell() - - # Sleep some time, so that we have accurate results (successfull cell data request) - time.sleep(2) - - # Retrieve nearby pokemons for validation - if 'wild_pokemons' in nearby_stuff: - nearby_pokemons.extend(nearby_stuff['wild_pokemons']) - if 'catchable_pokemons' in nearby_stuff: - nearby_pokemons.extend(nearby_stuff['catchable_pokemons']) - - # Make sure the target still/really exists (TODO: validate expiration) - for nearby_pokemon in nearby_pokemons: - is_wild = 'pokemon_data' in nearby_pokemon - nearby_pokemon_id = nearby_pokemon['pokemon_data']['pokemon_id'] if is_wild else nearby_pokemon['pokemon_id'] - - if nearby_pokemon_id == pokemon['pokemon_id']: - exists = True - - # Also, if the IDs arent valid, update them! - if not pokemon['encounter_id'] or not pokemon['spawnpoint_id']: - pokemon['encounter_id'] = nearby_pokemon['encounter_id'] - pokemon['spawn_point_id'] = nearby_pokemon['spawn_point_id'] - pokemon['disappear_time'] = nearby_pokemon['last_modified_timestamp_ms'] if is_wild else nearby_pokemon['expiration_timestamp_ms'] - break - - # If target exists, catch it, otherwise ignore - if exists: - self._encountered(pokemon) - catch_worker = PokemonCatchWorker(pokemon, self.bot, self.config) - api_encounter_response = catch_worker.create_encounter_api_call() - time.sleep(self.config.get('snipe_sleep_sec', 2)) - self._teleport_back(last_position) - self.bot.api.set_position(last_position[0], last_position[1], self.alt, False) - time.sleep(self.config.get('snipe_sleep_sec', 2)) - catch_worker.work(api_encounter_response) - else: - self._emit_failure('{} doesnt exist anymore. Skipping...'.format(pokemon['name'])) - time.sleep(self.config.get('snipe_sleep_sec', 2)) - self._teleport_back(last_position) - self.bot.api.set_position(last_position[0], last_position[1], self.alt, False) - time.sleep(self.config.get('snipe_sleep_sec', 2)) - - self.inspect(pokemon) - self.bot.hb_locked = False - return WorkerResult.SUCCESS - - def dump_caught_pokemon(self): - user_data_map_caught = os.path.join(_base_dir, 'data', 'map-caught-{}.json'.format(self.bot.config.username)) - with open(user_data_map_caught, 'w') as outfile: - json.dump(self.cache, outfile) - - def work(self): - # check for pokeballs (excluding masterball) - pokeballs_quantity = inventory.items().get(POKEBALL_ID).count - superballs_quantity = inventory.items().get(GREATBALL_ID).count - ultraballs_quantity = inventory.items().get(ULTRABALL_ID).count - - # Validate the balls quantity - if (pokeballs_quantity + superballs_quantity + ultraballs_quantity) < self.min_ball: - if self.debug: - self._emit_log("Not enough balls to start sniping (have {}, {} needed)".format( - pokeballs_quantity + superballs_quantity + ultraballs_quantity, self.min_ball)) - return WorkerResult.SUCCESS - - # Retrieve pokemos - self.dump_caught_pokemon() - if self.bot.config.enable_social: - if self.snip_enabled: - self.by_pass_times += 1 - if self.by_pass_times < self.config.get('skip_rounds', 30): - if self.debug: - self._emit_log("Skipping pass {}".format(self.by_pass_times)) - return WorkerResult.SUCCESS - self.by_pass_times = 0 - pokemon_list = self.get_pokemon_from_social() - else: - pokemon_list = self.get_pokemon_from_url() - - pokemon_list.sort(key=lambda x: x['dist']) - if self.config['mode'] == 'priority': - pokemon_list.sort(key=lambda x: x['priority'], reverse=True) - if self.config['prioritize_vips']: - pokemon_list.sort(key=lambda x: x['is_vip'], reverse=True) - - if not len(pokemon_list): - if self.debug: - self._emit_log("No pokemons in list to snipe") - return WorkerResult.SUCCESS - - pokemon = pokemon_list[0] - if self.debug: - self._emit_log('How many pokemon in list: {}'.format(len(pokemon_list))) - - if self.snip_enabled: - if self.snipe_high_prio_only: - count = 0 - for pokemon in pokemon_list: - if self.snipe_high_prio_threshold < pokemon['priority']: - self.snipe(pokemon) - count += 1 - if count >= self.config.get('snipe_max_in_chain', 2): - return WorkerResult.SUCCESS - if count is not 1: - time.sleep(self.config.get('snipe_sleep_sec', 2) * 5) - else: - if self.debug: - self._emit_log('this pokemon is not good enough to snipe {}'.format(pokemon)) - return WorkerResult.SUCCESS - else: - return self.snipe(pokemon) - - # check for pokeballs (excluding masterball) - # checking again as we may have lost some if we sniped - pokeballs_quantity = inventory.items().get(POKEBALL_ID).count - superballs_quantity = inventory.items().get(GREATBALL_ID).count - ultraballs_quantity = inventory.items().get(ULTRABALL_ID).count - - if pokeballs_quantity + superballs_quantity + ultraballs_quantity < self.min_ball: - return WorkerResult.SUCCESS - - nearest_fort = self.get_nearest_fort_on_the_way(pokemon) - - if pokemon['is_vip'] or nearest_fort is None: - # lock catching(with pokemon_id specified) while moving to vip pokemon or no fort around - self.bot.capture_locked = pokemon['pokemon_id'] - step_walker = self._move_to(pokemon) - if not step_walker.step(): - - if pokemon['dist'] < Constants.MAX_DISTANCE_POKEMON_IS_REACHABLE: - self._encountered(pokemon) - self.bot.capture_locked = False # unlock catch_worker - self.inspect(pokemon) - return WorkerResult.SUCCESS - else: - return WorkerResult.RUNNING - - else: - step_walker = self._move_to_pokemon_througt_fort(nearest_fort, pokemon) - if not step_walker or not step_walker.step(): - return WorkerResult.RUNNING - - def _emit_failure(self, msg): - self.emit_event( - 'move_to_map_pokemon_fail', - formatted='Failure! {message}', - data={'message': msg} - ) - - def _emit_log(self, msg): - self.emit_event( - 'move_to_map_pokemon', - formatted='{message}', - data={'message': msg} - ) - - def _pokemon_event_data(self, pokemon): - """Generates parameters used for the Bot's event manager. - - Args: - pokemon: Pokemon object - - Returns: - Dictionary with Pokemon's info. - """ - now = int(time.time()) - return { - 'poke_name': pokemon['name'], - 'poke_dist': (format_dist(pokemon['dist'], self.unit)), - 'poke_lat': pokemon['latitude'], - 'poke_lon': pokemon['longitude'], - 'disappears_in': (format_time(pokemon['disappear_time'] - now)) - } - - def _teleport_to(self, pokemon): - self.emit_event( - 'move_to_map_pokemon_teleport_to', - formatted='Teleporting to {poke_name}. ({poke_dist})', - data=self._pokemon_event_data(pokemon) - ) - self.bot.api.set_position(pokemon['latitude'], pokemon['longitude'], self.alt, True) - - def _encountered(self, pokemon): - self.emit_event( - 'move_to_map_pokemon_encounter', - formatted='Encountered Pokemon: {poke_name}', - data=self._pokemon_event_data(pokemon) - ) - - def _teleport_back(self, last_position): - self.emit_event( - 'move_to_map_pokemon_teleport_back', - formatted='Teleporting back to previous location ({last_lat}, {last_lon})...', - data={'last_lat': last_position[0], 'last_lon': last_position[1]} - ) - - def _move_to(self, pokemon): - """Moves trainer towards a Pokemon. - - Args: - pokemon: Pokemon to move to. - - Returns: - Walker - """ - self.emit_event( - 'move_to_map_pokemon_move_towards', - formatted=('Moving towards {poke_name}, {poke_dist}, left (' - '{disappears_in})'), - data=self._pokemon_event_data(pokemon) - ) - return walker_factory(self.walker, self.bot, pokemon['latitude'], pokemon['longitude']) - - def _move_to_pokemon_througt_fort(self, fort, pokemon): - """Moves trainer towards a fort before a Pokemon. - - Args: - fort - - Returns: - StepWalker - """ - - nearest_fort = fort - - lat = nearest_fort['latitude'] - lng = nearest_fort['longitude'] - fortID = nearest_fort['id'] - details = fort_details(self.bot, fortID, lat, lng) - fort_name = details.get('name', 'Unknown') - - unit = self.bot.config.distance_unit # Unit to use when printing formatted distance - - dist = distance( - self.bot.position[0], - self.bot.position[1], - lat, - lng - ) - - if dist > Constants.MAX_DISTANCE_FORT_IS_REACHABLE: - pokemon_throught_fort_event_data = { - 'fort_name': u"{}".format(fort_name), - 'distance': format_dist(dist, unit), - 'poke_name': pokemon['name'], - 'poke_dist': (format_dist(pokemon['dist'], self.unit)) - } - - self.emit_event( - 'moving_to_pokemon_throught_fort', - formatted="Moving towards {poke_name} - {poke_dist} through pokestop {fort_name} - {distance}", - data=pokemon_throught_fort_event_data - ) - else: - self.emit_event( - 'arrived_at_fort', - formatted='Arrived at fort.' - ) - - return walker_factory(self.walker, self.bot, lat, lng) - - def get_nearest_fort_on_the_way(self, pokemon): - 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']) - dist_fort_to_pokemon = distance(pokemon['latitude'], pokemon['longitude'], 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], pokemon['latitude'], - pokemon['longitude']) - 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/cell_workers/sniper.py b/pokemongo_bot/cell_workers/sniper.py new file mode 100644 index 0000000..3146f9f --- /dev/null +++ b/pokemongo_bot/cell_workers/sniper.py @@ -0,0 +1,353 @@ +from __future__ import unicode_literals + +import os +import time +import json +import requests + +from random import uniform +from pokemongo_bot import inventory +from pokemongo_bot.inventory import Pokemon, Pokemons +from pokemongo_bot.base_task import BaseTask +from pokemongo_bot.base_dir import _base_dir +from pokemongo_bot.constants import Constants +from pokemongo_bot.worker_result import WorkerResult +from pokemongo_bot.cell_workers.utils import format_dist, format_time, fort_details +from pokemongo_bot.walkers.walker_factory import walker_factory +from pokemongo_bot.cell_workers.pokemon_catch_worker import PokemonCatchWorker + +POKEBALL_ID = 1 +GREATBALL_ID = 2 +ULTRABALL_ID = 3 + +class OrderMode(): + IV = 'iv' + VIP = 'vip' + MISSING = 'missing' + THRESHOLD = 'threshold' + VALUES = [IV, VIP, MISSING, THRESHOLD] + DEFAULT = [MISSING, IV, THRESHOLD] + +class SnipingMode(): + URL = 'url' + SOCIAL = 'social' + VALUES = [URL, SOCIAL] + DEFAULT = SOCIAL + +class ResponseMapper(): + ID = 'id' + IV = 'iv' + NAME = 'name' + LATITUDE = 'latitude' + LONGITUDE = 'longitude' + ENCOUNTER = 'encounter' + SPAWNPOINT = 'spawnpoint' + +class Sniper(BaseTask): + SUPPORTED_TASK_API_VERSION = 1 + MIN_SECONDS_ALLOWED_FOR_CELL_CHECK = 10 + CACHE_LIST_MAX_SIZE = 200 + + # Constructor (invokes base constructor, which invokes initialize()) + def __init__(self, bot, config): + super(Sniper, self).__init__(bot, config) + + # Constructor dispatcher + def initialize(self): + self.cached_pokemons = [] + self.is_mappings_good = False + self.is_social = self.bot.config.enable_social + self.is_debug = self.config.get('debug', False) + self.url = self.config.get('url', '') + self.inventory = inventory.items() + self.pokedex = inventory.pokedex() + self.last_cell_check_time = time.time() + self.catch_list = self.config.get('catch', {}) + self.mode = self.config.get('mode', SnipingMode.DEFAULT).lower() + self.order_by = [order.lower() for order in self.config.get('order_by', OrderMode.DEFAULT)] + self.altitude = uniform(self.bot.config.alt_min, self.bot.config.alt_max) + self.max_consecutive_catches = self.config.get('max_consecutive_catches', 1) + self.min_balls_to_teleport_and_catch = self.config.get('min_balls_to_teleport_and_catch', 10) + self.min_iv_to_ignore_catch_list = self.config.get('min_iv_to_ignore_catch_list', 100) + self.optional_mappings = ['iv', 'id', 'name', 'encounter', 'spawnpoint'] + self.mappings = { + ResponseMapper.IV: self.config.get('mappings', {}).get('pokemon_iv', 'iv'), + ResponseMapper.ID: self.config.get('mappings', {}).get('pokemon_id', 'id'), + ResponseMapper.NAME: self.config.get('mappings', {}).get('pokemon_name', 'name'), + ResponseMapper.LATITUDE: self.config.get('mappings', {}).get('latitude', 'latitude'), + ResponseMapper.LONGITUDE: self.config.get('mappings', {}).get('longitude', 'longitude'), + ResponseMapper.ENCOUNTER: self.config.get('mappings', {}).get('encounter_id', 'encounter_id'), + ResponseMapper.SPAWNPOINT: self.config.get('mappings', {}).get('spawnpoint_id', 'spawn_point_id') + } + + # Validate ordering + for ordering in self.order_by: + if ordering not in OrderMode.VALUES: + raise Exception('Unrecognized ordering: {}'.format(ordering)) + + # Validate mode + if self.mode not in SnipingMode.VALUES: + raise Exception('Unrecognized mode: {}'.format(self.mode)) + else: + # Validate url if mode is url + if self.mode == SnipingMode.URL and not self.url: + raise Exception('You chose to use URL snipping but you did not specify which (empty)') + + # Validate social flag if mode is social + if self.mode == SnipingMode.SOCIAL and not self.is_social: + raise Exception('You chose to use SOCIAL snipping but you did not enable it (enable_social = false)') + + def is_snipeable(self, pokemon): + pokeballs_count = self.inventory.get(POKEBALL_ID).count + greatballs_count = self.inventory.get(GREATBALL_ID).count + ultraballs_count = self.inventory.get(ULTRABALL_ID).count + all_balls_count = pokeballs_count + greatballs_count + ultraballs_count + + # Skip if already cached + if self._is_cached(pokemon): + self._trace('{} was already caught! Skipping...'.format(pokemon['name'])) + return False + + # Skip if not enought balls + if all_balls_count < self.min_balls_to_teleport_and_catch: + self._trace('Not enought balls left! Skipping...') + return False + + # Skip if not in catch list, not a VIP and/or IV sucks (if any) + if pokemon['name'] not in self.catch_list: + # If its a VIP, ignore the IV check + if not pokemon['vip']: + # If target is not a VIP, check the IV (if any) + if pokemon['iv'] and pokemon['iv'] < self.min_iv_to_ignore_catch_list: + self._trace('{} is not a target, nor a VIP and its IV sucks ({}). Skipping...'.format(pokemon['name'], pokemon['iv'])) + return False + + return True + + def snipe(self, pokemon): + # Apply snipping business rules + if not self.is_snipeable(pokemon): + self._error('{} is not snipeable! Skipping...'.format(pokemon['name'])) + return WorkerResult.SUCCESS + + # Backup position before anything + last_position = self.bot.position[0:2] + + # Teleport, so that we can see nearby stuff + self.bot.hb_locked = True + self._teleport_to(pokemon) + + # If social is enabled, trust it. If encounter and spawnpoint IDs arent valid, verify them! + exists = self.is_social + verify = pokemon.get('encounter_id') and pokemon.get('spawn_point_id') + + # If information verification have to be done, do so + if verify: + seconds_since_last_check = time.time() - self.last_cell_check_time + + # Wait a maximum of MIN_SECONDS_ALLOWED_FOR_CELL_CHECK seconds before requesting nearby cells + if (seconds_since_last_check < self.MIN_SECONDS_ALLOWED_FOR_CELL_CHECK): + time.sleep(self.MIN_SECONDS_ALLOWED_FOR_CELL_CHECK - seconds_since_last_check) + + nearby_pokemons = [] + nearby_stuff = self.bot.get_meta_cell() + self.last_cell_check_time = time.time() + + # Retrieve nearby pokemons for validation + nearby_pokemons.extend(nearby_stuff.get('wild_pokemons', [])) + nearby_pokemons.extend(nearby_stuff.get('catchable_pokemons', [])) + + # Make sure the target really/still exists (nearby_pokemon key names are game-bound!) + for nearby_pokemon in nearby_pokemons: + nearby_pokemon_id = nearby_pokemon.get('pokemon_data', {}).get('pokemon_id') or nearby_pokemon.get('pokemon_id') + + if int(nearby_pokemon_id) == int(pokemon.get('id')): + exists = True + + # Also, if the IDs arent valid, update them (nearby_pokemon key names are game-bound!) + if not pokemon.get('encounter_id') or not pokemon.get('spawn_point_id'): + pokemon['encounter_id'] = nearby_pokemon['encounter_id'] + pokemon['spawn_point_id'] = nearby_pokemon['spawn_point_id'] + break + + # If target exists, catch it, otherwise ignore + if exists: + self._log('Encountered {}!'.format(pokemon['name'])) + self._teleport_back_and_catch(last_position, pokemon) + else: + self._error('{} does not exist anymore. Skipping...'.format(pokemon['name'])) + self._teleport_back(last_position) + + # Save target and unlock heartbeat calls + self._cache(pokemon) + self.bot.hb_locked = False + + return WorkerResult.SUCCESS + + def work(self): + targets = [] + + # Retrieve the targets + if self.mode == SnipingMode.SOCIAL: + targets = self._get_pokemons_from_social() + elif self.mode == SnipingMode.URL: + targets = self._get_pokemons_from_url() + + # Order the targets (descending) + for attr in self.order_by: + targets.sort(key=lambda pokemon: pokemon[attr], reverse=True) + + # Start sniping the first 'max_consecutive_catches' entries + for catch_attempt in range(len(targets)): + if catch_attempt < self.max_consecutive_catches: + self.snipe(targets[catch_attempt]) + + # Wait a bit if were going to snipe again + if self.max_consecutive_catches is not 1: + time.sleep(3) + + return WorkerResult.SUCCESS + + def _validate_mappings(self, dictionary): + invalid_mappings = [] + + # Gather invalid mappings. If one is optional, the correct value can still be retrieved + for key, value in self.mappings.iteritems(): + if key not in self.optional_mappings and value not in dictionary: + invalid_mappings.append(value) + + # If mappings are invalid, this would lead to a wrong formatted pokemon object (blank attributes) + if invalid_mappings: + raise Exception('Invalid mapping values for {} mode: {}'.format(self.mode, ', '.join(invalid_mappings))) + + # Mappings are good. No need to check again + self.is_mappings_good = True + + def _parse_pokemons(self, pokemon_dictionary_list): + pokemons = [] + + # If were parsing for the first time, lets validate the mappings (use first item as a dictionary sample) + if not self.is_mappings_good and pokemon_dictionary_list: + try: + self._validate_mappings(pokemon_dictionary_list[0]) + except Exception as exception: + # If mappings contain errors, log it instead of forwarding the exception + self._error(exception) + self._trace('This is the dictionary: {}'.format(pokemon_dictionary_list[0])) # TODO: Remove + return pokemons + + # Build up the pokemon. Pops are used to destroy random attribute names and keep the known ones! + for pokemon_dictonary in pokemon_dictionary_list: + pokemon_dictonary['iv'] = pokemon_dictonary.pop(self.mappings.get(ResponseMapper.IV), 0) + pokemon_dictonary['id'] = pokemon_dictonary.pop(self.mappings.get(ResponseMapper.ID), None) + pokemon_dictonary['name'] = pokemon_dictonary.pop(self.mappings.get(ResponseMapper.NAME), None) + pokemon_dictonary['encounter_id'] = pokemon_dictonary.pop(self.mappings.get(ResponseMapper.ENCOUNTER), None) + pokemon_dictonary['spawn_point_id'] = pokemon_dictonary.pop(self.mappings.get(ResponseMapper.SPAWNPOINT), None) + + # If ID or name couldnt be retrieved, retrieve ID by name or vice-versa + if not pokemon_dictonary['name'] or not pokemon_dictonary['id']: + if not pokemon_dictonary['name'] and pokemon_dictonary['id']: + pokemon_dictonary['name'] = Pokemons.name_for(pokemon_dictonary.get('id') - 1) + elif not pokemon_dictonary['id'] and pokemon_dictonary['name']: + pokemon_dictonary['id'] = Pokemons.id_for(pokemon_dictonary.get('name')) + else: + raise Exception('Response does not have both pokemon ID and name') + + # If positions are mapped by the same name, then we have to split it + if self.mappings.get(ResponseMapper.LATITUDE) == self.mappings.get(ResponseMapper.LONGITUDE): + position = pokemon_dictonary.get(self.mappings.get(ResponseMapper.LATITUDE)).replace(' ', '').split(',') + pokemon_dictonary['latitude'] = float(position[0]) + pokemon_dictonary['longitude'] = float(position[1]) + else: + pokemon_dictonary['latitude'] = pokemon_dictonary.pop(self.mappings.get('latitude'), .0) + pokemon_dictonary['longitude'] = pokemon_dictonary.pop(self.mappings.get('longitude'), .0) + + # Some other helpful values + pokemon_dictonary['vip'] = pokemon_dictonary.get('name') in self.bot.config.vips + pokemon_dictonary['missing'] = not self.pokedex.captured(pokemon_dictonary.get('id')) + pokemon_dictonary['threshold'] = self.catch_list.get(pokemon_dictonary.get('name'), 0) + + # Check whether this is a valid target + if self.is_snipeable(pokemon_dictonary): + pokemons.append(pokemon_dictonary) + + return pokemons + + def _get_pokemons_from_social(self): + if not hasattr(self.bot, 'mqtt_pokemon_list') or not self.bot.mqtt_pokemon_list: + return [] + + # Backup mqtt list and clean it for the next cycle + mqtt_pokemon_list = self.bot.mqtt_pokemon_list + self.bot.mqtt_pokemon_list = [] + + return self._parse_pokemons(mqtt_pokemon_list) + + def _get_pokemons_from_url(self): + try: + request = requests.get(self.url) + response = request.json() + except requests.exceptions.ConnectionError: + self._error('Could not get data from {}'.format(self.url)) + return [] + except ValueError: + self._error('Could not parse the JSON response. It might be related to a bad url or JSON format') + return [] + + # Use some known/possible response 'keywords' + return self._parse_pokemons(response.get('pokemons') or response.get('results') or []) + + def _is_cached(self, pokemon): + for entry in self.cached_pokemons: + # Since IDs might be invalid (null/blank) by this time, compare by approximate location + same_latitude = "{0:.4f}".format(pokemon['latitude']) == "{0:.4f}".format(entry['latitude']) + same_longitude = "{0:.4f}".format(pokemon['longitude']) == "{0:.4f}".format(entry['longitude']) + + if same_latitude and same_longitude: + return True + + return False + + def _cache(self, pokemon): + # Skip repeated items + if not self._is_cached(pokemon): + # Free space if full and store it + if len(self.cached_pokemons) >= self.CACHE_LIST_MAX_SIZE: + self.cached_pokemons.pop(0) + self.cached_pokemons.append(pokemon) + + def _log(self, message): + self.emit_event('sniper_log', formatted='{message}', data={'message': message}) + + def _error(self, message): + self.emit_event('sniper_error', formatted='{message}', data={'message': message}) + + def _trace(self, message): + if self.is_debug: + self._log(message) + + def _teleport(self, latitude, longitude, altitude): + self.bot.api.set_position(latitude, longitude, altitude, True) + time.sleep(3) + + def _teleport_to(self, pokemon): + self.emit_event( + 'sniper_teleporting', + formatted = 'Teleporting to {name} at [{latitude}; {longitude}]...', + data = { 'name': pokemon['name'], 'latitude': pokemon['latitude'], 'longitude': pokemon['longitude'] } + ) + self._teleport(pokemon['latitude'], pokemon['longitude'], self.altitude) + + def _teleport_back(self, position_array): + self.emit_event( + 'sniper_teleporting', + formatted = 'Teleporting back to ({latitude}; {longitude})...', + data = { 'latitude': position_array[0], 'longitude': position_array[1] } + ) + self._teleport(position_array[0], position_array[1], self.altitude) + + def _teleport_back_and_catch(self, position_array, pokemon): + catch_worker = PokemonCatchWorker(pokemon, self.bot, self.config) + api_encounter_response = catch_worker.create_encounter_api_call() + self._teleport_back(position_array) + catch_worker.work(api_encounter_response) \ No newline at end of file diff --git a/pokemongo_bot/event_handlers/logging_handler.py b/pokemongo_bot/event_handlers/logging_handler.py index 2dff9db..e9af888 100644 --- a/pokemongo_bot/event_handlers/logging_handler.py +++ b/pokemongo_bot/event_handlers/logging_handler.py @@ -76,8 +76,10 @@ class LoggingHandler(EventHandler): 'vip_pokemon': 'red', 'use_incense': 'blue', 'vanish_limit_reached': 'red', - 'move_to_map_pokemon_teleport_to': 'yellow', + 'sniper_teleporting': 'yellow', + 'sniper_error': 'red', + 'sniper_log': 'none', 'arrived_at_cluster': 'none', 'arrived_at_fort': 'none', 'bot_sleep': 'none', @@ -90,9 +92,6 @@ class LoggingHandler(EventHandler): 'location_found': 'none', 'login_started': 'none', 'lured_pokemon_found': 'none', - 'move_to_map_pokemon_move_towards': 'none', - 'move_to_map_pokemon_teleport_back': 'none', - 'move_to_map_pokemon_updated_map': 'none', 'moving_to_fort': 'none', 'moving_to_lured_fort': 'none', 'pokemon_catch_rate': 'none', diff --git a/pokemongo_bot/inventory.py b/pokemongo_bot/inventory.py index 60ad365..1bfff19 100644 --- a/pokemongo_bot/inventory.py +++ b/pokemongo_bot/inventory.py @@ -455,6 +455,15 @@ def data_for(cls, pokemon_id): def name_for(cls, pokemon_id): return cls.data_for(pokemon_id).name + @classmethod + def id_for(cls, pokemon_name): + # TODO: Use a better searching algorithm. This one is O(n) + for data in cls.STATIC_DATA: + if data.name.lower() == pokemon_name.lower(): + return data.id + + raise Exception('Could not find pokemon named {}'.format(pokemon_name)) + @classmethod def first_evolution_id_for(cls, pokemon_id): return cls.data_for(pokemon_id).first_evolution_id diff --git a/pokemongo_bot/tree_config_builder.py b/pokemongo_bot/tree_config_builder.py index 03caeba..48ec651 100644 --- a/pokemongo_bot/tree_config_builder.py +++ b/pokemongo_bot/tree_config_builder.py @@ -31,13 +31,16 @@ def build(self): for task in self.tasks_raw: task_type = task.get('type', None) - if task_type is None: - raise ConfigException('No type found for given task {}'.format(task)) - elif task_type == 'EvolveAll': - raise ConfigException('The EvolveAll task has been renamed to EvolvePokemon') - task_config = task.get('config', {}) + # Validate task name refactors + if type is None: + raise ConfigException('No type found for given task {}'.format(type)) + elif type == 'EvolveAll': + raise ConfigException('The EvolveAll task has been renamed to EvolvePokemon') + elif type == 'MoveToMapPokemon': + raise ConfigException('The MoveToMapPokemon task has been renamed to Sniper') + if task_type in ['CatchVisiblePokemon', 'CatchLuredPokemon']: if deprecated_pokemon_task: continue From 508f2aac3547c791697b5a2f2878283cd094e6df Mon Sep 17 00:00:00 2001 From: henrimeep Date: Wed, 14 Sep 2016 15:33:01 -0300 Subject: [PATCH 2/4] Added missing documentation about the newest snipping. Updated the snipping's default ordering and some minor refactoring --- docs/configuration_files.md | 12 +++++++----- pokemongo_bot/cell_workers/sniper.py | 5 ++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/configuration_files.md b/docs/configuration_files.md index 89c9876..7fde49a 100644 --- a/docs/configuration_files.md +++ b/docs/configuration_files.md @@ -661,13 +661,15 @@ This task will fetch pokemon informations from either a custom url or from the s * `url` - Address that returns a JSON with pokemons information. For the PokemonGo-Map, use `http://localhost:5000/raw_data`. This only needs to be specified if mode is `url`. * `mode` - The mode on which the sniper will fetch the informations. (default: social) - `social` - Information will come from the social network. Make sure to enable it (enable_social)! - - `url` - Information will come from the given url. + - `url` - Information will come from the given url. Make sure to enter a valid URL! * `max_consecutive_catches` - Number of catch attempts before terminating the task. (default: 1) * `min_balls_to_teleport_and_catch` - Minimum amount of balls required to run task. (default: 10) -* `min_iv_to_ignore_catch_list` - This will skip the catch list if the value is greater than the target's IV. This only works if JSON response contains an IV value. (default: 100) -* `order_by` - - - `True` - Will teleport to target pokemon, encounter it, teleport back then catch it - - `False` - Will walk normally to the pokemon +* `min_iv_to_ignore_catch_list` - This will skip the catch list if the value is greater than the target's IV. This currently does not work with `social` mode and only works if the given `url` has this information. (default: 100) +* `order_by` - The order on which you want to snipe. This can be one or multiple of the following values (default: [`missing`, `vip`, `threshold`]): + - `iv` - Order by IV, if any. See `min_iv_to_ignore_catch_list`. + - `vip` - Order by VIP. + - `missing` - Order by the target's pokedex missing status. + - `threshold` - Order by the threshold you have specified in the `catch` list. * `mappings` - The values below should map each of the JSON response params. For example: different urls will provide different JSON response formats. Map bellow their corresponding values: - `latitude` - The JSON param that corresponds to the latitude. It will work if a single param is used for both `latitude` and `longitude`, eg.: "coords": "1.2345, 6.7890" (default: latitude) - `longitude` - The JSON param that corresponds to the longitude. It will work if a single param is used for both `latitude` and `longitude`, eg.: "coords": "1.2345, 6.7890" (default: longitude) diff --git a/pokemongo_bot/cell_workers/sniper.py b/pokemongo_bot/cell_workers/sniper.py index 3146f9f..998dc75 100644 --- a/pokemongo_bot/cell_workers/sniper.py +++ b/pokemongo_bot/cell_workers/sniper.py @@ -26,7 +26,7 @@ class OrderMode(): MISSING = 'missing' THRESHOLD = 'threshold' VALUES = [IV, VIP, MISSING, THRESHOLD] - DEFAULT = [MISSING, IV, THRESHOLD] + DEFAULT = [MISSING, VIP, THRESHOLD] class SnipingMode(): URL = 'url' @@ -69,7 +69,7 @@ def initialize(self): self.max_consecutive_catches = self.config.get('max_consecutive_catches', 1) self.min_balls_to_teleport_and_catch = self.config.get('min_balls_to_teleport_and_catch', 10) self.min_iv_to_ignore_catch_list = self.config.get('min_iv_to_ignore_catch_list', 100) - self.optional_mappings = ['iv', 'id', 'name', 'encounter', 'spawnpoint'] + self.optional_mappings = [ResponseMapper.IV, ResponseMapper.ID, ResponseMapper.NAME, ResponseMapper.ENCOUNTER, ResponseMapper.SPAWNPOINT] self.mappings = { ResponseMapper.IV: self.config.get('mappings', {}).get('pokemon_iv', 'iv'), ResponseMapper.ID: self.config.get('mappings', {}).get('pokemon_id', 'id'), @@ -233,7 +233,6 @@ def _parse_pokemons(self, pokemon_dictionary_list): except Exception as exception: # If mappings contain errors, log it instead of forwarding the exception self._error(exception) - self._trace('This is the dictionary: {}'.format(pokemon_dictionary_list[0])) # TODO: Remove return pokemons # Build up the pokemon. Pops are used to destroy random attribute names and keep the known ones! From 0f900a4e41a86b34ad6868d3ce3ae6cfe2613171 Mon Sep 17 00:00:00 2001 From: Yves Henri Date: Wed, 14 Sep 2016 16:02:54 -0300 Subject: [PATCH 3/4] Fixed the catching bug after some previous refactorings. --- pokemongo_bot/cell_workers/sniper.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/pokemongo_bot/cell_workers/sniper.py b/pokemongo_bot/cell_workers/sniper.py index 998dc75..3e51818 100644 --- a/pokemongo_bot/cell_workers/sniper.py +++ b/pokemongo_bot/cell_workers/sniper.py @@ -43,16 +43,15 @@ class ResponseMapper(): ENCOUNTER = 'encounter' SPAWNPOINT = 'spawnpoint' +# TODO: Increase the JSON param mapping flexibility by adding 'optional' and 'required' values class Sniper(BaseTask): SUPPORTED_TASK_API_VERSION = 1 MIN_SECONDS_ALLOWED_FOR_CELL_CHECK = 10 CACHE_LIST_MAX_SIZE = 200 - # Constructor (invokes base constructor, which invokes initialize()) def __init__(self, bot, config): super(Sniper, self).__init__(bot, config) - # Constructor dispatcher def initialize(self): self.cached_pokemons = [] self.is_mappings_good = False @@ -69,7 +68,13 @@ def initialize(self): self.max_consecutive_catches = self.config.get('max_consecutive_catches', 1) self.min_balls_to_teleport_and_catch = self.config.get('min_balls_to_teleport_and_catch', 10) self.min_iv_to_ignore_catch_list = self.config.get('min_iv_to_ignore_catch_list', 100) - self.optional_mappings = [ResponseMapper.IV, ResponseMapper.ID, ResponseMapper.NAME, ResponseMapper.ENCOUNTER, ResponseMapper.SPAWNPOINT] + self.optional_mappings = [ + ResponseMapper.IV, + ResponseMapper.ID, + ResponseMapper.NAME, + ResponseMapper.ENCOUNTER, + ResponseMapper.SPAWNPOINT + ] self.mappings = { ResponseMapper.IV: self.config.get('mappings', {}).get('pokemon_iv', 'iv'), ResponseMapper.ID: self.config.get('mappings', {}).get('pokemon_id', 'id'), @@ -105,7 +110,7 @@ def is_snipeable(self, pokemon): # Skip if already cached if self._is_cached(pokemon): - self._trace('{} was already caught! Skipping...'.format(pokemon['name'])) + self._trace('{} was already seen! Skipping...'.format(pokemon['name'])) return False # Skip if not enought balls @@ -139,7 +144,7 @@ def snipe(self, pokemon): # If social is enabled, trust it. If encounter and spawnpoint IDs arent valid, verify them! exists = self.is_social - verify = pokemon.get('encounter_id') and pokemon.get('spawn_point_id') + verify = pokemon.get('encounter_id') is None or pokemon.get('spawn_point_id') is None # If information verification have to be done, do so if verify: From 5ba2390f4d54cdf01633580bda323b20c2ca3c33 Mon Sep 17 00:00:00 2001 From: henrimeep Date: Wed, 14 Sep 2016 16:10:28 -0300 Subject: [PATCH 4/4] jschwerberg has asked on slack to put his name in the contributors list (yea, lazy bitch). --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c5e642a..3d413a7 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,7 @@ If you do not want any data to be gathered, you can turn off this feature by set * rawgni * Breeze Ro * bruno-kenji - + * jschwerberg ## Disclaimer ©2016 Niantic, Inc. ©2016 Pokémon. ©1995–2016 Nintendo / Creatures Inc. / GAME FREAK inc. © 2016 Pokémon/Nintendo Pokémon and Pokémon character names are trademarks of Nintendo. The Google Maps Pin is a trademark of Google Inc. and the trade dress in the product design is a trademark of Google Inc. under license to The Pokémon Company. Other trademarks are the property of their respective owners. [Privacy Policy](http://www.pokemon.com/us/privacy-policy/)