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

Some XP efficiency improvements #2889

Closed
wants to merge 12 commits into from
Closed
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
19 changes: 14 additions & 5 deletions pokemongo_bot/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import random
import re
import sys
import struct
import time
import Queue
import threading
Expand All @@ -31,7 +32,9 @@
from worker_result import WorkerResult
from tree_config_builder import ConfigException, MismatchTaskApiVersion, TreeConfigBuilder
from sys import platform as _platform
import struct



class PokemonGoBot(object):
@property
def position(self):
Expand Down Expand Up @@ -441,11 +444,16 @@ def tick(self):

# Check if session token has expired
self.check_session(self.position[0:2])
start_tick = time.time()

for worker in self.workers:
if worker.work() == WorkerResult.RUNNING:
return

end_tick = time.time()
if end_tick - start_tick < 5:
time.sleep(5 - (end_tick - start_tick))

def get_meta_cell(self):
location = self.position[0:2]
cells = self.find_close_cells(*location)
Expand Down Expand Up @@ -590,7 +598,7 @@ def check_session(self, position):

# prevent crash if return not numeric value
if not self.is_numeric(self.api._auth_provider._ticket_expire):
self.logger.info("Ticket expired value is not numeric", 'yellow')
self.logger.info("Ticket expired value is not numeric")
return

remaining_time = \
Expand Down Expand Up @@ -650,7 +658,7 @@ def login(self):
def get_encryption_lib(self):
if _platform == "linux" or _platform == "linux2" or _platform == "darwin":
file_name = 'encrypt.so'
elif _platform == "Windows" or _platform == "win32":
elif _platform == "Windows" or _platform == "win32" or _platform == "cygwin":
# Check if we are on 32 or 64 bit
if sys.maxsize > 2**32:
file_name = 'encrypt_64.dll'
Expand All @@ -664,8 +672,9 @@ def get_encryption_lib(self):

full_path = path + '/'+ file_name
if not os.path.isfile(full_path):
self.logger.error(file_name + ' is not found! Please place it in the bots root directory or set libencrypt_location in config.')
self.logger.info('Platform: '+ _platform + ' Encrypt.so directory: '+ path)
self.logger.error(file_name + ' is not found! Please place it in the bots root directory.')
self.logger.info('Platform: '+ _platform)
self.logger.info('Bot root directory: '+ path)
sys.exit(1)
else:
self.logger.info('Found '+ file_name +'! Platform: ' + _platform + ' Encrypt.so directory: ' + path)
Expand Down
66 changes: 39 additions & 27 deletions pokemongo_bot/base_task.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,43 @@
import logging
import time


class BaseTask(object):
TASK_API_VERSION = 1

def __init__(self, bot, config):
self.bot = bot
self.config = config
self._validate_work_exists()
self.logger = logging.getLogger(type(self).__name__)
self.initialize()

def _validate_work_exists(self):
method = getattr(self, 'work', None)
if not method or not callable(method):
raise NotImplementedError('Missing "work" method')

def emit_event(self, event, sender=None, level='info', formatted='', data={}):
if not sender:
sender=self
self.bot.event_manager.emit(
event,
sender=sender,
level=level,
formatted=formatted,
data=data
)

def initialize(self):
pass
TASK_API_VERSION = 1

def __init__(self, bot, config):
self.bot = bot
self.config = config
self._validate_work_exists()
self.logger = logging.getLogger(type(self).__name__)
self.last_ran = time.time()
self.run_interval = config.get('run_interval', 10)
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems like this should default to None. It will always run every tick unless both the task utilizes _time_to_run, and the user configures how frequently..

Copy link
Member Author

@douglascamata douglascamata Aug 7, 2016

Choose a reason for hiding this comment

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

@TheSavior the default value is 10 to make it work if the task does run in a time-based way. Otherwise if task is time-based and user don't provide run_interval every time he will see an error.

Copy link
Contributor

Choose a reason for hiding this comment

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

Unless the task was written such that it would delay only if it was configured with an interval. Otherwise it would run every tick

self.initialize()

def _update_last_ran(self):
self.last_ran = time.time()

def _time_to_run(self):
interval = time.time() - self.last_ran
if interval > self.run_interval:
return True
return False

def _validate_work_exists(self):
method = getattr(self, 'work', None)
if not method or not callable(method):
raise NotImplementedError('Missing "work" method')

def emit_event(self, event, sender=None, level='info', formatted='', data={}):
if not sender:
sender=self
self.bot.event_manager.emit(
event,
sender=sender,
level=level,
formatted=formatted,
data=data
)

def initialize(self):
pass
40 changes: 27 additions & 13 deletions pokemongo_bot/cell_workers/catch_lured_pokemon.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from pokemongo_bot.cell_workers.utils import fort_details
from pokemongo_bot.cell_workers.pokemon_catch_worker import PokemonCatchWorker
from pokemongo_bot.base_task import BaseTask
from pokemongo_bot.constants import Constants
from pokemongo_bot.cell_workers.utils import fort_details, distance
from pokemongo_bot.cell_workers.pokemon_catch_worker import PokemonCatchWorker


class CatchLuredPokemon(BaseTask):
Expand All @@ -12,39 +13,52 @@ class CatchLuredPokemon(BaseTask):
def work(self):
lured_pokemon = self.get_lured_pokemon()
if lured_pokemon:
self.catch_pokemon(lured_pokemon)
for pokemon in lured_pokemon:
self.catch_pokemon(pokemon)
Copy link
Contributor

Choose a reason for hiding this comment

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

What happens if your pokebag gets full while catching pokemon?

Copy link
Member Author

Choose a reason for hiding this comment

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

it just skips the catch, like it does already


def get_lured_pokemon(self):
forts_in_range = []
pokemon_to_catch = []
forts = self.bot.get_forts(order_by_distance=True)

if len(forts) == 0:
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems like a big win is limiting this API request to only run on near enough forts. Another win would be caching these lookups so we don't make these requests every tick anyways. I wonder if doing that (here and in more places) would be enough that we wouldn't need to do this internal loop that breaks the model we have had about the tick.

Copy link
Member Author

Choose a reason for hiding this comment

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

What we need is an ORM-like lib to avoid doing any manual call using the raw api object. Otherwise we code lots of duplicated cache, like the one we already have for pokestops timers. Also caching all seen pokestops may have a very big impact in memory usage for long-running bots. A simple cache for all pokestops is not the way, we need something smarter.

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree. I'd rather us refactor a dumb cache to make it smarter, than break our model of what a tick does while we wait for a smart cache to come.

Copy link
Member Author

@douglascamata douglascamata Aug 7, 2016

Choose a reason for hiding this comment

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

@TheSavior yes... we will need a size-limited dict for forts with more data and not only the timer. Using a LRU policy should be good to keep the size limit.

return False

fort = forts[0]
details = fort_details(self.bot, fort_id=fort['id'],
latitude=fort['latitude'],
longitude=fort['longitude'])
fort_name = details.get('name', 'Unknown')
for fort in forts:
distance_to_fort = distance(
self.bot.position[0],
self.bot.position[1],
fort['latitude'],
fort['longitude']
)

encounter_id = fort.get('lure_info', {}).get('encounter_id', None)
if distance_to_fort < Constants.MAX_DISTANCE_FORT_IS_REACHABLE and encounter_id:
forts_in_range.append(fort)


encounter_id = fort.get('lure_info', {}).get('encounter_id', None)
for fort in forts_in_range:
details = fort_details(self.bot, fort_id=fort['id'],
latitude=fort['latitude'],
longitude=fort['longitude'])
fort_name = details.get('name', 'Unknown')
encounter_id = fort['lure_info']['encounter_id']

if encounter_id:
result = {
'encounter_id': encounter_id,
'fort_id': fort['id'],
'fort_name': u"{}".format(fort_name),
'latitude': fort['latitude'],
'longitude': fort['longitude']
}
pokemon_to_catch.append(result)

self.emit_event(
'lured_pokemon_found',
formatted='Lured pokemon at fort {fort_name} ({fort_id})',
data=result
)
return result

return False
return pokemon_to_catch

def catch_pokemon(self, pokemon):
worker = PokemonCatchWorker(pokemon, self.bot)
Expand Down
23 changes: 19 additions & 4 deletions pokemongo_bot/cell_workers/catch_visible_pokemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ def work(self):
lambda x: distance(self.bot.position[0], self.bot.position[1], x['latitude'], x['longitude'])
)
user_web_catchable = 'web/catchable-{}.json'.format(self.bot.config.username)


for pokemon in self.bot.cell['catchable_pokemons']:
with open(user_web_catchable, 'w') as outfile:
json.dump(pokemon, outfile)
self.emit_event(
'catchable_pokemon',
level='debug',
level='info',
data={
'pokemon_id': pokemon['pokemon_id'],
'spawn_point_id': pokemon['spawn_point_id'],
Expand All @@ -32,16 +34,29 @@ def work(self):
'expiration_timestamp_ms': pokemon['expiration_timestamp_ms'],
}
)

return self.catch_pokemon(self.bot.cell['catchable_pokemons'].pop(0))
self.catch_pokemon(pokemon)

if 'wild_pokemons' in self.bot.cell and len(self.bot.cell['wild_pokemons']) > 0:
# Sort all by distance from current pos- eventually this should
# build graph & A* it
self.bot.cell['wild_pokemons'].sort(
key=
lambda x: distance(self.bot.position[0], self.bot.position[1], x['latitude'], x['longitude']))
return self.catch_pokemon(self.bot.cell['wild_pokemons'].pop(0))

for pokemon in self.bot.cell['wild_pokemons']:
self.emit_event(
'catchable_pokemon',
level='info',
data={
'pokemon_id': pokemon['pokemon_data']['pokemon_id'],
'spawn_point_id': pokemon['spawn_point_id'],
'encounter_id': pokemon['encounter_id'],
'latitude': pokemon['latitude'],
'longitude': pokemon['longitude'],
'expiration_timestamp_ms': pokemon['time_till_hidden_ms'],
}
)
self.catch_pokemon(pokemon)

def catch_pokemon(self, pokemon):
worker = PokemonCatchWorker(pokemon, self.bot)
Expand Down
6 changes: 5 additions & 1 deletion pokemongo_bot/cell_workers/collect_level_up_reward.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from pokemongo_bot.base_task import BaseTask

from pokemongo_bot.worker_result import WorkerResult

class CollectLevelUpReward(BaseTask):
SUPPORTED_TASK_API_VERSION = 1
Expand All @@ -12,6 +12,10 @@ def initialize(self):
self.previous_level = 0

def work(self):
if not self._time_to_run():
return WorkerResult.SUCCESS
self._update_last_ran()

self.current_level = self._get_current_level()

# let's check level reward on bot initialization
Expand Down
8 changes: 7 additions & 1 deletion pokemongo_bot/cell_workers/evolve_pokemon.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from pokemongo_bot.human_behaviour import sleep
from pokemongo_bot.item_list import Item
from pokemongo_bot.base_task import BaseTask
from pokemongo_bot.worker_result import WorkerResult


class EvolvePokemon(BaseTask):
Expand All @@ -23,7 +24,9 @@ def _validate_config(self):

def work(self):
if not self._should_run():
return
return WorkerResult.SUCCESS

self._update_last_ran()

response_dict = self.api.get_inventory()
inventory_items = response_dict.get('responses', {}).get('GET_INVENTORY', {}).get('inventory_delta', {}).get(
Expand All @@ -42,6 +45,9 @@ def work(self):
self._execute_pokemon_evolve(pokemon, candy_list, cache)

def _should_run(self):
if not self._time_to_run():
return False
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here


if not self.evolve_all or self.evolve_all[0] == 'none':
return False

Expand Down
5 changes: 5 additions & 0 deletions pokemongo_bot/cell_workers/incubate_eggs.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from pokemongo_bot.human_behaviour import sleep
from pokemongo_bot.base_task import BaseTask
from pokemongo_bot.worker_result import WorkerResult


class IncubateEggs(BaseTask):
Expand All @@ -21,6 +22,10 @@ def _process_config(self):
self.longer_eggs_first = self.config.get("longer_eggs_first", True)

def work(self):
if not self._time_to_run():
return WorkerResult.SUCCESS
self._update_last_ran()

try:
self._check_inventory()
except:
Expand Down
9 changes: 5 additions & 4 deletions pokemongo_bot/cell_workers/move_to_fort.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,18 @@ class MoveToFort(BaseTask):

def initialize(self):
self.lure_distance = 0
self.lure_attraction = True #self.config.get("lure_attraction", True)
self.lure_max_distance = 2000 #self.config.get("lure_max_distance", 2000)
self.lure_attraction = self.config.get("lure_attraction", True)
self.lure_max_distance = self.config.get("lure_max_distance", 2000)
self.ignore_item_count = self.config.get("ignore_item_count", False)

def should_run(self):
has_space_for_loot = self.bot.has_space_for_loot()
if not has_space_for_loot:
self.emit_event(
'inventory_full',
formatted="Not moving to any forts as there aren't enough space. You might want to change your config to recycle more items if this message appears consistently."
formatted="Inventory is full. You might want to change your config to recycle more items if this message appears consistently."
)
return has_space_for_loot or self.bot.softban
return has_space_for_loot or self.ignore_item_count or self.bot.softban

def is_attracted(self):
return (self.lure_distance > 0)
Expand Down
6 changes: 6 additions & 0 deletions pokemongo_bot/cell_workers/nickname_pokemon.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from pokemongo_bot.human_behaviour import sleep
from pokemongo_bot.base_task import BaseTask
from pokemongo_bot.worker_result import WorkerResult


class NicknamePokemon(BaseTask):
SUPPORTED_TASK_API_VERSION = 1
Expand All @@ -10,6 +12,10 @@ def initialize(self):
self.template = ""

def work(self):
if not self._time_to_run():
return WorkerResult.SUCCESS
self._update_last_ran()

try:
inventory = reduce(dict.__getitem__, ["responses", "GET_INVENTORY", "inventory_delta", "inventory_items"], self.bot.get_inventory())
except KeyError:
Expand Down
6 changes: 6 additions & 0 deletions pokemongo_bot/cell_workers/recycle_items.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import os
from pokemongo_bot.base_task import BaseTask
from pokemongo_bot.tree_config_builder import ConfigException
from pokemongo_bot.worker_result import WorkerResult


class RecycleItems(BaseTask):
SUPPORTED_TASK_API_VERSION = 1
Expand All @@ -18,6 +20,10 @@ def _validate_item_filter(self):
raise ConfigException("item {} does not exist, spelling mistake? (check for valid item names in data/items.json)".format(config_item_name))

def work(self):
if not self._time_to_run():
return WorkerResult.SUCCESS
self._update_last_ran()

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

Expand Down
Loading