Skip to content
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

Recycle Items schedule/force #4513

Merged
merged 15 commits into from
Aug 25, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion configs/config.json.cluster.example
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,10 @@
"Razz Berry": { "keep" : 100 }
},
"recycle_wait_min": 3,
"recycle_wait_max": 5
"recycle_wait_max": 5,
"recycle_force": true,
"recycle_force_min": "00:00:00",
"recycle_force_max": "00:01:00"
}
},
{
Expand Down Expand Up @@ -159,6 +162,11 @@
"spin_wait_max": 5
}
},
{ "type": "UpdateWebInventory",
"config": {
"enabled": true
}
},
{
"type": "FollowCluster",
"config": {
Expand Down
10 changes: 9 additions & 1 deletion configs/config.json.example
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,10 @@
"Razz Berry": { "keep" : 100 }
},
"recycle_wait_min": 3,
"recycle_wait_max": 5
"recycle_wait_max": 5,
"recycle_force": true,
"recycle_force_min": "00:00:00",
"recycle_force_max": "00:01:00"
}
},
{
Expand Down Expand Up @@ -174,6 +177,11 @@
"spin_wait_max": 5
}
},
{ "type": "UpdateWebInventory",
"config": {
"enabled": true
}
},
{
"type": "MoveToFort",
"config": {
Expand Down
10 changes: 9 additions & 1 deletion configs/config.json.map.example
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,10 @@
"Razz Berry": { "keep" : 100 }
},
"recycle_wait_min": 1,
"recycle_wait_max": 4
"recycle_wait_max": 4,
"recycle_force": true,
"recycle_force_min": "00:00:00",
"recycle_force_max": "00:01:00"
}
},
{
Expand Down Expand Up @@ -159,6 +162,11 @@
"spin_wait_max": 5
}
},
{ "type": "UpdateWebInventory",
"config": {
"enabled": true
}
},
{
"type": "MoveToMapPokemon",
"config": {
Expand Down
10 changes: 9 additions & 1 deletion configs/config.json.optimizer.example
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,10 @@
"Razz Berry": { "keep" : 100 }
},
"recycle_wait_min": 3,
"recycle_wait_max": 5
"recycle_wait_max": 5,
"recycle_force": true,
"recycle_force_min": "00:00:00",
"recycle_force_max": "00:01:00"
}
},
{
Expand Down Expand Up @@ -221,6 +224,11 @@
"spin_wait_max": 5
}
},
{ "type": "UpdateWebInventory",
"config": {
"enabled": true
}
},
{
"type": "MoveToFort",
"config": {
Expand Down
10 changes: 9 additions & 1 deletion configs/config.json.path.example
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,10 @@
"Razz Berry": { "keep" : 100 }
},
"recycle_wait_min": 3,
"recycle_wait_max": 5
"recycle_wait_max": 5,
"recycle_force": true,
"recycle_force_min": "00:00:00",
"recycle_force_max": "00:01:00"
}
},
{
Expand Down Expand Up @@ -160,6 +163,11 @@
"spin_wait_max": 5
}
},
{ "type": "UpdateWebInventory",
"config": {
"enabled": true
}
},
{
"type": "FollowPath",
"config": {
Expand Down
10 changes: 9 additions & 1 deletion configs/config.json.pokemon.example
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,10 @@
"Razz Berry": { "keep" : 100 }
},
"recycle_wait_min": 3,
"recycle_wait_max": 5
"recycle_wait_max": 5,
"recycle_force": true,
"recycle_force_min": "00:00:00",
"recycle_force_max": "00:01:00"
}
},
{
Expand Down Expand Up @@ -159,6 +162,11 @@
"spin_wait_max": 5
}
},
{ "type": "UpdateWebInventory",
"config": {
"enabled": true
}
},
{
"type": "MoveToFort",
"config":{
Expand Down
4 changes: 4 additions & 0 deletions docs/configuration_files.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ The behaviors of the bot are configured via the `tasks` key in the `config.json`
* `max_potions_keep`: Default `None` | Maximum amount of potions to keep in inventory
* `max_berries_keep`: Default `None` | Maximum amount of berries to keep in inventory
* `max_revives_keep`: Default `None` | Maximum amount of revives to keep in inventory
* `recycle_force`: Default `False` | Force scheduled recycle, even if min_empty_space not exceeded
* `recycle_force_min`: Default `00:01:00` | Minimum time to wait before scheduling next forced recycle
* `recycle_force_max`: Default `00:10:00` | Maximum time to wait before scheduling next forced recycle

* SpinFort
* TransferPokemon
* `min_free_slot`: Default `5` | Once the pokebag has less empty slots than this amount, the transfer process is triggered. | Big values (i.e 9999) will trigger the transfer process after each catch.
Expand Down
9 changes: 9 additions & 0 deletions pokemongo_bot/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,15 @@ def _register_events(self):
)
)

# recycle stuff
self.event_manager.register_event(
'next_force_recycle',
parameters=(
'time'
)
)
self.event_manager.register_event('force_recycle')

# random alive pause
self.event_manager.register_event(
'next_random_alive_pause',
Expand Down
1 change: 1 addition & 0 deletions pokemongo_bot/cell_workers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@
from catch_pokemon import CatchPokemon
from complete_tutorial import CompleteTutorial
from random_pause import RandomPause
from update_web_inventory import UpdateWebInventory
from random_alive_pause import RandomAlivePause
99 changes: 89 additions & 10 deletions pokemongo_bot/cell_workers/recycle_items.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,19 @@
from pokemongo_bot.services.item_recycle_worker import ItemRecycler
from pokemongo_bot.tree_config_builder import ConfigException
from pokemongo_bot.worker_result import WorkerResult
from random import uniform
from datetime import datetime as dt, timedelta


DEFAULT_MIN_EMPTY_SPACE = 6

class RecycleItems(BaseTask):
"""
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

Can also force a recycle to occur at a pseudo-random time between recycle_force_min and
recycle_force_max minutes.

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.

Expand All @@ -38,8 +44,14 @@ class RecycleItems(BaseTask):
"Revive": {"keep": 0},
"Max Revive": {"keep": 20},
"Razz Berry": {"keep": 20}
}
},
"recycle_wait_min": 1,
"recycle_wait_max": 4,
"recycle_force": true,
"recycle_force_min": "00:00:00",
"recycle_force_max": "00:01:00"
}

}
"""
SUPPORTED_TASK_API_VERSION = 1
Expand All @@ -54,7 +66,56 @@ def initialize(self):
self.max_revives_keep = self.config.get('max_revives_keep', None)
self.recycle_wait_min = self.config.get('recycle_wait_min', 1)
self.recycle_wait_max = self.config.get('recycle_wait_max', 4)
self.recycle_force = self.config.get('recycle_force', False)
self.recycle_force_min = self.config.get('recycle_force_min', '00:01:00')
self.recycle_force_max = self.config.get('recycle_force_max', '00:10:00')
self.minInterval = self.getSeconds(self.recycle_force_min)
self.maxInterval = self.getSeconds(self.recycle_force_max)
self._validate_item_filter()

if self.recycle_force:
self._schedule_next_force()

def getSeconds(self, strTime):
'''
Return the duration in seconds of a time string
:param strTime: string time of format %H:%M:%S
'''
try:
x = dt.strptime(strTime, '%H:%M:%S')
seconds = int(timedelta(hours=x.hour,minutes=x.minute,seconds=x.second).total_seconds())
except ValueError:
seconds = 0;

if seconds < 0:
seconds = 0;

return seconds

def _schedule_next_force(self):
'''
Schedule the time aof the next forced recycle.
'''
self._next_force = self._get_next_force_schedule()
self.emit_event(
'next_force_recycle',
formatted="Next forced item recycle at {time}",
data={
'time': str(self._next_force.strftime("%H:%M:%S"))
}
)

def _should_force_now(self):
if dt.now() >= self._next_force:
return True

return False

def _get_next_force_schedule(self):
now = dt.now()
next_time = now + timedelta(seconds=int(uniform(self.minInterval, self.maxInterval)))

return next_time

def _validate_item_filter(self):
"""
Expand All @@ -77,6 +138,15 @@ def should_run(self):
:return: True if the recycling process should be run; otherwise, False.
:rtype: bool
"""

if self.recycle_force and self._should_force_now():
self.emit_event(
'force_recycle',
formatted="Forcing item recycle based on schedule"
)
self._schedule_next_force()
return True

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
Expand All @@ -92,24 +162,33 @@ def work(self):
if self.should_run():

if not (self.max_balls_keep is None):
Copy link
Contributor

@BriceSD BriceSD Aug 22, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you put the recycling based on type after the recycling based on item amount ? The bot is discarding high balls before looking for lower balls to discard instead of highers one. You’re making the recycling based on type useless for people using both of them (with a well configured task).

What do you think @developerllazy ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha. Didn't see that side of things before, but you're right. Happy to switch them back again.

Also happy to put back the inventory refresh (and remove it from bot tick) if this api call is likely to cause issues. Still just a temp fix, as the bot should be able to export inventory to web from its own internal cache without needing to call the api every time. If the current web interface is likely to disappear soon, then that should work for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both issues fixed in the latest commit:

  1. Changed the recycling order back to type first, then item amount
  2. Web inventory now updates from cached inventory rather than api call

How's that looking?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still don’t get it about the force inventory feature. Apart from that seems promising.

worker_result = self.recycle_excess_category_max(self.max_balls_keep, [1,2,3,4])
this_worker_result = self.recycle_excess_category_max(self.max_balls_keep, [1,2,3,4])
if this_worker_result <> WorkerResult.SUCCESS:
worker_result = this_worker_result
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the first returns error and the second running, what do we keep ? Looks like running, while we want to return error. For now we want to return error if one of them returns error. That’s it. Don’t make it harder than it needs to be with some boolean complementation.

One or two weeks ago we had no error state. This bot is moving fast, you can’t predict what will be the fourth state, thus you can’t predict if it should override the error state or not.
Don’t make it harder to spot bugs when something has changed somewhere else.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As it stands, the function will return "success" provided the final task is successful. That's inaccurate.

If any task returns something other than "success", the function should be able to report it accurately, so yes if the first is "running" and the second is "success", then overall the result is "running".

Copy link
Contributor

@BriceSD BriceSD Aug 22, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As it stands, the function will return "success" provided the final task is successful. That's inaccurate.
As I already agreed, this is unwanted.

If any task returns something other than "success", the function should be able to report it accurately, so yes if the first is "running" and the second is "success", then overall the result is "running".
Agreed. But you missed my point. Returning running over success is wanted, but not returning running over error.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok I see where you're coming from. However, given that none of the tasks called by the recycler actually return "running", is it something worth worrying about?

If it is important, I'm happy to take a look, but I can't currently see any possible condition where "RUNNING" will be returned.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Running isn’t returned. That’s why you don’t see it here

# If at any recycling process call we got an error, we consider that the result of this task is error too.
if ItemRecycler(self.bot, item_in_inventory, self.get_amount_to_recycle(item_in_inventory)).work() == WorkerResult.ERROR:
    worker_result = WorkerResult.ERROR 

But what about the fourth state. The day someone add it, he won’t know he added a bug by overwriting the error result. It’ll be an annoying bug to catch, hard to reproduce. Why not simply avoiding it by not allowing it to happen right now ?


if not (self.max_potions_keep is None):
worker_result = self.recycle_excess_category_max(self.max_potions_keep, [101,102,103,104])
this_worker_result = self.recycle_excess_category_max(self.max_potions_keep, [101,102,103,104])
if this_worker_result <> WorkerResult.SUCCESS:
worker_result = this_worker_result

if not (self.max_berries_keep is None):
worker_result = self.recycle_excess_category_max(self.max_berries_keep, [701,702,703,704,705])
this_worker_result = self.recycle_excess_category_max(self.max_berries_keep, [701,702,703,704,705])
if this_worker_result <> WorkerResult.SUCCESS:
worker_result = this_worker_result

if not (self.max_revives_keep is None):
worker_result = self.recycle_excess_category_max(self.max_revives_keep, [201,202])

inventory.refresh_inventory()

this_worker_result = self.recycle_excess_category_max(self.max_revives_keep, [201,202])
if this_worker_result <> WorkerResult.SUCCESS:
worker_result = this_worker_result
for item_in_inventory in inventory.items().all():

if self.item_should_be_recycled(item_in_inventory):
# Make the bot appears more human
action_delay(self.recycle_wait_min, self.recycle_wait_max)
# If at any recycling process call we got an error, we consider that the result of this task is error too.
if ItemRecycler(self.bot, item_in_inventory, self.get_amount_to_recycle(item_in_inventory)).work() == WorkerResult.ERROR:
worker_result = WorkerResult.ERROR
worker_result = WorkerResult.ERROR

return worker_result

def recycle_excess_category_max(self, category_max, category_items_list):
Expand Down
24 changes: 22 additions & 2 deletions pokemongo_bot/cell_workers/update_live_stats.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import ctypes
import json
import os
from sys import stdout, platform as _platform
from datetime import datetime, timedelta

from pokemongo_bot.base_task import BaseTask
from pokemongo_bot.worker_result import WorkerResult
from pokemongo_bot.tree_config_builder import ConfigException
from pokemongo_bot.base_dir import _base_dir


class UpdateLiveStats(BaseTask):
Expand Down Expand Up @@ -92,11 +95,15 @@ def work(self):
"""
if not self._should_display():
return WorkerResult.SUCCESS
line = self._get_stats_line(self._get_player_stats())

player_stats = self._get_player_stats()
line = self._get_stats_line(player_stats)
# If line is empty, it couldn't be generated.
if not line:
return WorkerResult.SUCCESS


self.update_web_stats(player_stats)

if self.terminal_title:
self._update_title(line, _platform)

Expand Down Expand Up @@ -356,3 +363,16 @@ def _get_player_stats(self):
for x in inventory_items
if x.get("inventory_item_data", {}).get("player_stats", {})),
None)

def update_web_stats(self,player_data):
web_inventory = os.path.join(_base_dir, "web", "inventory-%s.json" % self.bot.config.username)

with open(web_inventory, "r") as infile:
json_stats = json.load(infile)

json_stats = [x for x in json_stats if not x.get("inventory_item_data", {}).get("player_stats", None)]

json_stats.append({"inventory_item_data": {"player_stats": player_data}})

with open(web_inventory, "w") as outfile:
json.dump(json_stats, outfile)
12 changes: 12 additions & 0 deletions pokemongo_bot/cell_workers/update_web_inventory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from pokemongo_bot.base_task import BaseTask
from pokemongo_bot import inventory


class UpdateWebInventory(BaseTask):
SUPPORTED_TASK_API_VERSION = 1

def initialize(self):
pass

def work(self):
inventory.update_web_inventory()
2 changes: 2 additions & 0 deletions pokemongo_bot/event_handlers/colored_logging_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ class ColoredLoggingHandler(EventHandler):
'inventory_full': 'yellow',
'item_discard_fail': 'red',
'item_discarded': 'green',
'next_force_recycle': 'green',
'force_recycle': 'green',
'keep_best_release': 'green',
'level_up': 'green',
'level_up_reward': 'green',
Expand Down
Loading