Skip to content

Commit

Permalink
Merge pull request #214 from Gjum/progress
Browse files Browse the repository at this point in the history
implement block entity data and signs, improve Vector3 usage
  • Loading branch information
Gjum committed Jan 31, 2016
2 parents 8d1e2f7 + 3e25e58 commit dc93384
Show file tree
Hide file tree
Showing 12 changed files with 288 additions and 122 deletions.
2 changes: 2 additions & 0 deletions spockbot/mcdata/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@
ENTITY_ACTION_OPEN_INVENTORY = 6

# the six faces of a block
FACE_BOTTOM = 0
FACE_TOP = 1
FACE_Y_NEG = 0
FACE_Y_POS = 1
FACE_Z_NEG = 2
Expand Down
100 changes: 53 additions & 47 deletions spockbot/mcdata/recipes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,62 @@


RecipeItem = namedtuple('RecipeItem', 'id meta amount')
Recipe = namedtuple('Recipe', 'result ingredients in_shape out_shape')
# TODO make Recipe a class, make the helpers its methods


def to_recipe(raw):
def reformat_item(raw, default_meta=None):
if isinstance(raw, dict):
raw = raw.copy() # do not modify arg
if 'metadata' not in raw:
raw['metadata'] = default_meta
if 'count' not in raw:
raw['count'] = 1
return RecipeItem(raw['id'], raw['metadata'], raw['count'])
elif isinstance(raw, list):
return RecipeItem(raw[0], raw[1], 1)
else: # single ID or None
return RecipeItem(raw or None, default_meta, 1)
class Recipe(object):
def __init__(self, raw):
self.result = reformat_item(raw['result'], None)
if 'ingredients' in raw:
self.ingredients = [reformat_item(item, 0)
for item in raw['ingredients']]
self.in_shape = None
self.out_shape = None
else:
self.in_shape = reformat_shape(raw['inShape'])
self.out_shape = reformat_shape(raw['outShape']) \
if 'outShape' in raw else None
self.ingredients = [item for row in self.in_shape for item in row]

def reformat_shape(shape):
return [[reformat_item(item, None) for item in row] for row in shape]
@property
def total_ingredient_amounts(self):
"""
Returns:
dict: In the form { (item_id, metadata) -> amount }
"""
totals = defaultdict(int)
for id, meta, amount in self.ingredients:
totals[(id, meta)] += amount
return totals

result = reformat_item(raw['result'], None)
if 'ingredients' in raw:
ingredients = [reformat_item(item, 0) for item in raw['ingredients']]
in_shape = out_shape = None
else:
in_shape = reformat_shape(raw['inShape'])
out_shape = reformat_shape(raw['outShape']) \
if 'outShape' in raw else None
ingredients = [item for row in in_shape for item in row] # flatten
return Recipe(result, ingredients, in_shape, out_shape)
@property
def ingredient_positions(self):
"""
Returns:
dict: In the form { (item_id, metadata) -> [(x, y, amount), ...] }
"""
positions = defaultdict(list)
for y, row in enumerate(self.in_shape):
for x, (item_id, metadata, amount) in enumerate(row):
positions[(item_id, metadata)].append((x, y, amount))
return positions


def reformat_item(raw, default_meta=None):
if isinstance(raw, dict):
raw = raw.copy() # do not modify arg
if 'metadata' not in raw:
raw['metadata'] = default_meta
if 'count' not in raw:
raw['count'] = 1
return RecipeItem(raw['id'], raw['metadata'], raw['count'])
elif isinstance(raw, list):
return RecipeItem(raw[0], raw[1], 1)
else: # single ID or None
return RecipeItem(raw or None, default_meta, 1)


def reformat_shape(shape):
return [[reformat_item(item, None) for item in row] for row in shape]


def iter_recipes(item_id, meta=None):
Expand All @@ -46,7 +71,7 @@ def iter_recipes(item_id, meta=None):
return # no recipe found, do not yield anything
else:
for raw in recipes_for_item:
recipe = to_recipe(raw)
recipe = Recipe(raw)
if meta is None or meta == recipe.result.meta:
yield recipe

Expand All @@ -56,22 +81,3 @@ def get_any_recipe(item, meta=None):
for matching in iter_recipes(item, meta):
return matching
return None


def total_ingredient_amounts(recipe):
totals = defaultdict(int)
for id, meta, amount in recipe.ingredients:
totals[(id, meta)] += amount
return totals


def ingredient_positions(recipe):
"""
Returns:
dict: In the form { (item_id, metadata) -> [ (x, y, amount), ... ] }
"""
positions = defaultdict(list)
for y, row in enumerate(recipe.in_shape):
for x, (item_id, metadata, amount) in enumerate(row):
positions[(item_id, metadata)].append((x, y, amount))
return positions
4 changes: 2 additions & 2 deletions spockbot/mcp/proto.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@
0x14: 'Entity',
0x15: 'Entity Relative Move',
0x16: 'Entity Look',
0x17: 'Entity Look and Relative Move',
0x17: 'Entity Look And Relative Move',
0x18: 'Entity Teleport',
0x19: 'Entity Head Look',
0x1A: 'Entity Status',
Expand Down Expand Up @@ -480,7 +480,7 @@
(MC_BYTE, 'pitch'),
(MC_BOOL, 'on_ground'),
),
# Entity Look and Relative Move
# Entity Look And Relative Move
0x17: (
(MC_VARINT, 'eid'),
(MC_FP_BYTE, 'dx'),
Expand Down
44 changes: 20 additions & 24 deletions spockbot/plugins/core/event.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,32 @@
"""
Provides the core event loop
"""
import copy
import logging
import signal
from collections import defaultdict
from copy import deepcopy

from spockbot.plugins.base import pl_announce
from spockbot.plugins.tools.event import EVENT_UNREGISTER

logger = logging.getLogger('spockbot')


class EventCore(object):
def __init__(self):
@pl_announce('Event')
class EventPlugin(object):
def __init__(self, ploader, settings):
ploader.provides('Event', self)
self.has_run = False
self.kill_event = False
self.event_handlers = defaultdict(list)
signal.signal(signal.SIGINT, self.kill)
signal.signal(signal.SIGTERM, self.kill)

def event_loop(self, continuous=True):
if continuous:
self.run_continuous()
else:
def event_loop(self, once=False):
if once:
self.run_once()
else:
self.run_continuous()

def run_continuous(self):
if not self.has_run and not self.kill_event:
Expand Down Expand Up @@ -52,23 +54,17 @@ def unreg_event_handler(self, event, handler):
self.event_handlers[event].remove(handler)

def emit(self, event, data=None):
to_remove = []
# reversed, because handlers can register themselves
# for the same event they handle, and the new handler
# is appended to the end of the iterated handler list
# and immediately run, so an infinite loop can be created
for handler in reversed(self.event_handlers[event]):
d = data.clone() if hasattr(data, 'clone') else copy.deepcopy(data)
if handler(event, d) == EVENT_UNREGISTER:
to_remove.append(handler)
for handler in to_remove:
self.event_handlers[event].remove(handler)
# the handler list of this event can change during handler execution,
# so we loop over a copy
try:
for handler in self.event_handlers[event][:]:
d = data.clone() if hasattr(data, 'clone') else deepcopy(data)
if handler(event, d) == EVENT_UNREGISTER:
self.event_handlers[event].remove(handler)
except:
logger.debug('EVENTCORE: Exception while emitting %s %s',
event, data)
raise

def kill(self, *args):
self.kill_event = True


@pl_announce('Event')
class EventPlugin(object):
def __init__(self, ploader, settings):
ploader.provides('Event', EventCore())
9 changes: 4 additions & 5 deletions spockbot/plugins/helpers/craft.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
"""
from math import ceil

from spockbot.mcdata.recipes import get_any_recipe, ingredient_positions, \
total_ingredient_amounts
from spockbot.mcdata.recipes import get_any_recipe
from spockbot.plugins.base import PluginBase, pl_announce
from spockbot.plugins.tools.task import TaskFailed

Expand Down Expand Up @@ -67,7 +66,7 @@ def craft_task(self, recipe, amount=1):
storage_slots = inv.window.persistent_slots

# check ingredients for recipe
total_amounts_needed = total_ingredient_amounts(recipe)
total_amounts_needed = recipe.total_ingredient_amounts
for ingredient, needed in total_amounts_needed.items():
needed *= craft_times
stored = inv.total_stored(ingredient, storage_slots)
Expand All @@ -76,7 +75,7 @@ def craft_task(self, recipe, amount=1):
% ('%s:%s' % ingredient, stored, needed))

# put ingredients into crafting grid
for ingredient, p in ingredient_positions(recipe).items():
for ingredient, p in recipe.ingredient_positions.items():
for (x, y, ingredient_amount) in p:
slot = grid_slots[x + y * grid_width]
for i in range(ingredient_amount * craft_times):
Expand All @@ -101,7 +100,7 @@ def craft_task(self, recipe, amount=1):
while amount > crafted_amt + inv.cursor_slot.amount:
yield inv.async.click_slot(result_slot)
# TODO check that cursor is non-empty, otherwise we did not craft
result_stack_size = inv.cursor_slot.stack_size
result_stack_size = inv.cursor_slot.item.stack_size
if inv.cursor_slot.amount in (prev_cursor_amt, result_stack_size):
# cursor full, put away
crafted_amt += inv.cursor_slot.amount
Expand Down
2 changes: 1 addition & 1 deletion spockbot/plugins/helpers/entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ class EntitiesPlugin(PluginBase):
'PLAY<Entity Velocity': 'handle_velocity',
'PLAY<Entity Relative Move': 'handle_relative_move',
'PLAY<Entity Look': 'handle_set_dict',
'PLAY<Entity Look and Relative Move': 'handle_relative_move',
'PLAY<Entity Look And Relative Move': 'handle_relative_move',
'PLAY<Entity Teleport': 'handle_set_dict',
'PLAY<Entity Head Look': 'handle_set_dict',
'PLAY<Entity Status': 'handle_set_dict',
Expand Down
Loading

0 comments on commit dc93384

Please sign in to comment.