-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improved item recycling #2482
Improved item recycling #2482
Changes from 29 commits
815f42a
fe6f7b9
dc5cdde
62f916d
ef240a1
2d2c1a3
eaa6082
c1718cf
59df587
d7345d4
acfabca
fafcafd
2ad09c5
653532a
d398e45
bcbbf09
3600f70
dce922b
f5fef8b
385721f
7f35134
bf0995f
5c3e10a
1e42340
f4dbe1c
f18997b
06178bf
9992dbb
802e7c4
9981b91
36a001e
568fead
19370f8
c6ea433
7214101
7cc0c3c
055e223
a13e8e9
9982ae3
057c488
36823f5
930300e
d7d32de
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,85 +1,176 @@ | ||
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.worker_result import WorkerResult | ||
from pokemongo_bot.tree_config_builder import ConfigException | ||
|
||
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)) | ||
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 False | ||
return True | ||
|
||
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: | ||
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) | ||
""" | ||
Discard items if necessary. | ||
:return: Always returns WorkerResult.SUCCESS. | ||
:rtype: WorkerResult | ||
""" | ||
# Updating inventory | ||
inventory.init_inventory(self.bot) | ||
if self.should_run(): | ||
# For each user's item in inventory recycle it if needed | ||
for item_in_inventory in inventory.items().all(): | ||
item = RecycleItems._Item(item_in_inventory['item_id'], self.items_filter, self) | ||
|
||
if item.should_be_recycled(): | ||
item.request_recycle() | ||
item.emit_recycle_result() | ||
# TODO : Inventory should keep track of items beeing discarded rather than making an other api call | ||
inventory.refresh_inventory() | ||
return WorkerResult.SUCCESS | ||
|
||
class _Item: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Excuse my python noobishness, but what is the difference between There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When you inherit from New style objects have a different object model to classic objects, and some things won't work properly with old style objects (those that doesn't inherit from In Python 3 it's not necessary to inherit from |
||
""" | ||
An item found in user's inventory. | ||
|
||
This class contains details of recycling process. | ||
""" | ||
def __init__(self, item_id, items_filter, recycle_items): | ||
""" | ||
Initializes an item | ||
:param item_id: Item's id. | ||
:param items_filter: List of items and their maximum amount to keep. | ||
:param recycle_items: The recycle_items instance. | ||
""" | ||
self.recycle_items = recycle_items | ||
self.bot = recycle_items.bot | ||
self.id = item_id | ||
self.name = recycle_items.bot.item_list[str(item_id)] | ||
self.items_filter = items_filter | ||
self.amount_to_keep = self._get_amount_to_keep() | ||
self.amount_in_inventory = inventory.items().count_for(self.id) | ||
self.amount_to_recycle = 0 if self.amount_to_keep is None else self.amount_in_inventory - self.amount_to_keep | ||
self.recycle_item_request_result = None | ||
|
||
def _get_amount_to_keep(self): | ||
""" | ||
Determine item's amount to keep in inventory. | ||
:return: Item's amount to keep in inventory. | ||
:rtype: int | ||
""" | ||
item_filter_config = self.items_filter.get(self.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(self.id), 0) | ||
if item_filter_config is not 0: | ||
return item_filter_config.get('keep', 20) | ||
|
||
def should_be_recycled(self): | ||
""" | ||
Returns a value indicating whether the item should be recycled. | ||
:return: True if the title should be recycled; otherwise, False. | ||
:rtype: bool | ||
""" | ||
return (self.name in self.items_filter or str(self.id) in self.items_filter) and self.amount_to_recycle > 0 | ||
|
||
def request_recycle(self): | ||
""" | ||
Request recycling of the item and store api call response's result. | ||
:return: Nothing. | ||
:rtype: None | ||
""" | ||
response = self.bot.api.recycle_inventory_item(item_id=self.id, count=self.amount_to_recycle) | ||
# 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} | ||
self.recycle_item_request_result = response.get('responses', {}).get('RECYCLE_INVENTORY_ITEM', {}).get('result', 0) | ||
|
||
def _is_recycling_success(self): | ||
""" | ||
Returns a value indicating whether the item has been successfully recycled. | ||
:return: True if the item has been successfully recycled; otherwise, False. | ||
:rtype: bool | ||
""" | ||
return self.recycle_item_request_result == 1 | ||
|
||
def emit_recycle_result(self): | ||
""" | ||
Emits recycle result in logs | ||
:return: Nothing. | ||
:rtype: None | ||
""" | ||
if self._is_recycling_success(): | ||
self.recycle_items.emit_event( | ||
'item_discarded', | ||
formatted='Discarded {amount}x {item} (maximum {maximum}).', | ||
data={ | ||
'amount': str(self.amount_to_recycle), | ||
'item': self.name, | ||
'maximum': str(self.amount_to_keep) | ||
} | ||
) | ||
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 | ||
) | ||
self.recycle_items.emit_event( | ||
'item_discard_fail', | ||
formatted="Failed to discard {item}", | ||
data={ | ||
'item': self.name | ||
} | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -100,7 +100,27 @@ class Items(_BaseInventoryComponent): | |
STATIC_DATA_FILE = os.path.join(_base_dir, 'data', 'items.json') | ||
|
||
def count_for(self, item_id): | ||
return self._data[item_id]['count'] | ||
return self._data[item_id].get('count', False) | ||
|
||
def get_space_used(self): | ||
""" | ||
Counts the space used in item inventory. | ||
:return: The space used in item inventory. | ||
:rtype: int | ||
""" | ||
itemcount = 1 | ||
for item in self._data: | ||
itemcount += self.count_for(item) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if |
||
return itemcount | ||
|
||
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): | ||
|
@@ -214,6 +234,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 | ||
|
@@ -227,6 +248,15 @@ def refresh(self): | |
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'] | ||
|
||
_inventory = None | ||
|
||
|
@@ -238,6 +268,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 | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inventory is already initialized when the API is set up, you don't need this here. This will ready all JSON cache files again and can slow down the tick a lot.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once every task track inventory changes I’ll hapily remove the api call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If everyone thinks like that, we will never reach full integration. You can use the cached inventory here, we update other tasks as necessary.