diff --git a/Dockerfile b/Dockerfile index b5bdc16128..a47c67f76d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,17 +26,24 @@ RUN apk -U --no-cache add python py-pip tzdata \ && rm -rf /var/cache/apk/* \ && find / -name '*.pyc' -o -name '*.pyo' | xargs -rn1 rm -f -ADD http://pgoapi.com/pgoencrypt.tar.gz /tmp/pgoencrypt.tar.gz ADD https://raw.githubusercontent.com/$BUILD_REPO/$BUILD_BRANCH/requirements.txt . -RUN apk -U --no-cache add --virtual .build-dependencies python-dev gcc make musl-dev git \ - && tar zxf /tmp/pgoencrypt.tar.gz -C /tmp \ - && make -C /tmp/pgoencrypt/src \ - && cp /tmp/pgoencrypt/src/libencrypt.so /usr/src/app/encrypt.so \ - && ln -s locale.h /usr/include/xlocale.h \ - && pip install --no-cache-dir -r requirements.txt \ - && apk del .build-dependencies \ - && rm -rf /var/cache/apk/* /tmp/pgoencrypt* /usr/include/xlocale.h \ - && find / -name '*.pyc' -o -name '*.pyo' | xargs -rn1 rm -f + +#Need to load cert for WGET +RUN apk update +RUN apk add ca-certificates wget +RUN update-ca-certificates +RUN wget -P /tmp/ http://pgoapi.com/pgoencrypt.tar.gz + +RUN apk -U --no-cache add --virtual .build-dependencies python-dev gcc make musl-dev git +RUN tar xvzf /tmp/pgoencrypt.tar.gz -C /tmp +RUN make -C /tmp/pgoencrypt/src +RUN cp /tmp/pgoencrypt/src/libencrypt.so /usr/src/app/encrypt.so +RUN ln -s locale.h /usr/include/xlocale.h +RUN pip install --no-cache-dir -r requirements.txt +RUN apk del .build-dependencies +RUN rm -rf /var/cache/apk/* /tmp/pgoencrypt* /usr/include/xlocale.h +RUN find / -name '*.pyc' -o -name '*.pyo' | xargs -rn1 rm -f + ADD https://api.github.com/repos/$BUILD_REPO/commits/$BUILD_BRANCH /tmp/pgobot-version RUN apk -U --no-cache add --virtual .pgobot-dependencies wget ca-certificates tar jq \ diff --git a/README.md b/README.md index e4aaa79e22..db575b8ed2 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ If you need any help please don't create an issue as we have a great community o - #dev channel in slack ## Discord - - [Click here to join discord server](https://discord.gg/n3g5puF) + - [Click here to join discord server](https://discord.gg/n3g5puF) ---->RECOMENDED<---- ### [Bugs / Issues](https://github.com/PokemonGoF/PokemonGo-Bot/issues?q=is%3Aissue+sort%3Aupdated-desc) If you discover a bug in the bot, please [search our issue tracker](https://github.com/PokemonGoF/PokemonGo-Bot/issues?q=is%3Aissue+sort%3Aupdated-desc) first. If it hasn't been reported, please [create a new issue](https://github.com/PokemonGoF/PokemonGo-Bot/issues/new) and ensure you follow the template guide so that our team can assist you as quickly as possible. @@ -53,27 +53,25 @@ If this is your first time making a PR or aren't sure of the standard practice o - [x] Search Pokestops - [x] Catch Pokemon - [x] Determine which pokeball to use (uses Razz Berry if the catch percentage is low!) -- [x] Exchange Pokemon as per configuration -- [x] Evolve Pokemon as per configuration +- [x] Exchange, evolve and catch Pokemon as per configuration +- [x] Transfer Pokemon in bulk - [x] Auto switch mode (Inventory Checks - switches between catch/farming items) - [x] Limit the step to farm specific area for pokestops -- [x] Rudimentary IV Functionality filter -- [x] Ignore certain pokemon filter +- [x] Limit Pokestops/catch Pokemons per day +- [x] IV Functionality filter - [x] Adjust delay between Pokemon capture & Transfer as per configuration - [x] Hatch eggs - [x] Incubate eggs -- [x] Crowd Sourced Map Prototype -- [ ] [Standalone Desktop Application] (https://github.com/PokemonGoF/PokemonGo-Bot-Desktop) - [x] Use candy +- [x] Set family ID as VIP and priorice bot to catch it! +- [x] Spin Gyms pokestops +- [x] Transfer red slashed pokemons +- [x] Set shiny pokemons as VIP +- [x] Deploy a pokemon in Gym if slot available +- [x] Docker support +- [x] Auto heal Pokemons +- [x] Information about PGoAPI bot version is rather Bossland endpoint, expiration key date and RPM used -## Analytics -[PokemonGo-Bot](https://github.com/PokemonGoF/PokemonGo-Bot) is very popular and has a vibrant community. Because of that, it has become very difficult for us to know how the bot is used and what errors people hit. By capturing small amounts of data, we can prioritize our work better such as fixing errors that happen to a large percentage of our user base, not just a vocal minority. - -Our goal is to help inform our decisions by capturing data that helps us get aggregate usage and error reports, not personal information. To view the code that handles analytics in our master branch, you can use this [search link](https://github.com/PokemonGoF/PokemonGo-Bot/search?utf8=%E2%9C%93&q=BotEvent). - -If there are any concerns with this policy or you believe we are tracking something we shouldn't, please open a ticket in the tracker. The contributors always intend to do the right thing for our users, and we want to make sure we are held to that path. - -If you do not want any data to be gathered, you can turn off this feature by setting `health_record` to `false` in your `config.json`. ## Credits - [tejado](https://github.com/tejado) many thanks for the API @@ -167,6 +165,14 @@ If you do not want any data to be gathered, you can turn off this feature by set * Gobberwart * javajohnHub * kolinkorr839 + * lepeli + * davidakachaos + * MerlionRock + * walaoaaa1234 + * pogarek + * goedzo + * solderzzc aka BIG BOSS + ## 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. diff --git a/configs/config.json.example b/configs/config.json.example index 8dc00c4980..71d098d45a 100644 --- a/configs/config.json.example +++ b/configs/config.json.example @@ -464,6 +464,16 @@ "enabled": true } }, + { + "type": "GymPokemon", + "config": { + "enabled": false, + "order_by": "cp", + "min_interval":360, + "min_recheck":30, + "max_recheck":120 + } + }, { "type": "MoveToFort", "config": { @@ -482,6 +492,7 @@ "step_size": 70 } } + ], "map_object_cache_time": 5, "forts": { diff --git a/docs/configuration_files.md b/docs/configuration_files.md index dacf3a507e..853bfce16d 100644 --- a/docs/configuration_files.md +++ b/docs/configuration_files.md @@ -63,6 +63,7 @@ - [BuddyPokemon](#buddypokemon) - [PokemonHunter](#pokemonhunter) - [BadPokemon](#badpokemon) +- [HealPokemon](#healpokemon) # Configuration files @@ -1486,3 +1487,32 @@ If you have any Pokemon that Niantic has marked as bad (red slashes) this will n } } ``` + +## HealPokemon +[[back to top](#table-of-contents)] + +### Description +[[back to top](#table-of-contents)] + +If you have any Pokemon that are dead or need healing, this task will try to do that. + +### Options +[[back to top](#table-of-contents)] + +* `heal`: `Default: True`. Should Pokemon be healed? +* `revive`: `Default: True`. Should dead Pokemon be revived? + + +### Sample configuration +[[back to top](#table-of-contents)] +```json +{ + "type": "BadPokemon", + "config": { + "enabled": true, + "heal": true, + "revive": true + } +} +``` + diff --git a/pokemongo_bot/__init__.py b/pokemongo_bot/__init__.py index 08b7b208ca..769b61f47d 100644 --- a/pokemongo_bot/__init__.py +++ b/pokemongo_bot/__init__.py @@ -372,6 +372,7 @@ def _register_events(self): 'moving_to_fort', parameters=( 'fort_name', + 'target_type', 'distance' ) ) @@ -379,6 +380,7 @@ def _register_events(self): 'moving_to_lured_fort', parameters=( 'fort_name', + 'target_type', 'distance', 'lure_distance' ) @@ -386,7 +388,7 @@ def _register_events(self): self.event_manager.register_event( 'spun_pokestop', parameters=( - 'pokestop', 'exp', 'items' + 'pokestop', 'exp', 'items', 'stop_kind', 'spin_amount_now' ) ) self.event_manager.register_event( @@ -559,6 +561,8 @@ def _register_events(self): self.event_manager.register_event('catch_limit') self.event_manager.register_event('spin_limit') self.event_manager.register_event('show_best_pokemon', parameters=('pokemons')) + self.event_manager.register_event('revived_pokemon') + self.event_manager.register_event('healing_pokemon') # level up stuff self.event_manager.register_event( @@ -1725,6 +1729,25 @@ def get_forts(self, order_by_distance=False): )) return forts + + def get_gyms(self, order_by_distance=False): + forts = [fort + for fort in self.cell['forts'] + if 'latitude' in fort and 'type' not in fort] + # Need to filter out disabled gyms! + forts = filter(lambda x: x["enabled"] is True, forts) + forts = filter(lambda x: 'closed' not in fort, forts) + # forts = filter(lambda x: 'type' not in fort, forts) + + if order_by_distance: + forts.sort(key=lambda x: distance( + self.position[0], + self.position[1], + x['latitude'], + x['longitude'] + )) + + return forts def get_map_objects(self, lat, lng, timestamp, cellid): if time.time() - self.last_time_map_object < self.config.map_object_cache_time: diff --git a/pokemongo_bot/cell_workers/__init__.py b/pokemongo_bot/cell_workers/__init__.py index a08e45da4a..586d868e15 100644 --- a/pokemongo_bot/cell_workers/__init__.py +++ b/pokemongo_bot/cell_workers/__init__.py @@ -34,3 +34,5 @@ from .catch_limiter import CatchLimiter from .update_hash_stats import UpdateHashStats from .bad_pokemon import BadPokemon +from .heal_pokemon import HealPokemon +from .gym_pokemon import GymPokemon \ No newline at end of file diff --git a/pokemongo_bot/cell_workers/gym_pokemon.py b/pokemongo_bot/cell_workers/gym_pokemon.py new file mode 100644 index 0000000000..ec1ec91549 --- /dev/null +++ b/pokemongo_bot/cell_workers/gym_pokemon.py @@ -0,0 +1,778 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +from __future__ import absolute_import + +from datetime import datetime, timedelta +import sys +import time +import random +from random import uniform +from collections import Counter + +from pgoapi.utilities import f2i +from pokemongo_bot import inventory +from pokemongo_bot.inventory import player + +from pokemongo_bot.constants import Constants +from pokemongo_bot.human_behaviour import action_delay, sleep +from pokemongo_bot.worker_result import WorkerResult +from pokemongo_bot.base_task import BaseTask +from pokemongo_bot import inventory +from .utils import distance, format_time, fort_details, format_dist +from pokemongo_bot.tree_config_builder import ConfigException +from pokemongo_bot.walkers.walker_factory import walker_factory +from pokemongo_bot.inventory import Pokemons + +GYM_DETAIL_RESULT_SUCCESS = 1 +GYM_DETAIL_RESULT_OUT_OF_RANGE = 2 +GYM_DETAIL_RESULT_UNSET = 0 + +TEAM_NOT_SET = 0 +TEAM_BLUE = 1 +TEAM_RED = 2 +TEAM_YELLOW = 3 + +TEAMS = { + 0: "Not Set", + 1: "Mystic", + 2: "Valor", + 3: "Instinct" +} + +ITEM_RAZZBERRY = 701 +ITEM_NANABBERRY = 703 +ITEM_PINAPBERRY = 705 + + + +class GymPokemon(BaseTask): + SUPPORTED_TASK_API_VERSION = 1 + + def __init__(self, bot, config): + super(GymPokemon, self).__init__(bot, config) + + def initialize(self): + + #Adding this to play with finding a quantity that works + self.gym_quantity_test = 1 + + # 10 seconds from current time + self.next_update = datetime.now() + timedelta(0, 10) + self.order_by = self.config.get('order_by', 'cp') + self.enabled = self.config.get('enabled', False) + self.min_interval = self.config.get('min_interval', 360) + self.min_recheck = self.config.get('min_recheck', 30) + self.max_recheck = self.config.get('max_recheck', 120) + self.feed_berries = self.config.get('feed_berries', False) + self.recheck = datetime.now() + self.walker = self.config.get('walker', 'StepWalker') + self.destination = None + self.recent_gyms = [] + self.pokemons = [] + self.fort_pokemons = [] + self.expire_recent = 10 + self.next_expire = None + self.dropped_gyms = [] + self.blacklist= [] + self.check_interval = 0 + self.gyms = [] + self.raid_gyms = dict() + self.bot.event_manager.register_event('gym_error') + self.bot.event_manager.register_event('fed_pokemon') + self.bot.event_manager.register_event('gym_full') + self.bot.event_manager.register_event('deployed_pokemon') + + #self.logger.info("player_date %s." % self.bot.player_data) + + try: + self.team = self.bot.player_data['team'] + except KeyError: + self.team = TEAM_NOT_SET + if self.enabled: + self.emit_event( + 'gym_error', + formatted="You have no team selected, so the module GymPokemon should be disabled" + ) + + def should_run(self): + # Check if we have any Pokemons and are level > 5 and have selected a team + return player()._level >= 5 and len(self.pokemons) > 0 and self.team > TEAM_NOT_SET + + def display_fort_pokemon(self): + if len(self.fort_pokemons) == 0: + return + self.logger.info("We currently have %s Pokemon in Gym(s):" % len(self.fort_pokemons) ) + for pokemon in self.fort_pokemons: + lat = self.bot.position[0:2][0] + lng = self.bot.position[0:2][1] + self.logger.info("%s (%s CP)" % (pokemon.name, pokemon.cp)) + + def work(self): + self.pokemons = inventory.pokemons().all() + self.fort_pokemons = [p for p in self.pokemons if p.in_fort] + self.pokemons = [p for p in self.pokemons if not p.in_fort] + + self.dropped_gyms = [] + for pokemon in self.fort_pokemons: + self.dropped_gyms.append(pokemon.fort_id) + + if self._should_print(): + self.display_fort_pokemon() + self._compute_next_update() + # Do display the stats about Pokemon in Gym and collection time [please] + if not self.enabled: + return WorkerResult.SUCCESS + if self.bot.softban: + return WorkerResult.SUCCESS + + if len(self.fort_pokemons) >= 20: + if self._should_print(): + self.logger.info("We have a max of 20 Pokemon in gyms.") + return WorkerResult.SUCCESS + + if not self.should_run(): + return WorkerResult.SUCCESS + + if self.destination is None: + self.check_close_gym() + + if self.destination is None: + self.determin_new_destination() + + if self.destination is not None: + result = self.move_to_destination() + # Can return RUNNING to move to a gym + return result + + if hasattr(self.bot, "hunter_locked_target") and self.bot.hunter_locked_target is not None: + # Don't move to a gym when hunting for a Pokemon + return WorkerResult.SUCCESS + + return WorkerResult.SUCCESS + + def check_close_gym(self): + # Check if we are walking past a gym + close_gyms = self.get_gyms_in_range() + # Filter active raids from the gyms + close_gyms = filter(lambda gym: gym["id"] not in self.raid_gyms, close_gyms) + + if len(close_gyms) > 0: + # self.logger.info("Walking past a gym!") + for gym in close_gyms: + if gym["id"] in self.dropped_gyms: + continue + + gym_details = self.get_gym_details(gym) + if gym_details: + pokes = self._get_pokemons_in_gym(gym_details) + if len(pokes) == 6: + continue + if 'enabled' in gym: + if not gym['enabled']: + continue + if 'owned_by_team' in gym: + if gym["owned_by_team"] == self.team: + self.feed_pokemons_in_gym(gym) + + if 'gym_display' in gym: + display = gym['gym_display'] + if 'slots_available' in display: + self.logger.info("Gym has %s open spots!" % display['slots_available']) + if display['slots_available'] > 0 and gym["id"] not in self.dropped_gyms: + self.logger.info("Dropping pokemon in %s" % gym_details["name"]) + self.drop_pokemon_in_gym(gym, pokes) + if self.destination is not None and gym["id"] == self.destination["id"]: + self.destination = None + return WorkerResult.SUCCESS + else: + self.logger.info("Neutral gym? %s" % gym) + self.logger.info("Dropping pokemon in %s" % gym_details["name"]) + self.drop_pokemon_in_gym(gym, []) + if self.destination is not None and gym["id"] == self.destination["id"]: + self.destination = None + return WorkerResult.SUCCESS + + def determin_new_destination(self): + gyms = self.get_gyms() + if len(gyms) == 0: + if len(self.recent_gyms) == 0 and self._should_print(): + self.logger.info("No Gyms in range to scan!") + return WorkerResult.SUCCESS + + self.logger.info("Inspecting %s gyms." % len(gyms)) + self.logger.info("Recent gyms: %s" % len(self.recent_gyms)) + self.logger.info("Active raid gyms: %s" % len(self.raid_gyms)) + teams = [] + for gym in gyms: + # Ignore after done for 5 mins + self.recent_gyms.append(gym["id"]) + + if 'enabled' in gym: + # Gym can be closed for a raid or something, skipp to the next + if not gym['enabled']: + continue + + if 'owned_by_team' in gym: + if gym["owned_by_team"] == 1: + teams.append("Mystic") + elif gym["owned_by_team"] == 2: + teams.append("Valor") + elif gym["owned_by_team"] == 3: + teams.append("Instinct") + # else: + # self.logger.info("Unknown team? %s" % gym) + + if gym["owned_by_team"] == self.team: + if 'gym_display' in gym: + display = gym['gym_display'] + if 'slots_available' in display: + self.logger.info("Gym has %s open spots!" % display['slots_available']) + self.destination = gym + break + else: + #If there are mons we can feed, check them. Note: gym_details returns false if gym is not in range. + self.feed_pokemons_in_gym(gym) + else: + # self.logger.info("Found a Neutral gym?") + # self.logger.info("Info: %s" % gym) + self.destination = gym + break + if len(teams) > 0: + count_teams = Counter(teams) + self.logger.info("Gym Teams %s", ", ".join('{}({})'.format(key, val) for key, val in count_teams.items())) + + def move_to_destination(self): + if self.check_interval >= 4: + self.check_interval = 0 + gyms = self.get_gyms() + for g in gyms: + if g["id"] == self.destination["id"]: + # self.logger.info("Inspecting target: %s" % g) + if "owned_by_team" in g and g["owned_by_team"] is not self.team: + self.logger.info("Damn! Team %s took gym before we arrived!" % TEAMS[g["owned_by_team"]]) + self.destination = None + return WorkerResult.SUCCESS + break + else: + self.check_interval += 1 + + # Moving to a gym to deploy Pokemon + unit = self.bot.config.distance_unit # Unit to use when printing formatted distance + lat = self.destination["latitude"] + lng = self.destination["longitude"] + details = fort_details(self.bot, self.destination["id"], lat, lng) + gym_name = details.get('name', 'Unknown') + + dist = distance( + self.bot.position[0], + self.bot.position[1], + lat, + lng + ) + noised_dist = distance( + self.bot.noised_position[0], + self.bot.noised_position[1], + lat, + lng + ) + + moving = noised_dist > Constants.MAX_DISTANCE_FORT_IS_REACHABLE if self.bot.config.replicate_gps_xy_noise else dist > Constants.MAX_DISTANCE_FORT_IS_REACHABLE + + if moving: + fort_event_data = { + 'fort_name': u"{}".format(gym_name), + 'distance': format_dist(dist, unit), + } + self.emit_event( + 'moving_to_fort', + formatted="Moving towards open Gym {fort_name} - {distance}", + data=fort_event_data + ) + + step_walker = walker_factory(self.walker, self.bot, lat, lng) + + if not step_walker.step(): + return WorkerResult.RUNNING + else: + #Running fails. Let's stop moving to the gym + return WorkerResult.SUCCESS + else: + self.emit_event( + 'arrived_at_fort', + formatted=("Arrived at Gym %s." % gym_name) + ) + gym_details = self.get_gym_details(self.destination) + current_pokemons = self._get_pokemons_in_gym(gym_details) + self.drop_pokemon_in_gym(self.destination, current_pokemons) + # Feed the Pokemon now we're here... + self.feed_pokemons_in_gym(self.destination) + self.destination = None + # Look around if there are more gyms to fill + self.determin_new_destination() + # If there is none, we're done, else we go to the next! + if self.destination is None: + return WorkerResult.SUCCESS + else: + return WorkerResult.RUNNING + + def get_gym_details(self, gym): + lat = gym['latitude'] + lng = gym['longitude'] + + in_reach = False + + if self.bot.config.replicate_gps_xy_noise: + if distance(self.bot.noised_position[0], self.bot.noised_position[1], gym['latitude'], gym['longitude']) <= Constants.MAX_DISTANCE_FORT_IS_REACHABLE: + in_reach = True + else: + if distance(self.bot.position[0], self.bot.position[1], gym['latitude'], gym['longitude']) <= Constants.MAX_DISTANCE_FORT_IS_REACHABLE: + in_reach = True + + if in_reach: + request = self.bot.api.create_request() + request.gym_get_info(gym_id=gym['id'], gym_lat_degrees=lat, gym_lng_degrees=lng, player_lat_degrees=self.bot.position[0],player_lng_degrees=self.bot.position[1]) + response_dict = request.call() + + if ('responses' in response_dict) and ('GYM_GET_INFO' in response_dict['responses']): + details = response_dict['responses']['GYM_GET_INFO'] + return details + else: + return False + # details = fort_details(self.bot, , lat, lng) + # fort_name = details.get('name', 'Unknown') + # self.logger.info("Checking Gym: %s (%s pts)" % (fort_name, gym['gym_points'])) + + def _get_pokemons_in_gym(self, gym_details): + pokemon_names = [] + gym_info = gym_details.get('gym_status_and_defenders', None) + if gym_info: + defenders = gym_info.get('gym_defender', []) + for defender in defenders: + motivated_pokemon = defender.get('motivated_pokemon') + pokemon_info = motivated_pokemon.get('pokemon') + pokemon_id = pokemon_info.get('pokemon_id') + pokemon_names.append(Pokemons.name_for(pokemon_id)) + + return pokemon_names + + def feed_pokemons_in_gym(self, gym): + #Check if berry feeding is enabled from config + if self.feed_berries == False: + return True + + #check if gym is in range. If not, gym=false + if gym == False: + return True + + berries = inventory.items().get(ITEM_RAZZBERRY).count + (inventory.items().get(ITEM_PINAPBERRY).count - 10) + inventory.items().get(ITEM_NANABBERRY).count + if berries < 1: + self.logger.info("No berries left to feed Pokemon.") + return True + + max_gym_time = timedelta(hours=8,minutes=20) + + try: + gym_info = self.get_gym_details(gym).get('gym_status_and_defenders', None) + except (TypeError,KeyError,AttributeError): + #This gym does not give status results. Probably it is not in range + return True + + # self.logger.info("Defenders in gym:")okemon_info..items()get('pokemon_id') + if gym_info: + defenders = gym_info.get('gym_defender', []) + for defender in defenders: + #self.logger.info("LOG: Defender data: defender %s" % defender) + + motivated_pokemon = defender.get('motivated_pokemon') + pokemon_info = motivated_pokemon.get('pokemon') + pokemon_id=pokemon_info.get('id') + # timestamp when deployed + deployed_on = datetime.fromtimestamp(int(motivated_pokemon.get('deploy_ms')) / 1e3) + time_deployed = datetime.now() - deployed_on + # % of motivation + current_motivation = motivated_pokemon.get('motivation_now') + + # Let's see if we should feed this Pokemon + if time_deployed < max_gym_time and current_motivation < 1.0: + # Let's feed this Pokemon a candy + # self.logger.info("This pokemon deserves a candy") + berry_id = self._determin_feed_berry_id(motivated_pokemon) + poke_id = pokemon_id + + #Testing to see what quantity field does. Probably just the amount of berries you want to feed + #quantity = pokemon_info.get('num_upgrades') <- Failed + quantity=2 + self._feed_pokemon(gym, poke_id, berry_id, quantity) + + # quantity=0 + # food_values = motivated_pokemon.get('food_value') + # for food_value in food_values: + # if food_value.get('food_item') == berry_id: + # quantity = food_value.get('motivation_increase') + # if quantity !=0: + # self._feed_pokemon(gym, poke_id, berry_id, quantity) + + def _determin_feed_berry_id(self, motivated_pokemon): + # # Store the amount of berries we have + razzb = inventory.items().get(ITEM_RAZZBERRY).count + pinap = inventory.items().get(ITEM_PINAPBERRY).count + nanab = inventory.items().get(ITEM_NANABBERRY).count + missing_motivation = 1.0 - motivated_pokemon.get('motivation_now') + # Always allow feeding with RAZZ and NANAB + allowed_berries = [] + if razzb > 0: + allowed_berries.append(ITEM_RAZZBERRY) + + if nanab > 0: + allowed_berries.append(ITEM_NANABBERRY) + + if pinap > 10: + allowed_berries.append(ITEM_PINAPBERRY) + + food_values = motivated_pokemon['food_value'] + # Only check the berries we wish to feed + food_values = [f for f in food_values if f['food_item'] in allowed_berries] + # Sort by the least restore first + sorted(food_values, key=lambda x: x['motivation_increase']) + + for food_value in food_values: + if food_value['motivation_increase'] >= missing_motivation: + # We fully restore CP with this berry! + return food_value['food_item'] + # Okay, we can't completely fill the CP for the pokemon, get the best berry then NO GOLDEN + return food_values[-1]['food_item'] + + def _feed_pokemon(self, gym, pokemon_id, berry_id, quantity): + + self.logger.info("----THIS IS IN DEVELOPEMENT-----") + self.logger.info("We are feeding pokemon %s with berry %s ",pokemon_id,berry_id) + self.logger.info("Feed data: quantity %s" % self.gym_quantity_test) + #self.logger.info("Feed data: gym %s" % gym) + self.logger.info("----ABORTING HERE SINCE WE HAVE NOT YET FIND THE RIGHT API PARAMETERS-----") + return True + + #Get the gym_name to show in messages + lat = self.destination["latitude"] + lng = self.destination["longitude"] + + details = fort_details(self.bot, gym["id"], lat, lng) + if details: + gym_name = details.get('name', 'Unknown') + else: + #Seem that we cannot read details here. Let's exit + return True + + + #Overide the quantity to find the right value + #quantity = self.gym_quantity_test + + request = self.bot.api.create_request() + request.gym_feed_pokemon( + starting_quantity=self.gym_quantity_test, + item_id=berry_id, + gym_id=gym["id"], + pokemon_id=pokemon_id, + player_lat_degrees=f2i(self.bot.position[0]), + player_lng_degrees=f2i(self.bot.position[1]) + ) + response_dict = request.call() + if ('responses' in response_dict) and ('GYM_FEED_POKEMON' in response_dict['responses']): + feeding = response_dict['responses']['GYM_FEED_POKEMON'] + self.logger.info("Feeding result: %s" % feeding) + result = feeding.get('result', -1) + if result == 1: + # Succesful feeding + self.emit_event( + 'fed_pokemon', + formatted=("We fed %s in the gym %s!!" % (pokemon_id, gym_name)), + data={'gym_id': gym['id'], 'pokemon_id': pokemon_id} + ) + elif result == 4: #ERROR_POKEMON_NOT_THERE + self.emit_event( + 'fed_pokemon', + formatted=("Pokemon %s has dropped out of the gym %s!" % (pokemon_id, gym_name)), + data={'gym_id': gym['id'], 'pokemon_id': pokemon_id} + ) + elif result == 5: #ERROR_POKEMON_FULL + self.emit_event( + 'fed_pokemon', + formatted=("Pokemon %s is full in gym %s!" % (pokemon_id, gym_name)), + data={'gym_id': gym['id'], 'pokemon_id': pokemon_id} + ) + elif result == 7: #ERROR_WRONG_TEAM + self.emit_event( + 'fed_pokemon', + formatted=("Team Switched while feeding %s in gym %s!" % (pokemon_id, gym_name)), + data={'gym_id': gym['id'], 'pokemon_id': pokemon_id} + ) + elif result == 8: #ERROR_WRONG_COUNT + #Let's try a different quantity + self.gym_quantity_test += 1 + else: + self.logger.info("Feeding failed! %s" % result) + + + def drop_pokemon_in_gym(self, gym, current_pokemons): + self.pokemons = inventory.pokemons().all() + self.fort_pokemons = [p for p in self.pokemons if p.in_fort] + self.pokemons = [p for p in self.pokemons if not p.in_fort] + close_gyms = self.get_gyms_in_range() + + empty_gym = False + + for pokemon in self.fort_pokemons: + if pokemon.fort_id == gym["id"]: + self.logger.info("We are already in this gym!") + if pokemon.fort_id not in self.dropped_gyms: + self.dropped_gyms.append(pokemon.fort_id) + self.recent_gyms.append(gym["id"]) + return WorkerResult.SUCCESS + + for g in close_gyms: + if g["id"] == gym["id"]: + if 'owned_by_team' in g: + self.logger.info("Expecting team: %s it is: %s" % (self.bot.player_data['team'], g["owned_by_team"]) ) + if g["owned_by_team"] is not self.team: + self.logger.info("Can't drop in a enemy gym!") + self.recent_gyms.append(gym["id"]) + return WorkerResult.SUCCESS + else: + #self.logger.info("Empty gym?? %s" % g) + gym_details = self.get_gym_details(gym) + #self.logger.info("Details: %s" % gym_details) + empty_gym = True + if not gym_details or gym_details == {}: + self.logger.info("No details for this Gym? Blacklisting!") + self.blacklist.append(gym["id"]) + return WorkerResult.SUCCESS + + # Check for raid + if 'raid_info' in gym: + raid_info = gym["raid_info"] + raid_starts = datetime.fromtimestamp(int(raid_info["raid_battle_ms"]) / 1e3) + raid_ends = datetime.fromtimestamp(int(raid_info["raid_end_ms"]) / 1e3) + self.logger.info("Raid starts: %s" % raid_starts.strftime('%Y-%m-%d %H:%M:%S.%f')) + self.logger.info("Raid ends: %s" % raid_ends.strftime('%Y-%m-%d %H:%M:%S.%f')) + t = datetime.today() + + if raid_starts < datetime.now(): + self.logger.info("Active raid?") + if raid_ends < datetime.now(): + self.logger.info("No need to wait.") + elif (raid_ends-t).seconds > 600: + self.logger.info("Need to wait long than 10 minutes, skipping") + self.destination = None + self.recent_gyms.append(gym["id"]) + self.raid_gyms[gym["id"]] = raid_ends + return WorkerResult.SUCCESS + else: + while raid_ends > datetime.now(): + self.logger.info("Waiting for %s seconds for raid to end..." % (raid_ends-datetime.today()).seconds) + if (raid_ends-datetime.today()).seconds > 20: + sleep(20) + else: + sleep((raid_ends-datetime.today()).seconds) + break + else: + self.logger.info("Raid has not begun yet!") + + if 'same_team_deploy_lockout_end_ms' in gym: + # self.logger.info("%f" % gym["same_team_deploy_lockout_end_ms"]) + org_time = int(gym["same_team_deploy_lockout_end_ms"]) / 1e3 + lockout_time = datetime.fromtimestamp(org_time) + t = datetime.today() + + if lockout_time > datetime.now(): + self.logger.info("Lockout time: %s" % lockout_time.strftime('%Y-%m-%d %H:%M:%S.%f')) + while lockout_time > datetime.now(): + self.logger.info("Waiting for %s seconds deployment lockout to end..." % (lockout_time-datetime.today()).seconds) + if (lockout_time-datetime.today()).seconds > 40: + sleep(40) + #Feed any mons while we are waiting + self.feed_pokemons_in_gym(gym) + else: + sleep((lockout_time-datetime.today()).seconds) + break + + #FortDeployPokemon + # self.logger.info("Trying to deploy Pokemon in gym: %s" % gym) + gym_details = self.get_gym_details(gym) + # self.logger.info("Gym details: %s" % gym_details) + fort_pokemon = self._get_best_pokemon(current_pokemons) + pokemon_id = fort_pokemon.unique_id + # self.logger.info("Trying to deploy %s (%s)" % (fort_pokemon, pokemon_id)) + # self.logger.info("Gym in control by %s. I am on team %s" % (gym["owned_by_team"], self.bot.player_data['team'])) + + request = self.bot.api.create_request() + request.gym_deploy( + fort_id=gym["id"], + pokemon_id=pokemon_id, + player_lat_degrees=f2i(self.bot.position[0]), + player_lng_degrees=f2i(self.bot.position[1]) + ) + # self.logger.info("Req: %s" % request) + response_dict = request.call() + # self.logger.info("Called deploy pokemon: %s" % response_dict) + + if ('responses' in response_dict) and ('GYM_DEPLOY' in response_dict['responses']): + deploy = response_dict['responses']['GYM_DEPLOY'] + result = response_dict.get('status_code', -1) + self.recent_gyms.append(gym["id"]) + # self.logger.info("Status: %s" % result) + if result == 1: + self.dropped_gyms.append(gym["id"]) + gym_details = self.get_gym_details(gym) + # SUCCES + #self.logger.info("We deployed %s (%s CP) in the gym! We now have %s Pokemon in gyms!" % (fort_pokemon.name, fort_pokemon.cp, len(self.dropped_gyms))) + self.emit_event( + 'deployed_pokemon', + formatted=("We deployed %s (%s CP) in the gym %s!!" % (fort_pokemon.name, fort_pokemon.cp, gym_details["name"])), + data={'gym_id': gym['id'], 'pokemon_id': pokemon_id} + ) + return WorkerResult.SUCCESS + elif result == 2: + #ERROR_ALREADY_HAS_POKEMON_ON_FORT + self.logger.info('ERROR_ALREADY_HAS_POKEMON_ON_FORT') + self.dropped_gyms.append(gym["id"]) + return WorkerResult.ERROR + elif result == 3: + #ERROR_OPPOSING_TEAM_OWNS_FORT + self.logger.info('ERROR_OPPOSING_TEAM_OWNS_FORT') + return WorkerResult.ERROR + elif result == 4: + #ERROR_FORT_IS_FULL + self.logger.info('ERROR_FORT_IS_FULL') + return WorkerResult.ERROR + elif result == 5: + #ERROR_NOT_IN_RANGE + self.logger.info('ERROR_NOT_IN_RANGE') + return WorkerResult.ERROR + elif result == 6: + #ERROR_PLAYER_HAS_NO_TEAM + self.logger.info('ERROR_PLAYER_HAS_NO_TEAM') + return WorkerResult.ERROR + elif result == 7: + #ERROR_POKEMON_NOT_FULL_HP + self.logger.info('ERROR_POKEMON_NOT_FULL_HP') + return WorkerResult.ERROR + elif result == 8: + #ERROR_PLAYER_BELOW_MINIMUM_LEVEL + self.logger.info('ERROR_PLAYER_BELOW_MINIMUM_LEVEL') + return WorkerResult.ERROR + elif result == 8: + #ERROR_POKEMON_IS_BUDDY + self.logger.info('ERROR_POKEMON_IS_BUDDY') + return WorkerResult.ERROR + + def get_gyms(self, skip_recent_filter=False): + if len(self.gyms) == 0: + self.gyms = self.bot.get_gyms(order_by_distance=True) + + if self._should_recheck(): + self.gyms = self.bot.get_gyms(order_by_distance=True) + self._compute_next_recheck() + + if self._should_expire(): + self.recent_gyms = [] + self._compute_next_expire() + # Check raid gyms for raids that ended + for gym_id in list(self.raid_gyms.keys()): + if self.raid_gyms[gym_id] < datetime.now(): + self.logger.info("Raid at %s ended (%s)" % (gym_id, self.raid_gyms[gym_id])) + del(self.raid_gyms[gym_id]) + + gyms = [] + # if not skip_recent_filter: + gyms = filter(lambda gym: gym["id"] not in self.recent_gyms, self.gyms) + # Filter blacklisted gyms + gyms = filter(lambda gym: gym["id"] not in self.blacklist, gyms) + # Filter out gyms we already in + gyms = filter(lambda gym: gym["id"] not in self.dropped_gyms, gyms) + # Filter ongoing raids + gyms = filter(lambda gym: gym["id"] not in self.raid_gyms, gyms) + # filter fake gyms + # self.gyms = filter(lambda gym: "type" not in gym or gym["type"] != 1, self.gyms) + # sort by current distance + gyms.sort(key=lambda x: distance( + self.bot.position[0], + self.bot.position[1], + x['latitude'], + x['longitude'] + )) + + return gyms + + def get_gyms_in_range(self): + gyms = self.get_gyms() + + if self.bot.config.replicate_gps_xy_noise: + gyms = filter(lambda fort: distance( + self.bot.noised_position[0], + self.bot.noised_position[1], + fort['latitude'], + fort['longitude'] + ) <= Constants.MAX_DISTANCE_FORT_IS_REACHABLE, self.gyms) + else: + gyms = filter(lambda fort: distance( + self.bot.position[0], + self.bot.position[1], + fort['latitude'], + fort['longitude'] + ) <= Constants.MAX_DISTANCE_FORT_IS_REACHABLE, self.gyms) + + return gyms + + def _should_print(self): + return self.next_update is None or datetime.now() >= self.next_update + + def _should_expire(self): + return self.next_expire is None or datetime.now() >= self.next_expire + + def _compute_next_expire(self): + self.next_expire = datetime.now() + timedelta(seconds=300) + + def _compute_next_recheck(self): + wait = uniform(self.min_recheck, self.max_recheck) + self.recheck = datetime.now() + timedelta(seconds=wait) + + def _should_recheck(self): + return self.recheck is None or datetime.now() >= self.recheck + + def _compute_next_update(self): + """ + Computes the next update datetime based on the minimum update interval. + :return: Nothing. + :rtype: None + """ + self.next_update = datetime.now() + timedelta(seconds=self.min_interval) + + def _get_best_pokemon(self, current_pokemons): + def get_poke_info(info, pokemon): + poke_info = { + 'cp': pokemon.cp, + 'iv': pokemon.iv, + 'ivcp': pokemon.ivcp, + 'ncp': pokemon.cp_percent, + 'level': pokemon.level, + 'hp': pokemon.hp, + 'dps': pokemon.moveset.dps + } + if info not in poke_info: + raise ConfigException("order by {}' isn't available".format(self.order_by)) + return poke_info[info] + # Don't place a Pokemon which is already in the gym (prevent ALL Blissey etc) + possible_pokemons = [p for p in self.pokemons if not p.name in current_pokemons] + # Don't put in Pokemon above 3000 cp (morale drops too fast) + possible_pokemons = [p for p in possible_pokemons if p.cp < 3000] + # Filter out "bad" Pokemon + possible_pokemons = [p for p in possible_pokemons if not p.is_bad] + # HP Must be max + possible_pokemons = [p for p in possible_pokemons if p.hp == p.hp_max] + possible_pokemons = [p for p in possible_pokemons if not p.in_fort] + # Sort them + pokemons_ordered = sorted(possible_pokemons, key=lambda x: get_poke_info(self.order_by, x), reverse=True) + # Top 20 picks + pokemons_ordered = pokemons_ordered[0:20] + # Pick a random one! + random.shuffle(pokemons_ordered) + return pokemons_ordered[0] diff --git a/pokemongo_bot/cell_workers/heal_pokemon.py b/pokemongo_bot/cell_workers/heal_pokemon.py new file mode 100644 index 0000000000..c5cde80688 --- /dev/null +++ b/pokemongo_bot/cell_workers/heal_pokemon.py @@ -0,0 +1,251 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +from __future__ import absolute_import + +from datetime import datetime, timedelta +from pokemongo_bot.base_task import BaseTask +from pokemongo_bot.worker_result import WorkerResult +from pokemongo_bot import inventory +from pokemongo_bot.item_list import Item +from pokemongo_bot.human_behaviour import sleep, action_delay + +class HealPokemon(BaseTask): + SUPPORTED_TASK_API_VERSION = 1 + + def __init__(self, bot, config): + super(HealPokemon, self).__init__(bot, config) + self.bot = bot + self.config = config + self.enabled = self.config.get("enabled", False) + self.revive_pokemon = self.config.get("revive", True) + self.heal_pokemon = self.config.get("heal", True) + self.next_update = None + self.to_heal = [] + + def work(self): + + if not self.enabled: + return WorkerResult.SUCCESS + + # Check for pokemon to heal or revive + to_revive = [] + self.to_heal = [] + pokemons = inventory.pokemons().all() + pokemons.sort(key=lambda p: p.hp) + for pokemon in pokemons: + if pokemon.hp < 1.0: + self.logger.info("Dead: %s (%s CP| %s/%s )" % (pokemon.name, pokemon.cp, pokemon.hp, pokemon.hp_max)) + to_revive += [pokemon] + elif pokemon.hp < pokemon.hp_max: + self.logger.info("Heal: %s (%s CP| %s/%s )" % (pokemon.name, pokemon.cp, pokemon.hp, pokemon.hp_max)) + self.to_heal += [pokemon] + + if len(self.to_heal) == 0 and len(to_revive) == 0: + if self._should_print: + self.next_update = datetime.now() + timedelta(seconds=120) + #self.logger.info("No pokemon to heal or revive") + return WorkerResult.SUCCESS + # Okay, start reviving pokemons + # Check revives and potions + revives = inventory.items().get(Item.ITEM_REVIVE.value).count + max_revives = inventory.items().get(Item.ITEM_MAX_REVIVE.value).count + normal = inventory.items().get(Item.ITEM_POTION.value).count + super_p = inventory.items().get(Item.ITEM_SUPER_POTION.value).count + hyper = inventory.items().get(Item.ITEM_HYPER_POTION.value).count + max_p = inventory.items().get(Item.ITEM_MAX_POTION.value).count + + self.logger.info("Healing %s pokemon" % len(self.to_heal)) + self.logger.info("Reviving %s pokemon" % len(to_revive)) + + if self.revive_pokemon: + if len(to_revive) > 0 and revives == 0 and max_revives == 0: + self.logger.info("No revives left! Can't revive %s pokemons." % len(to_revive)) + elif len(to_revive) > 0: + self.logger.info("Reviving %s pokemon..." % len(to_revive)) + for pokemon in to_revive: + self._revive_pokemon(pokemon) + + if self.heal_pokemon: + if len(self.to_heal) > 0 and (normal + super_p + hyper + max_p) == 0: + self.logger.info("No potions left! Can't heal %s pokemon" % len(self.to_heal)) + elif len(self.to_heal) > 0: + self.logger.info("Healing %s pokemon" % len(self.to_heal)) + for pokemon in self.to_heal: + self._heal_pokemon(pokemon) + + if self._should_print: + self.next_update = datetime.now() + timedelta(seconds=120) + self.logger.info("Done healing/reviving pokemon") + + def _revive_pokemon(self, pokemon): + item = Item.ITEM_REVIVE.value + amount = inventory.items().get(item).count + if amount == 0: + self.logger.info("No normal revives left, using MAX revive!") + item = Item.ITEM_MAX_REVIVE.value + + amount = inventory.items().get(item).count + if amount > 0: + response_dict_revive = self.bot.api.use_item_revive(item_id=item, pokemon_id=pokemon.unique_id) + action_delay(2, 3) + if response_dict_revive: + result = response_dict_revive.get('responses', {}).get('USE_ITEM_REVIVE', {}).get('result', 0) + revive_item = inventory.items().get(item) + # Remove the revive from the iventory + revive_item.remove(1) + if result is 1: # Request success + self.emit_event( + 'revived_pokemon', + formatted='Revived {name}.', + data={ + 'name': pokemon.name + } + ) + if item == Item.ITEM_REVIVE.value: + pokemon.hp = int(pokemon.hp_max / 2) + self.to_heal.append(pokemon) + else: + # Set pokemon as revived + pokemon.hp = pokemon.hp_max + return True + else: + self.emit_event( + 'revived_pokemon', + level='error', + formatted='Failed to revive {name}!', + data={ + 'name': pokemon.name + } + ) + return False + + def _heal_pokemon(self, pokemon): + if pokemon.hp == 0: + self.logger.info("Can't heal a dead %s" % pokemon.name) + return False + # normal = inventory.items().get(Item.ITEM_POTION.value).count + # super_p = inventory.items().get(Item.ITEM_SUPER_POTION.value).count + # hyper = inventory.items().get(Item.ITEM_HYPER_POTION.value).count + max_p = inventory.items().get(Item.ITEM_MAX_POTION.value).count + # Figure out how much healing needs to be done. + def hp_to_restore(pokemon): + pokemon = inventory.pokemons().get_from_unique_id(pokemon.unique_id) + return pokemon.hp_max - pokemon.hp + + if hp_to_restore(pokemon) > 200 and max_p > 0: + # We should use a MAX Potion + self._use_potion(Item.ITEM_MAX_POTION.value, pokemon) + pokemon.hp = pokemon.hp_max + return True + # Okay, now we see to heal as effective as possible + potions = [103, 102, 101] + heals = [200, 50, 20] + + for item_id, max_heal in zip(potions, heals): + if inventory.items().get(item_id).count > 0: + while hp_to_restore(pokemon) > max_heal: + if inventory.items().get(item_id).count == 0: + break + action_delay(2, 3) + # More than 200 to restore, use a hyper first + if self._use_potion(item_id, pokemon): + pokemon.hp += max_heal + if pokemon.hp > pokemon.hp_max: + pokemon.hp = pokemon.hp_max + else: + break + # return WorkerResult.ERROR + + # Now we use the least + potion_id = 101 # Normals first + while hp_to_restore(pokemon) > 0: + action_delay(2, 4) + if inventory.items().get(potion_id).count > 0: + if potion_id == 104: + self.logger.info("Using MAX potion to heal a %s" % pokemon.name) + if self._use_potion(potion_id, pokemon): + if potion_id == 104: + pokemon.hp = pokemon.hp_max + else: + pokemon.hp += heals[potion_id - 101] + if pokemon.hp > pokemon.hp_max: + pokemon.hp = pokemon.hp_max + else: + if potion_id < 104: + self.logger.info("Failed with potion %s. Trying next." % potion_id) + potion_id += 1 + else: + self.logger.info("Failed with MAX potion. Done.") + break + elif potion_id < 104: + potion_id += 1 + else: + self.logger.info("Can't heal a %s" % pokemon.name) + break + + + def _use_potion(self, potion_id, pokemon): + potion_count = inventory.items().get(potion_id).count + healing = 0 + if potion_count == 0: + return False + if potion_id == 101: + self.logger.info("Healing with a normal potion we have %s left." % (potion_count - 1)) + healing = 20 + if potion_id == 102: + self.logger.info("Healing with a Super potion we have %s left." % (potion_count - 1)) + healing = 50 + if potion_id == 103: + self.logger.info("Healing with a HYper potion we have %s left." % (potion_count - 1)) + healing = 200 + if potion_id == 104: + self.logger.info("Healing with a MAX potion we have %s left." % (potion_count - 1)) + healing = pokemon.hp_max - pokemon.hp + + response_dict_potion = self.bot.api.use_item_potion(item_id=potion_id, pokemon_id=pokemon.unique_id) + # Select potion + sleep(2) + if response_dict_potion: + result = response_dict_potion.get('responses', {}).get('USE_ITEM_POTION', {}).get('result', 0) + if result is 1 or result is 0: # Request success + potion_item = inventory.items().get(potion_id) + # Remove the potion from the iventory + potion_item.remove(1) + self.emit_event( + 'healing_pokemon', + formatted='Healing {name} ({hp} -> {hp_new}/{hp_max}).', + data={ + 'name': pokemon.name, + 'hp': pokemon.hp, + 'hp_new': pokemon.hp + healing, + 'hp_max': pokemon.hp_max + } + ) + return True + elif result == 3: + # ERROR_CANNOT_USE + pokemon.hp = pokemon.hp_max + self.logger.info("Can't use this to heal the %s" % pokemon.name) + return False + else: + self.logger.info("Result was: %s" % result) + self.emit_event( + 'healing_pokemon', + level='error', + formatted='Failed to heal {name} ({hp} -> {hp_new}/{hp_max})!', + data={ + 'name': pokemon.name, + 'hp': pokemon.hp, + 'hp_new': pokemon.hp + healing, + 'hp_max': pokemon.hp_max + } + ) + return False + + def _should_print(self): + """ + Returns a value indicating whether the pokemon should be displayed. + :return: True if the stats should be displayed; otherwise, False. + :rtype: bool + """ + return self.next_update is None or datetime.now() >= self.next_update diff --git a/pokemongo_bot/cell_workers/move_to_fort.py b/pokemongo_bot/cell_workers/move_to_fort.py index 40c4d68658..24a65523b8 100644 --- a/pokemongo_bot/cell_workers/move_to_fort.py +++ b/pokemongo_bot/cell_workers/move_to_fort.py @@ -9,6 +9,7 @@ from pokemongo_bot.base_task import BaseTask from .utils import distance, format_dist, fort_details from datetime import datetime, timedelta +import time class MoveToFort(BaseTask): SUPPORTED_TASK_API_VERSION = 1 @@ -22,6 +23,8 @@ def initialize(self): self.walker = self.config.get('walker', 'StepWalker') self.wait_at_fort = self.config.get('wait_on_lure', False) self.wait_log_sent = None + self.previous_distance = [] + self.target_id = None def should_run(self): has_space_for_loot = inventory.Items.has_space_for_loot() @@ -39,6 +42,9 @@ def work(self): if not self.should_run(): return WorkerResult.SUCCESS + if hasattr(self.bot, "hunter_locked_target") and self.bot.hunter_locked_target is not None: + return WorkerResult.SUCCESS + nearest_fort = self.get_nearest_fort() if nearest_fort is None: @@ -50,6 +56,9 @@ def work(self): details = fort_details(self.bot, fortID, lat, lng) fort_name = details.get('name', 'Unknown') + if self.target_id is None: + self.target_id = fort_name + unit = self.bot.config.distance_unit # Unit to use when printing formatted distance dist = distance( @@ -67,24 +76,64 @@ def work(self): moving = noised_dist > Constants.MAX_DISTANCE_FORT_IS_REACHABLE if self.bot.config.replicate_gps_xy_noise else dist > Constants.MAX_DISTANCE_FORT_IS_REACHABLE + distance_to_target = int(noised_dist if self.bot.config.replicate_gps_xy_noise else dist) + if len(self.previous_distance) == 0: + self.previous_distance.append(distance_to_target) + elif self.target_id is not fort_name: + # self.logger.info("Changed target from %s to %s" % (self.target_id, fort_name)) + self.previous_distance = [distance_to_target] + self.target_id = fort_name + if self.walker is not self.config.get('walker', 'StepWalker'): + self.walker = self.config.get('walker', 'StepWalker') + else: + # self.logger.info("Previous distances: %s" % self.previous_distance) + if len(self.previous_distance) > 5: + self.previous_distance.pop(0) + error_moving = False + times_found = 0 + for prev_distance in self.previous_distance: + if prev_distance == distance_to_target: + error_moving = True + break + + if error_moving: + if self.walker == 'StepWalker': + self.logger.info("Having difficulty walking to %s" % fort_name) + self.bot.recent_forts = self.bot.recent_forts[1:] + [fortID] + return WorkerResult.ERROR + else: + self.logger.info("Having difficulty walking to %s. Changing walker." % fort_name) + self.walker = 'StepWalker' + self.previous_distance = [distance_to_target] + else: + self.previous_distance.append(distance_to_target) + if moving: self.wait_log_sent = None + if "type" in nearest_fort and nearest_fort["type"] == 1: + # It's a Pokestop + target_type = "pokestop" + else: + # It's a gym + target_type = "gym" + fort_event_data = { 'fort_name': u"{}".format(fort_name), 'distance': format_dist(dist, unit), + 'target_type': target_type, } if self.is_attracted() > 0: fort_event_data.update(lure_distance=format_dist(self.lure_distance, unit)) self.emit_event( 'moving_to_lured_fort', - formatted="Moving towards pokestop {fort_name} - {distance} (attraction of lure {lure_distance})", + formatted="Moving towards {target_type} {fort_name} - {distance} (attraction of lure {lure_distance})", data=fort_event_data ) else: self.emit_event( 'moving_to_fort', - formatted="Moving towards pokestop {fort_name} - {distance}", + formatted="Moving towards {target_type} {fort_name} - {distance}", data=fort_event_data ) @@ -178,7 +227,7 @@ def get_nearest_fort(self): if len(forts) >= 3: # Get ID of fort, store it. Check index 0 & index 2. Both must not be same nearest_fort = forts[0] - + if len(self.fort_ids) < 3: self.fort_ids.extend(nearest_fort['id']) else: @@ -191,7 +240,7 @@ def get_nearest_fort(self): else: self.fort_ids.pop(0) self.fort_ids.extend(nearest_fort['id']) - + return nearest_fort else: return None diff --git a/pokemongo_bot/cell_workers/spin_fort.py b/pokemongo_bot/cell_workers/spin_fort.py index e47d20f1a7..1f7d971403 100644 --- a/pokemongo_bot/cell_workers/spin_fort.py +++ b/pokemongo_bot/cell_workers/spin_fort.py @@ -19,6 +19,7 @@ SPIN_REQUEST_RESULT_OUT_OF_RANGE = 2 SPIN_REQUEST_RESULT_IN_COOLDOWN_PERIOD = 3 SPIN_REQUEST_RESULT_INVENTORY_FULL = 4 +SPIN_REQUEST_RESULT_POI_INACCESSIBLE = 5 LURE_REQUEST_RESULT_SUCCESS = 1 LURE_REQUEST_FORT_ALREADY_HAS_MODIFIER= 2 @@ -35,6 +36,8 @@ def __init__(self, bot, config): def initialize(self): # 10 seconds from current time self.next_update = datetime.now() + timedelta(0, 10) + self.fort_spins = 0 + self.streak_forts = [] self.ignore_item_count = self.config.get("ignore_item_count", False) self.spin_wait_min = self.config.get("spin_wait_min", 2) @@ -42,6 +45,10 @@ def initialize(self): self.min_interval = int(self.config.get('min_interval', 120)) self.exit_on_limit_reached = self.config.get("exit_on_limit_reached", True) self.use_lure = self.config.get("use_lure", False) + self.try_to_keep_streak = self.config.get("try_to_keep_streak", True) + + # if self.try_to_keep_streak and len(self.bot.recent_forts) is not 10: + # self.logger.warn("You enabled the setting for keeping a 10 stop streak, but the number of recent forts is not set to 10! It is set to %s. This will cause the streak to fail!" % len(self.bot.recent_forts)) def should_run(self): has_space_for_loot = inventory.Items.has_space_for_loot() @@ -74,27 +81,36 @@ def work(self): fort = forts[0] + if fort['id'] in self.streak_forts: + self.fort_spins = 1 + self.streak_forts = [fort['id']] + elif self.fort_spins >= 10: + self.fort_spins = 1 + self.streak_forts = [fort['id']] + else: + self.fort_spins += 1 + lat = fort['latitude'] lng = fort['longitude'] details = fort_details(self.bot, fort['id'], lat, lng) - fort_name = details.get('name', 'Unknown') + fort_name = details.get('name', 'Unknown') check_fort_modifier = details.get('modifiers', {}) - if check_fort_modifier: + if self.use_lure and check_fort_modifier: # check_fort_modifier_id = check_fort_modifier[0].get('item_id') self.emit_event('lure_info', formatted='A lure is already in fort, skip deploying lure') - + if self.use_lure and not check_fort_modifier: # check lures availiblity lure_count = inventory.items().get(501).count - + if lure_count > 1: # Only use lures when there's more than one request = self.bot.api.create_request() request.add_fort_modifier( modifier_type=501, - fort_id = fort['id'], - player_latitude = f2i(self.bot.position[0]), + fort_id = fort['id'], + player_latitude = f2i(self.bot.position[0]), player_longitude = f2i(self.bot.position[1]) ) response_dict = request.call() @@ -114,7 +130,7 @@ def work(self): self.emit_event('lure_info', formatted='Unkown Error') else: self.emit_event('lure_not_enough', formatted='Not enough lure in inventory') - + request = self.bot.api.create_request() request.fort_search( fort_id=fort['id'], @@ -124,7 +140,7 @@ def work(self): player_longitude=f2i(self.bot.position[1]) ) response_dict = request.call() - + if ('responses' in response_dict) and ('FORT_SEARCH' in response_dict['responses']): spin_details = response_dict['responses']['FORT_SEARCH'] spin_result = spin_details.get('result', -1) @@ -134,20 +150,39 @@ def work(self): experience_awarded = spin_details.get('experience_awarded', 0) items_awarded = self.get_items_awarded_from_fort_spinned(response_dict) egg_awarded = spin_details.get('pokemon_data_egg', None) + gym_badge_awarded = spin_details.get('awarded_gym_badge', None) + chain_hack_sequence_number = spin_details.get('chain_hack_sequence_number', 0) if egg_awarded is not None: items_awarded[u'Egg'] = egg_awarded['egg_km_walked_target'] + # if gym_badge_awarded is not None: + # self.logger.info("Gained a Gym Badge! %s" % gym_badge_awarded) + # + # if chain_hack_sequence_number > 0: + # self.logger.info("Chain hack sequence: %s" % chain_hack_sequence_number) + if experience_awarded or items_awarded: awards = ', '.join(["{}x {}".format(items_awarded[x], x) for x in items_awarded if x != u'Egg']) if egg_awarded is not None: awards += u', {} Egg'.format(egg_awarded['egg_km_walked_target']) + self.fort_spins = chain_hack_sequence_number + + if "type" in fort and fort["type"] == 1: + # It's a Pokestop + stop_kind = "pokestop" + else: + # It's a gym + stop_kind = "gym" + self.emit_event( 'spun_pokestop', - formatted="Spun pokestop {pokestop}. Experience awarded: {exp}. Items awarded: {items}", + formatted="Spun {stop_kind} {pokestop} ({spin_amount_now} streak). Experience awarded: {exp}. Items awarded: {items}", data={ + 'stop_kind': stop_kind, 'pokestop': fort_name, 'exp': experience_awarded, + 'spin_amount_now': chain_hack_sequence_number, 'items': awards } ) @@ -196,6 +231,9 @@ def work(self): formatted="Pokestop {pokestop} on cooldown. Time left: {minutes_left}.", data={'pokestop': fort_name, 'minutes_left': minutes_left} ) + elif spin_result == SPIN_REQUEST_RESULT_POI_INACCESSIBLE: + self.logger.info("Pokestop not accessable at this time.") + self.bot.fort_timeouts[fort["id"]] = (time.time() + 300) * 1000 # Don't spin for 5m else: self.emit_event( 'unknown_spin_result', @@ -224,7 +262,7 @@ def work(self): result = c.fetchone() if result[0] == 1: - source = str("PokemonCatchWorker") + source = str("SpinFort") status = str("Possible Softban") conn.execute('''INSERT INTO softban_log (status, source) VALUES (?, ?)''', (status, source)) else: @@ -246,6 +284,14 @@ def work(self): def get_forts_in_range(self): forts = self.bot.get_forts(order_by_distance=True) forts = filter(lambda fort: fort["id"] not in self.bot.fort_timeouts, forts) + if hasattr(self.bot, "camping_forts") and self.bot.camping_forts and self.try_to_keep_streak: + if datetime.now() >= self.next_update: + self.logger.info("Camping forts, ignoring 10 stops streak.") + elif self.try_to_keep_streak: + if len(self.streak_forts) > 10: + self.streak_forts.pop() + # Remove all forts which were spun in the last ticks to keep 10 stops streak + forts = filter(lambda x: x["id"] not in self.streak_forts, forts) if self.bot.config.replicate_gps_xy_noise: forts = filter(lambda fort: distance( diff --git a/pokemongo_bot/cell_workers/utils.py b/pokemongo_bot/cell_workers/utils.py index b0112a0958..25a8f6a9ce 100644 --- a/pokemongo_bot/cell_workers/utils.py +++ b/pokemongo_bot/cell_workers/utils.py @@ -26,8 +26,28 @@ def fort_details(bot, fort_id, latitude, longitude): """ Lookup fort metadata and (if possible) serve from cache. """ - + first_call = False if fort_id not in FORT_CACHE: + if distance(latitude, longitude, bot.position[0], bot.position[1]) > 1000: + # Fort too far away to get the details! + FORT_CACHE[fort_id] = dict() + first_call = True + else: + """ + Lookup the fort details and cache the response for future use. + """ + request = bot.api.create_request() + request.fort_details(fort_id=fort_id, latitude=latitude, longitude=longitude) + try: + response_dict = request.call() + FORT_CACHE[fort_id] = response_dict['responses']['FORT_DETAILS'] + first_call = True + except Exception: + FORT_CACHE[fort_id] = dict() + first_call = True + + if not first_call and FORT_CACHE.get(fort_id, dict()) == dict(): + if distance(latitude, longitude, bot.position[0], bot.position[1]) < 1000: """ Lookup the fort details and cache the response for future use. """ @@ -37,7 +57,7 @@ def fort_details(bot, fort_id, latitude, longitude): response_dict = request.call() FORT_CACHE[fort_id] = response_dict['responses']['FORT_DETAILS'] except Exception: - pass + FORT_CACHE[fort_id] = dict() # Just to avoid KeyErrors return FORT_CACHE.get(fort_id, {}) @@ -133,14 +153,14 @@ def getSeconds(strTime): try: x = dt.strptime(strTime, '%H:%M:%S') seconds = int(timedelta(hours=x.hour,minutes=x.minute,seconds=x.second).total_seconds()) - except ValueError: + except ValueError: seconds = 0; - + if seconds < 0: seconds = 0; - + return seconds - + def format_time(seconds): # Return a string displaying the time given as seconds or minutes num, duration = 0, long(round(seconds)) diff --git a/pokemongo_bot/event_handlers/logging_handler.py b/pokemongo_bot/event_handlers/logging_handler.py index c873cc7a0f..22db8f75fe 100644 --- a/pokemongo_bot/event_handlers/logging_handler.py +++ b/pokemongo_bot/event_handlers/logging_handler.py @@ -128,7 +128,11 @@ class LoggingHandler(EventHandler): 'threw_pokeball': 'none', 'used_lucky_egg': 'none', 'catch_limit_on': 'yellow', - 'catch_limit_off': 'green' + 'catch_limit_off': 'green', + 'revived_pokemon': 'green', + 'healing_pokemon': 'green', + 'deployed_pokemon': 'green', + 'gym_error': 'red' } COLOR_CODE = { 'gray': '\033[90m', diff --git a/pokemongo_bot/inventory.py b/pokemongo_bot/inventory.py index 52bb00025e..53a56380b0 100644 --- a/pokemongo_bot/inventory.py +++ b/pokemongo_bot/inventory.py @@ -522,6 +522,11 @@ def add(self, pokemon): raise ValueError("Pokemon already present in the inventory") self._data[pokemon.unique_id] = pokemon + def get_from_unique_id(self, pokemon_unique_id): + if pokemon_unique_id not in self._data: + raise ValueError("Pokemon not present in the inventory") + return self._data[pokemon_unique_id] + def remove(self, pokemon_unique_id): if pokemon_unique_id not in self._data: raise ValueError("Pokemon not present in the inventory") @@ -999,6 +1004,9 @@ def __init__(self, data): self.nickname = self.nickname_raw or self.name self.in_fort = 'deployed_fort_id' in data + if 'deployed_fort_id' in data: + self.fort_id = data['deployed_fort_id'] + 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) @@ -1394,11 +1402,12 @@ def retrieve_inventories_size(self): # STAB (Same-type attack bonus) # Factor applied to attack of the same type as pokemon -STAB_FACTOR = 1.25 +STAB_FACTOR = 1.2 # Factor applied to attack when it's effective against defending pokemon type -EFFECTIVENESS_FACTOR = 1.25 +EFFECTIVENESS_FACTOR = 1.4 # Factor applied to attack when it's weak against defending pokemon type -RESISTANCE_FACTOR = 0.8 +RESISTANCE_FACTOR = 0.714 +IMMUNITY_FACTOR = 0.51 _inventory = None # type: Inventory diff --git a/requirements.txt b/requirements.txt index d517615426..f76d0fc5f5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ numpy==1.11.0 networkx==1.11 six==1.10 -git+https://github.com/pogodevorg/pgoapi.git@develop#egg=pgoapi +git+https://github.com/goedzo/pgoapi.git@develop#egg=pgoapi geopy==1.11.0 geographiclib==1.46.3 requests==2.10.0 diff --git a/tests/inventory_test.py b/tests/inventory_test.py index 0a236daf42..7b18342984 100644 --- a/tests/inventory_test.py +++ b/tests/inventory_test.py @@ -133,9 +133,9 @@ def test_pokemons(self): self.assertEqual(poke.nickname, "Golb") self.assertEqual(poke.nickname_raw, poke.nickname) self.assertAlmostEqual(poke.moveset.dps,15.130190007037298 ) - self.assertAlmostEqual(poke.moveset.dps_attack, 16.5376495425756) + self.assertAlmostEqual(poke.moveset.dps_attack, 16.256157635467982) self.assertAlmostEqual(poke.moveset.dps_defense, 6.377929397804805 ) - self.assertAlmostEqual(poke.moveset.attack_perfection, 0.1976822769744798) + self.assertAlmostEqual(poke.moveset.attack_perfection, 0.15433958252117524) self.assertAlmostEqual(poke.moveset.defense_perfection, 0.62438387986335) poke = Pokemon({ @@ -153,9 +153,9 @@ def test_pokemons(self): self.assertEqual(poke.nickname, poke.name) self.assertEqual(poke.nickname_raw, '') self.assertAlmostEqual(poke.moveset.dps, 17.333333333333332 ) - self.assertAlmostEqual(poke.moveset.dps_attack, 21.666666666666668) + self.assertAlmostEqual(poke.moveset.dps_attack, 20.8) self.assertAlmostEqual(poke.moveset.dps_defense, 4.814814814814815) - self.assertAlmostEqual(poke.moveset.attack_perfection, 0.777011494252873) + self.assertAlmostEqual(poke.moveset.attack_perfection, 0.7607623318385653) self.assertAlmostEqual(poke.moveset.defense_perfection, 0.08099928856783224) def test_levels_to_cpm(self): diff --git a/tests/nickname_test.py b/tests/nickname_test.py index a547594e3a..34d0cb733e 100644 --- a/tests/nickname_test.py +++ b/tests/nickname_test.py @@ -37,9 +37,9 @@ def test_nickname_generation(self): self.assertNicks('{fast_attack_char}', ['l', 'n']) self.assertNicks('{charged_attack_char}', ['h', 'N']) self.assertNicks('{attack_code}', ['lh', 'nN']) - self.assertNicks('{attack_pct}', ['020', '078']) - self.assertNicks('{attack_pct2}', ['20', '77']) - self.assertNicks('{attack_pct1}', ['2', '7']) + self.assertNicks('{attack_pct}', ['015', '076']) + self.assertNicks('{attack_pct2}', ['15', '75']) + self.assertNicks('{attack_pct1}', ['1', '7']) self.assertNicks('{defense_pct}', ['062', '008']) self.assertNicks('{defense_pct2}', ['62', '08']) self.assertNicks('{defense_pct1}', ['6', '1']) @@ -62,7 +62,7 @@ def test_nickname_generation(self): ['48_46_948', '38_44_6E0']) self.assertNicks( '{attack_code}{attack_pct1}{defense_pct1}{ivcp_pct1}{name}', - ['lh264Golbat', 'nN713Rattata']) + ['lh164Golbat', 'nN713Rattata']) # def setUp(self):