diff --git a/configs/config.json.example b/configs/config.json.example index 70d9b4a4dc..f9692f11e7 100644 --- a/configs/config.json.example +++ b/configs/config.json.example @@ -200,6 +200,7 @@ "enabled": true, "catch_visible_pokemon": true, "catch_lured_pokemon": true, + "catch_incensed_pokemon": true, "min_ultraball_to_keep": 5, "berry_threshold": 0.35, "vip_berry_threshold": 0.9, diff --git a/docs/configuration_files.md b/docs/configuration_files.md index 69124a0b44..9b0034245d 100644 --- a/docs/configuration_files.md +++ b/docs/configuration_files.md @@ -161,6 +161,7 @@ The behaviors of the bot are configured via the `tasks` key in the `config.json` * `treat_unseen_as_vip`: Default `"true"` | If true, treat new to dex as VIP * `catch_visible_pokemon`: Default "true" | If enabled, attempts to catch "visible" pokemon that are reachable * `catch_lured_pokemon`: Default "true" | If enabled, attempts to catch "lured" pokemon that are reachable + * `catch_incensed_pokemon`: Default "true" | If enabled, attempts to catch pokemon that are found because of an active incense * `min_ultraball_to_keep`: Default 5 | Minimum amount of reserved ultraballs to have on hand (for VIP) * `berry_threshold`: Default 0.35 | Catch percentage we start throwing berries * `vip_berry_threshold`: Default 0.9 | Something similar? diff --git a/pokemongo_bot/__init__.py b/pokemongo_bot/__init__.py index a2f6e6401e..d39b1bc366 100644 --- a/pokemongo_bot/__init__.py +++ b/pokemongo_bot/__init__.py @@ -372,6 +372,16 @@ def _register_events(self): 'pokemon_name' ) ) + self.event_manager.register_event( + 'incensed_pokemon_found', + parameters=( + 'pokemon_id', + 'encounter_id', + 'encounter_location', + 'latitude', + 'longitude' + ) + ) self.event_manager.register_event( 'pokemon_appeared', parameters=( diff --git a/pokemongo_bot/cell_workers/catch_pokemon.py b/pokemongo_bot/cell_workers/catch_pokemon.py index ab6b56e1af..77adf173dc 100644 --- a/pokemongo_bot/cell_workers/catch_pokemon.py +++ b/pokemongo_bot/cell_workers/catch_pokemon.py @@ -10,11 +10,10 @@ from pokemongo_bot.worker_result import WorkerResult from pokemongo_bot.item_list import Item from pokemongo_bot import inventory -from utils import fort_details, distance +from utils import fort_details, distance, format_time from pokemongo_bot.base_dir import _base_dir from pokemongo_bot.constants import Constants -from pokemongo_bot.inventory import Pokemons, Pokemon, Attack - +from pokemongo_bot.inventory import Pokemons class CatchPokemon(BaseTask): SUPPORTED_TASK_API_VERSION = 1 @@ -35,6 +34,8 @@ def work(self): self.get_visible_pokemon() if self.config.get('catch_lured_pokemon', True): self.get_lured_pokemon() + if self._have_applied_incense() and self.config.get('catch_incensed_pokemon', True): + self.get_incensed_pokemon() random.shuffle(self.pokemon) @@ -60,17 +61,17 @@ def get_visible_pokemon(self): pokemon_to_catch = self.bot.cell['catchable_pokemons'] if len(pokemon_to_catch) > 0: - user_web_catchable = os.path.join(_base_dir, 'web', 'catchable-{}.json'.format(self.bot.config.username)) - for pokemon in pokemon_to_catch: - - # Update web UI - with open(user_web_catchable, 'w') as outfile: + user_web_catchable = os.path.join(_base_dir, 'web', 'catchable-{}.json'.format(self.bot.config.username)) + for pokemon in pokemon_to_catch: + # Update web UI + with open(user_web_catchable, 'w') as outfile: json.dump(pokemon, outfile) - self.emit_event( - 'catchable_pokemon', - level='debug', - data={ + + self.emit_event( + 'catchable_pokemon', + level='debug', + data={ 'pokemon_id': pokemon['pokemon_id'], 'spawn_point_id': pokemon['spawn_point_id'], 'encounter_id': pokemon['encounter_id'], @@ -81,7 +82,7 @@ def get_visible_pokemon(self): } ) - self.add_pokemon(pokemon) + self.add_pokemon(pokemon) if 'wild_pokemons' in self.bot.cell: for pokemon in self.bot.cell['wild_pokemons']: @@ -132,6 +133,22 @@ def get_lured_pokemon(self): self.add_pokemon(pokemon) + def get_incensed_pokemon(self): + # call self.bot.api.get_incense_pokemon + pokemon_to_catch = self.bot.api.get_incense_pokemon() + + if len(pokemon_to_catch) > 0: + for pokemon in pokemon_to_catch: + self.logger.warning("Pokemon: %s", pokemon) + self.emit_event( + 'incensed_pokemon_found', + level='info', + formatted='Incense attracted a pokemon at {encounter_location}', + data=pokemon + ) + + self.add_pokemon(pokemon) + def add_pokemon(self, pokemon): if pokemon['encounter_id'] not in self.pokemon: self.pokemon.append(pokemon) @@ -141,3 +158,15 @@ def catch_pokemon(self, pokemon): return_value = worker.work() return return_value + + def _have_applied_incense(self): + for applied_item in inventory.applied_items().all(): + self.logger.info(applied_item) + if applied_item.expire_ms > 0: + mins = format_time(applied_item.expire_ms * 1000) + self.logger.info("Not applying incense, currently active: %s, %s minutes remaining", applied_item.item.name, mins) + return True + else: + self.logger.info("") + return False + return False diff --git a/pokemongo_bot/cell_workers/pokemon_catch_worker.py b/pokemongo_bot/cell_workers/pokemon_catch_worker.py index 759d9a99b2..6073678e88 100644 --- a/pokemongo_bot/cell_workers/pokemon_catch_worker.py +++ b/pokemongo_bot/cell_workers/pokemon_catch_worker.py @@ -4,7 +4,6 @@ import time import json import logging -import time import sys from random import random, randrange, uniform @@ -25,6 +24,8 @@ ENCOUNTER_STATUS_SUCCESS = 1 ENCOUNTER_STATUS_NOT_IN_RANGE = 5 ENCOUNTER_STATUS_POKEMON_INVENTORY_FULL = 7 +INCENSE_ENCOUNTER_AVAILABLE = 1 +INCENSE_ENCOUNTER_NOT_AVAILABLE = 2 ITEM_POKEBALL = 1 ITEM_GREATBALL = 2 @@ -105,9 +106,11 @@ def work(self, response_dict=None): try: responses = response_dict['responses'] response = responses[self.response_key] - if response[self.response_status_key] != ENCOUNTER_STATUS_SUCCESS: + if response[self.response_status_key] != ENCOUNTER_STATUS_SUCCESS and response[self.response_status_key] != INCENSE_ENCOUNTER_AVAILABLE: if response[self.response_status_key] == ENCOUNTER_STATUS_NOT_IN_RANGE: self.emit_event('pokemon_not_in_range', formatted='Pokemon went out of range!') + elif response[self.response_status_key] == INCENSE_ENCOUNTER_NOT_AVAILABLE: + self.emit_event('pokemon_not_in_range', formatted='Incensed Pokemon went out of range!') elif response[self.response_status_key] == ENCOUNTER_STATUS_POKEMON_INVENTORY_FULL: self.emit_event('pokemon_inventory_full', formatted='Your Pokemon inventory is full! Could not catch!') return WorkerResult.ERROR @@ -122,14 +125,14 @@ def work(self, response_dict=None): if not self._should_catch_pokemon(pokemon): if not hasattr(self.bot,'skipped_pokemon'): self.bot.skipped_pokemon = [] - + # Check if pokemon already skipped and suppress alert if so for skipped_pokemon in self.bot.skipped_pokemon: if pokemon.pokemon_id == skipped_pokemon.pokemon_id and \ pokemon.cp_exact == skipped_pokemon.cp_exact and \ pokemon.ivcp == skipped_pokemon.ivcp: return WorkerResult.SUCCESS - + self.bot.skipped_pokemon.append(pokemon) self.emit_event( 'pokemon_appeared', @@ -215,7 +218,7 @@ def create_encounter_api_call(self): player_latitude=player_latitude, player_longitude=player_longitude ) - else: + elif 'fort_id' in self.pokemon: fort_id = self.pokemon['fort_id'] self.spawn_point_guid = fort_id self.response_key = 'DISK_ENCOUNTER' @@ -226,6 +229,14 @@ def create_encounter_api_call(self): player_latitude=player_latitude, player_longitude=player_longitude ) + else: + # This must be a incensed mon + self.response_key = 'INCENSE_ENCOUNTER' + self.response_status_key = 'result' + request.incense_encounter( + encounter_id=encounter_id, + encounter_location=self.pokemon['encounter_location'] + ) return request.call() ############################################################################ @@ -716,7 +727,7 @@ def generate_throw_quality_parameters(self, throw_parameters): def start_rest(self): duration = int(uniform(self.rest_duration_min, self.rest_duration_max)) resume = datetime.now() + timedelta(seconds=duration) - + self.emit_event( 'vanish_limit_reached', formatted="Vanish limit reached! Taking a rest now for {duration}, will resume at {resume}.", @@ -725,7 +736,7 @@ def start_rest(self): 'resume': resume.strftime("%H:%M:%S") } ) - + sleep(duration) self.rest_completed = True self.bot.login() diff --git a/pokemongo_bot/cell_workers/use_incense.py b/pokemongo_bot/cell_workers/use_incense.py index dfb99e82cb..e569dbdd2e 100644 --- a/pokemongo_bot/cell_workers/use_incense.py +++ b/pokemongo_bot/cell_workers/use_incense.py @@ -3,81 +3,91 @@ from pokemongo_bot.worker_result import WorkerResult from pokemongo_bot.item_list import Item from pokemongo_bot import inventory +from utils import format_time + class UseIncense(BaseTask): SUPPORTED_TASK_API_VERSION = 1 def initialize(self): - self.start_time = 0 - self.use_incense = self.config.get('use_incense', False) - self.use_order = self.config.get('use_order', {}) - self._update_inventory() - - self.types = { - 401: "Ordinary", - 402: "Spicy", - 403: "Cool", - 404: "Floral" + self.start_time = 0 + self.use_incense = self.config.get('use_incense', False) + self.use_order = self.config.get('use_order', {}) + self._update_inventory() + + self.types = { + 401: "Ordinary", + 402: "Spicy", + 403: "Cool", + 404: "Floral" } - + + def _have_applied_incense(self): + for applied_item in inventory.applied_items().all(): + if applied_item.expire_ms > 0: + mins = format_time(applied_item.expire_ms * 1000) + self.logger.info("Not applying incense, currently active: %s, %s minutes remaining", applied_item.item.name, mins) + return False + else: + return True + def _get_type(self): - for order in self.use_order: - if order == "ordinary" and self.incense_ordinary_count > 0: - return Item.ITEM_INCENSE_ORDINARY.value - if order == "spicy" and self.incense_spicy_count > 0: - return Item.ITEM_INCENSE_SPICY.value - if order == "cool" and self.incense_cool_count > 0: - return Item.ITEM_INCENSE_COOL.value - if order == "floral" and self.incense_floral_count > 0: - return Item.ITEM_INCENSE_FLORAL.value - - return Item.ITEM_INCENSE_ORDINARY.value - + for order in self.use_order: + if order == "ordinary" and self.incense_ordinary_count > 0: + return Item.ITEM_INCENSE_ORDINARY.value + if order == "spicy" and self.incense_spicy_count > 0: + return Item.ITEM_INCENSE_SPICY.value + if order == "cool" and self.incense_cool_count > 0: + return Item.ITEM_INCENSE_COOL.value + if order == "floral" and self.incense_floral_count > 0: + return Item.ITEM_INCENSE_FLORAL.value + + return Item.ITEM_INCENSE_ORDINARY.value + def _update_inventory(self): - self.incense_ordinary_count = inventory.items().get(Item.ITEM_INCENSE_ORDINARY.value).count - self.incense_spicy_count = inventory.items().get(Item.ITEM_INCENSE_SPICY.value).count - self.incense_cool_count = inventory.items().get(Item.ITEM_INCENSE_COOL.value).count - self.incense_floral_count = inventory.items().get(Item.ITEM_INCENSE_FLORAL.value).count - + self.incense_ordinary_count = inventory.items().get(Item.ITEM_INCENSE_ORDINARY.value).count + self.incense_spicy_count = inventory.items().get(Item.ITEM_INCENSE_SPICY.value).count + self.incense_cool_count = inventory.items().get(Item.ITEM_INCENSE_COOL.value).count + self.incense_floral_count = inventory.items().get(Item.ITEM_INCENSE_FLORAL.value).count + def _has_count(self): - return self.incense_ordinary_count > 0 or self.incense_spicy_count > 0 or self.incense_cool_count > 0 or self.incense_floral_count > 0 - + return self.incense_ordinary_count > 0 or self.incense_spicy_count > 0 or self.incense_cool_count > 0 or self.incense_floral_count > 0 + def _should_run(self): - if not self.use_incense: - return False + if self._have_applied_incense: + return False + + if not self.use_incense: + return False + + if self._has_count() > 0 and self.start_time == 0: + return True - if self._has_count() > 0 and self.start_time == 0: - return True - - using_incense = time.time() - self.start_time < 1800 - if not using_incense: self._update_inventory() if self._has_count() and self.use_incense: - return True + return True def work(self): - if self._should_run(): - self.start_time = time.time() - type = self._get_type() - response_dict = self.bot.api.use_incense(incense_type=type) - result = response_dict.get('responses', {}).get('USE_INCENSE', {}).get('result', 0) - if result is 1: - self.emit_event( - 'use_incense', - formatted="Using {type} incense. {incense_count} incense remaining", - data={ - 'type': self.types.get(type, 'Unknown'), - 'incense_count': inventory.items().get(type).count - } - ) - else: - self.emit_event( - 'use_incense', - formatted="Unable to use incense {type}. {incense_count} incense remaining", - data={ - 'type': self.types.get(type, 'Unknown'), - 'incense_count': inventory.items().get(type).count - } - ) - - return WorkerResult.SUCCESS + if self._should_run(): + self.start_time = time.time() + response_dict = self.bot.api.use_incense(incense_type=self._get_type()) + result = response_dict.get('responses', {}).get('USE_INCENSE', {}).get('result', 0) + if result is 1: + self.emit_event( + 'use_incense', + formatted="Using {type} incense. {incense_count} incense remaining", + data={ + 'type': self.types.get(type, 'Unknown'), + 'incense_count': inventory.items().get(type).count + } + ) + else: + self.emit_event( + 'use_incense', + formatted="Unable to use incense {type}. {incense_count} incense remaining", + data={ + 'type': self.types.get(type, 'Unknown'), + 'incense_count': inventory.items().get(type).count + } + ) + return WorkerResult.SUCCESS diff --git a/pokemongo_bot/inventory.py b/pokemongo_bot/inventory.py index e39ec75f40..60ad365a70 100644 --- a/pokemongo_bot/inventory.py +++ b/pokemongo_bot/inventory.py @@ -319,6 +319,85 @@ def has_space_for_loot(cls): max_number_of_items_looted_at_stop = 5 return cls.get_space_left() >= max_number_of_items_looted_at_stop +class AppliedItem(object): + """ + Representation of an applied item, like incense. + """ + def __init__(self, item_id, expire_ms, applied_ms): + """ + Representation of an applied item + :param item_id: ID of the item + :type item_id: int + :param expire_ms: expire in ms + :type expire_ms: in + :param applied_ms: applied at + :type applied_ms: int + :return: An applied item + :rtype: AppliedItemItem + """ + self.id = item_id + self.name = Items.name_for(self.id) + self.applied_ms = applied_ms + self.expire_ms = expire_ms + + def refresh(self,inventory): + self.retrieve_data(inventory) + + def parse(self, item): + if not item: + item = {} + + self.id = item.get('id', 0) + self.name = Items.name_for(self.id) + self.expire_ms = item.get('expire_ms', 0) + self.applied_ms = item.get('applied_ms', 0) + + def retrieve_data(self, inventory): + ret = {} + for item in inventory: + data = item['inventory_item_data'] + if self.TYPE in data: + item = data[self.TYPE] + ret = item + self.parse(item) + + return ret + + def __str__(self): + return self.name + + +class AppliedItems(_BaseInventoryComponent): + TYPE='applied_items' + ID_FIELD = 'item_id' + STATIC_DATA_FILE = os.path.join(_base_dir, 'data', 'items.json') + + def all(self): + """ + Get EVERY Item from the cached inventory. + :return: List of evey item in the cached inventory + :rtype: list of Item + """ + return list(self._data.values()) + + def get(self, item_id): + """ + Get ONE Item from the cached inventory. + :param item_id: Item's ID to search for. + :return: Instance of the item from the cached inventory + :rtype: Item + """ + return self._data.setdefault(item_id, Item(item_id, 0)) + + @classmethod + def name_for(cls, item_id): + """ + Search the name for an item from its ID. + :param item_id: Item's ID to search for. + :return: Item's name. + :rtype: str + """ + return cls.STATIC_DATA[str(item_id)] class Pokemons(_BaseInventoryComponent): @@ -1174,6 +1253,7 @@ def __init__(self, bot): self.pokedex = Pokedex() self.candy = Candies() self.items = Items() + self.applied_items = AppliedItems() self.pokemons = Pokemons() self.player = Player(self.bot) # include inventory inside Player? self.egg_incubators = None @@ -1189,6 +1269,7 @@ def refresh(self, inventory=None): for i in (self.pokedex, self.candy, self.items, self.pokemons, self.player): i.refresh(inventory) + # self.applied_items = [x["inventory_item_data"] for x in inventory if "applied_items" in x["inventory_item_data"]] self.egg_incubators = [x["inventory_item_data"] for x in inventory if "egg_incubators" in x["inventory_item_data"]] self.update_web_inventory() @@ -1241,6 +1322,9 @@ def jsonify_inventory(self): for inc in self.egg_incubators: json_inventory.append({"inventory_item_data": inc}) + # for item in self.applied_items: + # json_inventory.append({"inventory_applied_item_data": {"applied_item": {"item_id": item.item_id, "applied_ms": item.applied_ms, "expire_ms": item.expire_ms}}}) + return json_inventory def retrieve_inventories_size(self): @@ -1415,6 +1499,13 @@ def items(): """ return _inventory.items +def applied_items(): + """ + Access to the cached applied item inventory. + :return: Instance of the cached applied item inventory. + :rtype: Items + """ + return _inventory.applied_items def types_data(): """