Skip to content

Commit

Permalink
Improved item recycling (#2482)
Browse files Browse the repository at this point in the history
* Now recycling only if less than 5 space left in inventory
Now trying to recycle before moving to/spinning fort if bags are almost full
Refactored recycle_items

* Removed recycling before moving to/spinning fort if bags are almost full

* Removed recycling before moving to/spinning fort if bags are almost full

* Removed unused import

* Now recycling only if less than 5 space left in inventory
Now trying to recycle before moving to/spinning fort if bags are almost full
Refactored recycle_items

* Removed recycling before moving to/spinning fort if bags are almost full

* Added documentation
Replace all `logger.log` calls with events! (#2173)

* Deleted change on files not concerned

* Deleted change on files not concerned

* The inner class is now "private"

* new class to centralize inventory management

* use new inventory class in evolve_pokemon

* use new inventory to display # candy after catch

* Now using the new inventory (#2528)

* Fixed #3256

* Merge branch 'dev' of https://github.com/PokemonGoF/PokemonGo-Bot into PokemonGoF-dev

# Conflicts:
#	pokemongo_bot/cell_workers/recycle_items.py

Added methods in the inventory manager needed for the recycle_items task

* Fixed error if item_count result is false

* Now keeps track of item inventory

* Moved inventory update in request_recycle method

* Minor comment change

* Fixed not running if had more item than inventory size (#3531)

* Added to the inventory class the necessary to keep trace of items

* Now using the new inventory class properly

* Decoupled when to recycle an item from how to do it.

* Moved the recycler in the services folder
  • Loading branch information
BriceSD authored and douglascamata committed Aug 11, 2016
1 parent ca4a197 commit b8af467
Show file tree
Hide file tree
Showing 4 changed files with 277 additions and 65 deletions.
168 changes: 106 additions & 62 deletions pokemongo_bot/cell_workers/recycle_items.py
Original file line number Diff line number Diff line change
@@ -1,85 +1,129 @@
import json
import os

from pokemongo_bot import inventory
from pokemongo_bot.base_dir import _base_dir
from pokemongo_bot.base_task import BaseTask
from pokemongo_bot.services.item_recycle_worker import ItemRecycler
from pokemongo_bot.tree_config_builder import ConfigException
from pokemongo_bot.worker_result import WorkerResult

DEFAULT_MIN_EMPTY_SPACE = 6

class RecycleItems(BaseTask):
SUPPORTED_TASK_API_VERSION = 1

"""
Recycle undesired items if there is less than five space in inventory.
You can use either item's name or id. For the full list of items see ../../data/items.json
It's highly recommended to put this task before move_to_fort and spin_fort task in the config file so you'll most likely be able to loot.
Example config :
{
"type": "RecycleItems",
"config": {
"min_empty_space": 6, # 6 by default
"item_filter": {
"Pokeball": {"keep": 20},
"Greatball": {"keep": 50},
"Ultraball": {"keep": 100},
"Potion": {"keep": 0},
"Super Potion": {"keep": 0},
"Hyper Potion": {"keep": 20},
"Max Potion": {"keep": 50},
"Revive": {"keep": 0},
"Max Revive": {"keep": 20},
"Razz Berry": {"keep": 20}
}
}
}
"""

def initialize(self):
self.items_filter = self.config.get('item_filter', {})
self.min_empty_space = self.config.get('min_empty_space', None)
self.item_filter = self.config.get('item_filter', {})
self._validate_item_filter()

def _validate_item_filter(self):
"""
Validate user's item filter config
:return: Nothing.
:rtype: None
:raise: ConfigException: When an item doesn't exist in ../../data/items.json
"""
item_list = json.load(open(os.path.join(_base_dir, 'data', 'items.json')))
for config_item_name, bag_count in self.item_filter.iteritems():
for config_item_name, bag_count in self.items_filter.iteritems():
if config_item_name not in item_list.viewvalues():
if config_item_name not in item_list:
raise ConfigException(
"item {} does not exist, spelling mistake? (check for valid item names in data/items.json)".format(
config_item_name))

def should_run(self):
"""
Returns a value indicating whether the recycling process should be run.
:return: True if the recycling process should be run; otherwise, False.
:rtype: bool
"""
if inventory.items().get_space_left() < (DEFAULT_MIN_EMPTY_SPACE if self.min_empty_space is None else self.min_empty_space):
return True
return False

def work(self):
items_in_bag = self.bot.get_inventory_count('item')
total_bag_space = self.bot.player_data['max_item_storage']
free_bag_space = total_bag_space - items_in_bag

if self.min_empty_space is not None:
if free_bag_space >= self.min_empty_space and items_in_bag < total_bag_space:
self.emit_event(
'item_discard_skipped',
formatted="Skipping Recycling of Items. {space} space left in bag.",
data={
'space': free_bag_space
}
)
return

self.bot.latest_inventory = None
item_count_dict = self.bot.item_inventory_count('all')

for item_id, bag_count in item_count_dict.iteritems():
item_name = self.bot.item_list[str(item_id)]
id_filter = self.item_filter.get(item_name, 0)
if id_filter is not 0:
id_filter_keep = id_filter.get('keep', 20)
else:
id_filter = self.item_filter.get(str(item_id), 0)
if id_filter is not 0:
id_filter_keep = id_filter.get('keep', 20)

bag_count = self.bot.item_inventory_count(item_id)
if (item_name in self.item_filter or str(item_id) in self.item_filter) and bag_count > id_filter_keep:
items_recycle_count = bag_count - id_filter_keep
response_dict_recycle = self.send_recycle_item_request(item_id=item_id, count=items_recycle_count)
result = response_dict_recycle.get('responses', {}).get('RECYCLE_INVENTORY_ITEM', {}).get('result', 0)

if result == 1: # Request success
self.emit_event(
'item_discarded',
formatted='Discarded {amount}x {item} (maximum {maximum}).',
data={
'amount': str(items_recycle_count),
'item': item_name,
'maximum': str(id_filter_keep)
}
)
else:
self.emit_event(
'item_discard_fail',
formatted="Failed to discard {item}",
data={
'item': item_name
}
)

def send_recycle_item_request(self, item_id, count):
# Example of good request response
# {'responses': {'RECYCLE_INVENTORY_ITEM': {'result': 1, 'new_count': 46}}, 'status_code': 1, 'auth_ticket': {'expire_timestamp_ms': 1469306228058L, 'start': '/HycFyfrT4t2yB2Ij+yoi+on778aymMgxY6RQgvrGAfQlNzRuIjpcnDd5dAxmfoTqDQrbz1m2dGqAIhJ+eFapg==', 'end': 'f5NOZ95a843tgzprJo4W7Q=='}, 'request_id': 8145806132888207460L}
return self.bot.api.recycle_inventory_item(
item_id=item_id,
count=count
)
"""
Discard items if necessary.
:return: Returns wether or not the task went well
:rtype: WorkerResult
"""
# TODO: Use new inventory everywhere and then remove the inventory update
# Updating inventory
inventory.refresh_inventory()
worker_result = WorkerResult.SUCCESS
if self.should_run():

# For each user's item in inventory recycle it if needed
for item_in_inventory in inventory.items().all():
amount_to_recycle = self.get_amount_to_recycle(item_in_inventory)

if self.item_should_be_recycled(item_in_inventory, amount_to_recycle):
if ItemRecycler(self.bot, item_in_inventory, amount_to_recycle).work() == WorkerResult.ERROR:
worker_result = WorkerResult.ERROR

return worker_result

def item_should_be_recycled(self, item, amount_to_recycle):
"""
Returns a value indicating whether the item should be recycled.
:param amount_to_recycle:
:param item:
:return: True if the title should be recycled; otherwise, False.
:rtype: bool
"""
return (item.name in self.items_filter or str(
item.id) in self.items_filter) and amount_to_recycle > 0

def get_amount_to_recycle(self, item):
"""
Determine the amount to recycle accordingly to user config
:param item: Item to determine the amount to recycle
:return: The amount to recycle
:rtype: int
"""
amount_to_keep = self.get_amount_to_keep(item)
return 0 if amount_to_keep is None else item.count - amount_to_keep

def get_amount_to_keep(self, item):
"""
Determine item's amount to keep in inventory.
:param item:
:return: Item's amount to keep in inventory.
:rtype: int
"""
item_filter_config = self.items_filter.get(item.name, 0)
if item_filter_config is not 0:
return item_filter_config.get('keep', 20)
else:
item_filter_config = self.items_filter.get(str(item.id), 0)
if item_filter_config is not 0:
return item_filter_config.get('keep', 20)
65 changes: 62 additions & 3 deletions pokemongo_bot/inventory.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import json
import logging
import os

from pokemongo_bot.base_dir import _base_dir

'''
Expand Down Expand Up @@ -101,14 +100,59 @@ def captured(self, pokemon_id):
return False
return self._data[pokemon_id]['times_captured'] > 0

class Item(object):
def __init__(self, item_id, item_count):
self.id = item_id
self.name = Items.name_for(self.id)
self.count = item_count

def remove(self, amount):
if self.count < amount:
raise Exception('Tried to remove more {} than you have'.format(self.name))
self.count -= amount

def add(self, amount):
if amount < 0:
raise Exception('Must add positive amount of {}'.format(self.name))
self.count += amount


class Items(_BaseInventoryComponent):
TYPE = 'item'
ID_FIELD = 'item_id'
STATIC_DATA_FILE = os.path.join(_base_dir, 'data', 'items.json')

def count_for(self, item_id):
return self._data[item_id]['count']
def parse(self, item_data):
item_id = item_data.get(Items.ID_FIELD, None)
item_count = item_data['count'] if 'count' in item_data else 0
return Item(item_id, item_count)

def get(self, item_id):
return self._data.setdefault(item_id, Item(item_id, 0))

@classmethod
def name_for(cls, item_id):
return cls.STATIC_DATA[str(item_id)]

def get_space_used(self):
"""
Counts the space used in item inventory.
:return: The space used in item inventory.
:rtype: int
"""
space_used = 1
for item_in_inventory in _inventory.items.all():
space_used += item_in_inventory.count
return space_used

def get_space_left(self):
"""
Compute the space left in item inventory.
:return: The space left in item inventory.
:rtype: int
"""
_inventory.retrieve_item_inventory_size()
return _inventory.item_inventory_size - self.get_space_used()


class Pokemons(_BaseInventoryComponent):
Expand Down Expand Up @@ -749,6 +793,7 @@ def __init__(self, bot):
self.items = Items()
self.pokemons = Pokemons()
self.refresh()
self.item_inventory_size = None

def refresh(self):
# TODO: it would be better if this class was used for all
Expand All @@ -761,13 +806,24 @@ 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
:return: Nothing.
:rtype: None
"""
# TODO: Force update of _item_inventory_size if the player upgrades its size
if self.item_inventory_size is None:
self.item_inventory_size = self.bot.api.get_player()['responses']['GET_PLAYER']['player_data']['max_item_storage']


#
# Usage helpers

# STAB (Same-type attack bonus)
STAB_FACTOR = 1.25


_inventory = None
LevelToCPm() # init LevelToCPm
FastAttacks() # init FastAttacks
Expand Down Expand Up @@ -812,6 +868,9 @@ def init_inventory(bot):
def refresh_inventory():
_inventory.refresh()

def get_item_inventory_size():
_inventory.retrieve_item_inventory_size()
return _inventory.item_inventory_size

def pokedex():
return _inventory.pokedex
Expand Down
Empty file.
Loading

0 comments on commit b8af467

Please sign in to comment.