From 4b753afdf1dfd1d50c38aa78a8d17496d8aea730 Mon Sep 17 00:00:00 2001 From: SAUNIER DEBES Brice Date: Fri, 12 Aug 2016 13:02:43 +0200 Subject: [PATCH 1/8] Ignoring compiled test python file of PyCharm --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 06973c1249..b1897b0627 100644 --- a/.gitignore +++ b/.gitignore @@ -101,6 +101,7 @@ share/ # PyCharm IDE settings .idea/ *.iml +out/ # Personal load details src/ From 20bff34a7262372ab138fc939be814b446801ac5 Mon Sep 17 00:00:00 2001 From: SAUNIER DEBES Brice Date: Fri, 12 Aug 2016 19:00:31 +0200 Subject: [PATCH 2/8] Now spinning fort keeps track of cached inventory --- pokemongo_bot/cell_workers/move_to_fort.py | 3 +- pokemongo_bot/cell_workers/spin_fort.py | 63 +++++++++++++++------- 2 files changed, 45 insertions(+), 21 deletions(-) diff --git a/pokemongo_bot/cell_workers/move_to_fort.py b/pokemongo_bot/cell_workers/move_to_fort.py index 24ecf5e74a..dd16240279 100644 --- a/pokemongo_bot/cell_workers/move_to_fort.py +++ b/pokemongo_bot/cell_workers/move_to_fort.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals +from pokemongo_bot import inventory from pokemongo_bot.constants import Constants from pokemongo_bot.step_walker import StepWalker from pokemongo_bot.worker_result import WorkerResult @@ -22,7 +23,7 @@ def initialize(self): self.ignore_item_count = True def should_run(self): - has_space_for_loot = self.bot.has_space_for_loot() + has_space_for_loot = inventory.items().has_space_for_loot() if not has_space_for_loot and not self.ignore_item_count: self.emit_event( 'inventory_full', diff --git a/pokemongo_bot/cell_workers/spin_fort.py b/pokemongo_bot/cell_workers/spin_fort.py index 61d3eb02bd..0016cb874f 100644 --- a/pokemongo_bot/cell_workers/spin_fort.py +++ b/pokemongo_bot/cell_workers/spin_fort.py @@ -1,9 +1,11 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals +import json import time from pgoapi.utilities import f2i +from pokemongo_bot import inventory from pokemongo_bot.constants import Constants from pokemongo_bot.human_behaviour import sleep @@ -11,6 +13,11 @@ from pokemongo_bot.base_task import BaseTask from utils import distance, format_time, fort_details +SPIN_REQUEST_RESULT_SUCCESS = 1 +SPIN_REQUEST_RESULT_OUT_OF_RANGE = 2 +SPIN_REQUEST_RESULT_IN_COOLDOWN_PERIOD = 3 +SPIN_REQUEST_RESULT_INVENTORY_FULL = 4 + class SpinFort(BaseTask): SUPPORTED_TASK_API_VERSION = 1 @@ -19,12 +26,13 @@ def initialize(self): self.ignore_item_count = self.config.get("ignore_item_count", False) def should_run(self): - if not self.bot.has_space_for_loot() and not self.ignore_item_count: + has_space_for_loot = inventory.items().has_space_for_loot() + if not has_space_for_loot and not self.ignore_item_count: self.emit_event( 'inventory_full', formatted="Inventory is full. You might want to change your config to recycle more items if this message appears consistently." ) - return self.ignore_item_count or self.bot.has_space_for_loot() + return self.ignore_item_count or has_space_for_loot def work(self): forts = self.get_forts_in_range() @@ -47,25 +55,16 @@ def work(self): player_latitude=f2i(self.bot.position[0]), player_longitude=f2i(self.bot.position[1]) ) - if 'responses' in response_dict and \ - 'FORT_SEARCH' in response_dict['responses']: + 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) - if spin_result == 1: + if spin_result == SPIN_REQUEST_RESULT_SUCCESS: self.bot.softban = False experience_awarded = spin_details.get('experience_awarded', 0) - items_awarded = spin_details.get('items_awarded', {}) - if items_awarded: - self.bot.latest_inventory = None - tmp_count_items = {} - for item in items_awarded: - item_id = item['item_id'] - item_name = self.bot.item_list[str(item_id)] - if not item_name in tmp_count_items: - tmp_count_items[item_name] = item['item_count'] - else: - tmp_count_items[item_name] += item['item_count'] + + + items_awarded = self.get_items_awarded_from_fort_spinned(response_dict) if experience_awarded or items_awarded: self.emit_event( @@ -74,7 +73,7 @@ def work(self): data={ 'pokestop': fort_name, 'exp': experience_awarded, - 'items': tmp_count_items + 'items': items_awarded } ) else: @@ -87,13 +86,13 @@ def work(self): 'cooldown_complete_timestamp_ms') self.bot.fort_timeouts.update({fort["id"]: pokestop_cooldown}) self.bot.recent_forts = self.bot.recent_forts[1:] + [fort['id']] - elif spin_result == 2: + elif spin_result == SPIN_REQUEST_RESULT_OUT_OF_RANGE: self.emit_event( 'pokestop_out_of_range', formatted="Pokestop {pokestop} out of range.", data={'pokestop': fort_name} ) - elif spin_result == 3: + elif spin_result == SPIN_REQUEST_RESULT_IN_COOLDOWN_PERIOD: pokestop_cooldown = spin_details.get( 'cooldown_complete_timestamp_ms') if pokestop_cooldown: @@ -107,7 +106,7 @@ def work(self): formatted="Pokestop {pokestop} on cooldown. Time left: {minutes_left}.", data={'pokestop': fort_name, 'minutes_left': minutes_left} ) - elif spin_result == 4: + elif spin_result == SPIN_REQUEST_RESULT_INVENTORY_FULL: if not self.ignore_item_count: self.emit_event( 'inventory_full', @@ -162,3 +161,27 @@ def get_forts_in_range(self): ) <= Constants.MAX_DISTANCE_FORT_IS_REACHABLE, forts) return forts + + def get_items_awarded_from_fort_spinned(self, response_dict): + items_awarded = response_dict['responses']['FORT_SEARCH'].get('items_awarded', {}) + if items_awarded: + tmp_count_items = {} + for item_awarded in items_awarded: + + item_awarded_id = item_awarded['item_id'] + item_awarded_name = inventory.Items.name_for(item_awarded_id) + item_awarded_count = item_awarded['item_count'] + + if not item_awarded_name in tmp_count_items: + tmp_count_items[item_awarded_name] = item_awarded_count + else: + tmp_count_items[item_awarded_name] += item_awarded_count + + self._update_inventory(item_awarded) + + return tmp_count_items + + # TODO : Refactor this class, hide the inventory update right after the api call + def _update_inventory(self, item_awarded): + inventory.items().get(item_awarded['item_id']).add(item_awarded['item_count']) + From e1d24ef4f3cff56550f36b6e31009998f97e65df Mon Sep 17 00:00:00 2001 From: SAUNIER DEBES Brice Date: Fri, 12 Aug 2016 19:51:30 +0200 Subject: [PATCH 3/8] Now the pokemon_catch_worker keeps track of cached ITEMS (and only items, not pokemon) inventory --- .../cell_workers/pokemon_catch_worker.py | 97 +++++++++++-------- 1 file changed, 57 insertions(+), 40 deletions(-) diff --git a/pokemongo_bot/cell_workers/pokemon_catch_worker.py b/pokemongo_bot/cell_workers/pokemon_catch_worker.py index d28f3f58fd..bda48fb375 100644 --- a/pokemongo_bot/cell_workers/pokemon_catch_worker.py +++ b/pokemongo_bot/cell_workers/pokemon_catch_worker.py @@ -7,6 +7,8 @@ from pokemongo_bot.human_behaviour import sleep from pokemongo_bot.worker_result import WorkerResult +USE_ITEM_CAPTURE_STATUS_SUCCESS = 1 + CATCH_STATUS_SUCCESS = 1 CATCH_STATUS_FAILED = 2 CATCH_STATUS_VANISHED = 3 @@ -55,7 +57,6 @@ def __init__(self, pokemon, bot): self.config = bot.config self.pokemon_list = bot.pokemon_list self.item_list = bot.item_list - self.inventory = bot.inventory self.spawn_point_guid = '' self.response_key = '' self.response_status_key = '' @@ -210,11 +211,8 @@ def _use_berry(self, berry_id, berry_count, encounter_id, catch_rate_by_ball, cu } ) - response_dict = self.api.use_item_capture( - item_id=berry_id, - encounter_id=encounter_id, - spawn_point_id=self.spawn_point_guid - ) + response_dict = self.request_use_item_capture(berry_id, encounter_id) + responses = response_dict['responses'] if response_dict and response_dict['status_code'] == 1: @@ -255,53 +253,63 @@ def _use_berry(self, berry_id, berry_count, encounter_id, catch_rate_by_ball, cu return new_catch_rate_by_ball + def request_use_item_capture(self, berry_id, encounter_id): + response_dict = self.api.use_item_capture( + item_id=berry_id, + encounter_id=encounter_id, + spawn_point_id=self.spawn_point_guid + ) + if response_dict and response_dict['responses']['status_code'] == USE_ITEM_CAPTURE_STATUS_SUCCESS: + inventory.items().get(berry_id).remove(1) + return response_dict + def _do_catch(self, pokemon, encounter_id, catch_rate_by_ball, is_vip=False): # settings that may be exposed at some point berry_id = ITEM_RAZZBERRY - maximum_ball = ITEM_ULTRABALL if is_vip else ITEM_GREATBALL + maximum_ball_id = ITEM_ULTRABALL if is_vip else ITEM_GREATBALL ideal_catch_rate_before_throw = 0.9 if is_vip else 0.35 - berry_count = self.bot.item_inventory_count(berry_id) - items_stock = self.bot.current_inventory() + item_inventory = inventory.items() + berry_count = item_inventory.get(berry_id).count while True: # find lowest available ball - current_ball = ITEM_POKEBALL - while items_stock[current_ball] == 0 and current_ball < maximum_ball: - current_ball += 1 - if items_stock[current_ball] == 0: + current_ball_id = ITEM_POKEBALL + while item_inventory.get(current_ball_id).count == 0 and current_ball_id < maximum_ball_id: + current_ball_id += 1 + if item_inventory.get(current_ball_id).count == 0: self.emit_event('no_pokeballs', formatted='No usable pokeballs found!') break # check future ball count num_next_balls = 0 - next_ball = current_ball - while next_ball < maximum_ball: - next_ball += 1 - num_next_balls += items_stock[next_ball] + next_ball_id = current_ball_id + while next_ball_id < maximum_ball_id: + next_ball_id += 1 + num_next_balls += item_inventory.get(next_ball_id).count # check if we've got berries to spare berries_to_spare = berry_count > 0 if is_vip else berry_count > num_next_balls + 30 # use a berry if we are under our ideal rate and have berries to spare used_berry = False - if catch_rate_by_ball[current_ball] < ideal_catch_rate_before_throw and berries_to_spare: - catch_rate_by_ball = self._use_berry(berry_id, berry_count, encounter_id, catch_rate_by_ball, current_ball) + if catch_rate_by_ball[current_ball_id] < ideal_catch_rate_before_throw and berries_to_spare: + catch_rate_by_ball = self._use_berry(berry_id, berry_count, encounter_id, catch_rate_by_ball, current_ball_id) berry_count -= 1 used_berry = True # pick the best ball to catch with - best_ball = current_ball - while best_ball < maximum_ball: - best_ball += 1 - if catch_rate_by_ball[current_ball] < ideal_catch_rate_before_throw and items_stock[best_ball] > 0: + best_ball_id = current_ball_id + while best_ball_id < maximum_ball_id: + best_ball_id += 1 + if catch_rate_by_ball[current_ball_id] < ideal_catch_rate_before_throw and item_inventory.get(best_ball_id).count > 0: # if current ball chance to catch is under our ideal rate, and player has better ball - then use it - current_ball = best_ball + current_ball_id = best_ball_id # if the rate is still low and we didn't throw a berry before, throw one - if catch_rate_by_ball[current_ball] < ideal_catch_rate_before_throw and berry_count > 0 and not used_berry: - catch_rate_by_ball = self._use_berry(berry_id, berry_count, encounter_id, catch_rate_by_ball, current_ball) + if catch_rate_by_ball[current_ball_id] < ideal_catch_rate_before_throw and berry_count > 0 and not used_berry: + catch_rate_by_ball = self._use_berry(berry_id, berry_count, encounter_id, catch_rate_by_ball, current_ball_id) berry_count -= 1 # Randomize the quality of the throw @@ -315,26 +323,17 @@ def _do_catch(self, pokemon, encounter_id, catch_rate_by_ball, is_vip=False): # try to catch pokemon! # TODO : Log which type of throw we selected - items_stock[current_ball] -= 1 self.emit_event( 'threw_pokeball', formatted='Used {ball_name}, with chance {success_percentage} ({count_left} left)', data={ - 'ball_name': self.item_list[str(current_ball)], - 'success_percentage': self._pct(catch_rate_by_ball[current_ball]), - 'count_left': items_stock[current_ball] + 'ball_name': self.item_list[str(current_ball_id)], + 'success_percentage': self._pct(catch_rate_by_ball[current_ball_id]), + 'count_left': item_inventory.get(current_ball_id).count } ) - response_dict = self.api.catch_pokemon( - encounter_id=encounter_id, - pokeball=current_ball, - normalized_reticle_size=throw_parameters['normalized_reticle_size'], - spawn_point_id=self.spawn_point_guid, - hit_pokemon=1, - spin_modifier=throw_parameters['spin_modifier'], - normalized_hit_position=throw_parameters['normalized_hit_position'] - ) + response_dict = self.request_throw_ball(current_ball_id, encounter_id, throw_parameters) try: catch_pokemon_status = response_dict['responses']['CATCH_POKEMON']['status'] @@ -364,7 +363,7 @@ def _do_catch(self, pokemon, encounter_id, catch_rate_by_ball, is_vip=False): 'pokemon_id': pokemon.num } ) - if self._pct(catch_rate_by_ball[current_ball]) == 100: + if self._pct(catch_rate_by_ball[current_ball_id]) == 100: self.bot.softban = True # pokemon caught! @@ -401,6 +400,24 @@ def _do_catch(self, pokemon, encounter_id, catch_rate_by_ball, is_vip=False): break + def request_throw_ball(self, ball_id, encounter_id, throw_parameters): + response_dict = self.api.catch_pokemon( + encounter_id=encounter_id, + pokeball=ball_id, + normalized_reticle_size=throw_parameters['normalized_reticle_size'], + spawn_point_id=self.spawn_point_guid, + hit_pokemon=1, + spin_modifier=throw_parameters['spin_modifier'], + normalized_hit_position=throw_parameters['normalized_hit_position'] + ) + if response_dict: + inventory.items().get(ball_id).remove(1) + else: + # Not having a response from the server isn’t normal. + # Refreshing inventory since we can’t be sure whether or not the ball has been thrown + inventory.refresh_inventory() + return response_dict + def generate_spin_parameter(self, throw_parameters): spin_success_rate = self.config.catch_throw_parameters_spin_success_rate if random() <= spin_success_rate: From 87327750eebb40f68ec8a7355b7d16bc32eb2d7e Mon Sep 17 00:00:00 2001 From: SAUNIER DEBES Brice Date: Fri, 12 Aug 2016 19:55:03 +0200 Subject: [PATCH 4/8] Minor improvements of the new inventory --- pokemongo_bot/inventory.py | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/pokemongo_bot/inventory.py b/pokemongo_bot/inventory.py index 3ade99c0af..0939af11c7 100644 --- a/pokemongo_bot/inventory.py +++ b/pokemongo_bot/inventory.py @@ -116,6 +116,9 @@ def add(self, amount): raise Exception('Must add positive amount of {}'.format(self.name)) self.count += amount + def __str__(self): + return self.name + " : " + str(self.count) + class Items(_BaseInventoryComponent): TYPE = 'item' @@ -134,6 +137,13 @@ def get(self, item_id): def name_for(cls, item_id): return cls.STATIC_DATA[str(item_id)] + @classmethod + def id_for(cls, item_name): + for item_data in cls.STATIC_DATA: + if item_data == item_name: + return item_data + return None + def get_space_used(self): """ Counts the space used in item inventory. @@ -148,11 +158,22 @@ def get_space_used(self): def get_space_left(self): """ Compute the space left in item inventory. - :return: The space left in item inventory. + :return: The space left in item inventory. 0 if the player has more item than his item inventory can carry. :rtype: int """ _inventory.retrieve_item_inventory_size() - return _inventory.item_inventory_size - self.get_space_used() + space_left = _inventory.item_inventory_size - self.get_space_used() + # Space left should never be negative. Returning 0 if the computed value is negative. + return space_left if space_left >= 0 else 0 + + def has_space_for_loot(self): + """ + Returns a value indicating whether or not the item inventory has enough space to loot a fort + :return: True if the item inventory has enough space; otherwise, False. + :rtype: bool + """ + max_number_of_items_looted_at_stop = 5 + return self.get_space_left() >= max_number_of_items_looted_at_stop class Pokemons(_BaseInventoryComponent): @@ -806,6 +827,7 @@ def refresh(self): user_web_inventory = os.path.join(_base_dir, 'web', 'inventory-%s.json' % (self.bot.config.username)) with open(user_web_inventory, 'w') as outfile: json.dump(inventory, outfile) + def retrieve_item_inventory_size(self): """ Retrieves the item inventory size @@ -889,6 +911,11 @@ def pokemons(refresh=False): def items(): + """ + Access to the cached item inventory + :return: Instance of the cached item inventory + :rtype: Items + """ return _inventory.items From be193ebf1a7a622982926d475edec2833d5634b4 Mon Sep 17 00:00:00 2001 From: SAUNIER DEBES Brice Date: Fri, 12 Aug 2016 20:08:21 +0200 Subject: [PATCH 5/8] Fixed key error --- pokemongo_bot/cell_workers/pokemon_catch_worker.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pokemongo_bot/cell_workers/pokemon_catch_worker.py b/pokemongo_bot/cell_workers/pokemon_catch_worker.py index bda48fb375..9e71703bd3 100644 --- a/pokemongo_bot/cell_workers/pokemon_catch_worker.py +++ b/pokemongo_bot/cell_workers/pokemon_catch_worker.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- - +import json import time from random import random from pokemongo_bot import inventory @@ -210,7 +210,6 @@ def _use_berry(self, berry_id, berry_count, encounter_id, catch_rate_by_ball, cu 'berry_count': berry_count } ) - response_dict = self.request_use_item_capture(berry_id, encounter_id) responses = response_dict['responses'] @@ -259,7 +258,7 @@ def request_use_item_capture(self, berry_id, encounter_id): encounter_id=encounter_id, spawn_point_id=self.spawn_point_guid ) - if response_dict and response_dict['responses']['status_code'] == USE_ITEM_CAPTURE_STATUS_SUCCESS: + if response_dict and response_dict['status_code'] == USE_ITEM_CAPTURE_STATUS_SUCCESS: inventory.items().get(berry_id).remove(1) return response_dict @@ -271,7 +270,6 @@ def _do_catch(self, pokemon, encounter_id, catch_rate_by_ball, is_vip=False): item_inventory = inventory.items() berry_count = item_inventory.get(berry_id).count - while True: # find lowest available ball From e5e4e5bc5c5dfbd7298a5b366d6e8fdece267b1e Mon Sep 17 00:00:00 2001 From: SAUNIER DEBES Brice Date: Fri, 12 Aug 2016 20:20:36 +0200 Subject: [PATCH 6/8] Minor improvements of the new inventory --- pokemongo_bot/inventory.py | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/pokemongo_bot/inventory.py b/pokemongo_bot/inventory.py index 3ade99c0af..0939af11c7 100644 --- a/pokemongo_bot/inventory.py +++ b/pokemongo_bot/inventory.py @@ -116,6 +116,9 @@ def add(self, amount): raise Exception('Must add positive amount of {}'.format(self.name)) self.count += amount + def __str__(self): + return self.name + " : " + str(self.count) + class Items(_BaseInventoryComponent): TYPE = 'item' @@ -134,6 +137,13 @@ def get(self, item_id): def name_for(cls, item_id): return cls.STATIC_DATA[str(item_id)] + @classmethod + def id_for(cls, item_name): + for item_data in cls.STATIC_DATA: + if item_data == item_name: + return item_data + return None + def get_space_used(self): """ Counts the space used in item inventory. @@ -148,11 +158,22 @@ def get_space_used(self): def get_space_left(self): """ Compute the space left in item inventory. - :return: The space left in item inventory. + :return: The space left in item inventory. 0 if the player has more item than his item inventory can carry. :rtype: int """ _inventory.retrieve_item_inventory_size() - return _inventory.item_inventory_size - self.get_space_used() + space_left = _inventory.item_inventory_size - self.get_space_used() + # Space left should never be negative. Returning 0 if the computed value is negative. + return space_left if space_left >= 0 else 0 + + def has_space_for_loot(self): + """ + Returns a value indicating whether or not the item inventory has enough space to loot a fort + :return: True if the item inventory has enough space; otherwise, False. + :rtype: bool + """ + max_number_of_items_looted_at_stop = 5 + return self.get_space_left() >= max_number_of_items_looted_at_stop class Pokemons(_BaseInventoryComponent): @@ -806,6 +827,7 @@ def refresh(self): user_web_inventory = os.path.join(_base_dir, 'web', 'inventory-%s.json' % (self.bot.config.username)) with open(user_web_inventory, 'w') as outfile: json.dump(inventory, outfile) + def retrieve_item_inventory_size(self): """ Retrieves the item inventory size @@ -889,6 +911,11 @@ def pokemons(refresh=False): def items(): + """ + Access to the cached item inventory + :return: Instance of the cached item inventory + :rtype: Items + """ return _inventory.items From 7edfabbf1ed79c3fc8bebc30adb30bbbaa62e9e6 Mon Sep 17 00:00:00 2001 From: SAUNIER DEBES Brice Date: Fri, 12 Aug 2016 20:47:15 +0200 Subject: [PATCH 7/8] Fixed attribute non existent --- pokemongo_bot/cell_workers/pokemon_catch_worker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pokemongo_bot/cell_workers/pokemon_catch_worker.py b/pokemongo_bot/cell_workers/pokemon_catch_worker.py index 4030a33e41..80dfec20e0 100644 --- a/pokemongo_bot/cell_workers/pokemon_catch_worker.py +++ b/pokemongo_bot/cell_workers/pokemon_catch_worker.py @@ -367,7 +367,7 @@ def _do_catch(self, pokemon, encounter_id, catch_rate_by_ball, is_vip=False): 'encounter_id': self.pokemon['encounter_id'], 'latitude': self.pokemon['latitude'], 'longitude': self.pokemon['longitude'], - 'pokemon_id': pokemon.num + 'pokemon_id': pokemon.id } ) if self._pct(catch_rate_by_ball[current_ball]) == 100: From cf6c5bf41afa7e231fcdb673fef51ac6247a5caf Mon Sep 17 00:00:00 2001 From: SAUNIER DEBES Brice Date: Fri, 12 Aug 2016 21:16:36 +0200 Subject: [PATCH 8/8] Removed duplicated import --- pokemongo_bot/cell_workers/spin_fort.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pokemongo_bot/cell_workers/spin_fort.py b/pokemongo_bot/cell_workers/spin_fort.py index a4cddf2e6e..eee88525c5 100644 --- a/pokemongo_bot/cell_workers/spin_fort.py +++ b/pokemongo_bot/cell_workers/spin_fort.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -import json import json import os import time