From 6125aa9ab3e5cbb50c255384210fc9945ca4d782 Mon Sep 17 00:00:00 2001 From: Gjum Date: Thu, 7 Jan 2016 20:42:16 +0100 Subject: [PATCH 01/18] AuthCore does not bitch anymore about setting an empty password --- spockbot/plugins/core/auth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spockbot/plugins/core/auth.py b/spockbot/plugins/core/auth.py index 3b51fe5..eb6eae5 100644 --- a/spockbot/plugins/core/auth.py +++ b/spockbot/plugins/core/auth.py @@ -48,7 +48,7 @@ def set_username(self, username): username = property(get_username, set_username) def set_password(self, password): - if not self.online_mode: + if password and not self.online_mode: logger.warning("PASSWORD PROVIDED WITH ONLINE_MODE == FALSE") logger.warning("YOU PROBABLY DIDN'T WANT TO DO THAT") self.ygg.password = password From 447d2961dfa179fe57416598f78a409f901d7a01 Mon Sep 17 00:00:00 2001 From: Gjum Date: Fri, 8 Jan 2016 02:54:36 +0100 Subject: [PATCH 02/18] Physics only sends pitch/yaw if they changed --- spockbot/plugins/helpers/physics.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/spockbot/plugins/helpers/physics.py b/spockbot/plugins/helpers/physics.py index f1465d9..e63bd8d 100644 --- a/spockbot/plugins/helpers/physics.py +++ b/spockbot/plugins/helpers/physics.py @@ -82,6 +82,7 @@ def __init__(self, ploader, settings): self.world, BoundingBox(const.PLAYER_WIDTH, const.PLAYER_HEIGHT) ) self.pos = self.clientinfo.position + self.prev_dict = None self.skip_tick = False self.pc = PhysicsCore(self.pos, self.vec, self.clientinfo.abilities) ploader.provides('Physics', self.pc) @@ -98,8 +99,13 @@ def resume_physics(self, _=None, __=None): self.event.reg_event_handler('physics_tick', self.physics_tick) def client_tick(self, name, data): - self.net.push_packet('PLAY>Player Position and Look', - self.clientinfo.position.get_dict()) + current_dict = self.clientinfo.position.get_dict() + if self.prev_dict and self.prev_dict['yaw'] == current_dict['yaw'] \ + and self.prev_dict['pitch'] == current_dict['pitch']: + self.net.push_packet('PLAY>Player Position', current_dict) + else: + self.net.push_packet('PLAY>Player Position and Look', current_dict) + self.prev_dict = current_dict def physics_tick(self, _, __): if self.skip_tick: From 742b52679e4a5b099baef11f296b664359d73d3c Mon Sep 17 00:00:00 2001 From: Gjum Date: Fri, 8 Jan 2016 02:20:29 +0100 Subject: [PATCH 03/18] Chat every line separately --- spockbot/plugins/helpers/chat.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/spockbot/plugins/helpers/chat.py b/spockbot/plugins/helpers/chat.py index c1e05f2..cca66f9 100644 --- a/spockbot/plugins/helpers/chat.py +++ b/spockbot/plugins/helpers/chat.py @@ -24,9 +24,10 @@ def __init__(self, net): self.net = net def chat(self, message): - while message: - msg_part, message = message[:100], message[100:] - self.net.push_packet('PLAY>Chat Message', {'message': msg_part}) + for line in message.split('\n'): + while line: + part, line = line[:100], line[100:] + self.net.push_packet('PLAY>Chat Message', {'message': part}) def whisper(self, player, message): self.chat('/tell %s %s' % (player, message)) From 383db6dc6f61344caaeecfb0fd904cbc250c97fd Mon Sep 17 00:00:00 2001 From: Gjum Date: Thu, 7 Jan 2016 02:01:52 +0100 Subject: [PATCH 04/18] Send window property name in inventory_win_prop, store inv_data dict in Window --- spockbot/mcdata/windows.py | 5 +++++ spockbot/plugins/helpers/inventory.py | 13 ++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/spockbot/mcdata/windows.py b/spockbot/mcdata/windows.py index 941257e..f8ae10c 100644 --- a/spockbot/mcdata/windows.py +++ b/spockbot/mcdata/windows.py @@ -256,6 +256,10 @@ def apply(self, inv_plugin): class Window(object): """ Base class for all inventory types. """ + name = None + inv_type = None + inv_data = {} + # the arguments must have the same names as the keys in the packet dict def __init__(self, window_id, title, slot_count, inv_type=None, persistent_slots=None, eid=None): @@ -324,6 +328,7 @@ def _make_window(window_dict): '__module__': sys.modules[__name__], 'name': str(window_dict['name']), 'inv_type': str(window_dict['id']), + 'inv_data': window_dict, } # creates function-local index and size variables diff --git a/spockbot/plugins/helpers/inventory.py b/spockbot/plugins/helpers/inventory.py index 7813ea8..35a3feb 100644 --- a/spockbot/plugins/helpers/inventory.py +++ b/spockbot/plugins/helpers/inventory.py @@ -223,9 +223,16 @@ def emit_set_slot(self, slot): self.event.emit('inventory_set_slot', {'slot': slot}) def handle_window_prop(self, event, packet): - self.inventory.window.properties[packet.data['property']] = \ - packet.data['value'] - self.event.emit('inventory_win_prop', packet.data) + window = self.inventory.window + prop_id = packet.data['property'] + prop_name = window.inv_data['properties'][prop_id] + window.properties[prop_id] = packet.data['value'] + self.event.emit('inventory_win_prop', { + 'window_id': packet.data['window_id'], + 'property_name': prop_name, + 'property_id': prop_id, + 'value': packet.data['value'], + }) def handle_confirm_transaction(self, event, packet): click = self.last_click From d1e5c345c43064afa32de27432307d01045f7ec4 Mon Sep 17 00:00:00 2001 From: Gjum Date: Fri, 8 Jan 2016 02:56:11 +0100 Subject: [PATCH 05/18] Fix InventoryAsync a bit --- spockbot/plugins/tools/inventory_async.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/spockbot/plugins/tools/inventory_async.py b/spockbot/plugins/tools/inventory_async.py index 71a1c06..012ef38 100644 --- a/spockbot/plugins/tools/inventory_async.py +++ b/spockbot/plugins/tools/inventory_async.py @@ -6,7 +6,8 @@ def unpack_slots_list(slots): - if len(slots) > 1 or isinstance(slots, int) or hasattr(slots, 'slot_nr'): + if len(slots) > 1 or isinstance(slots[0], int) \ + or hasattr(slots[0], 'slot_nr'): return slots return slots[0] @@ -15,13 +16,13 @@ class InventoryAsync(object): def __init__(self, inventory): self.inventory = inventory - def click_slot(self, slot, *args, **kwargs): + def click_slot(self, slot, right=False): if isinstance(slot, int): slot = self.inventory.window.slots[slot] old_slot = slot.copy() old_cursor = self.inventory.cursor_slot.copy() - action_id = self.inventory.click_slot(slot, *args, **kwargs) + action_id = self.inventory.click_slot(slot, right) if not action_id: raise TaskFailed('Click slot failed: not clicked') yield 'inventory_click_response', check_key('action_id', action_id) @@ -38,18 +39,17 @@ def click_slot(self, slot, *args, **kwargs): raise TaskFailed('Click slot failed: slot %i did not change (%s)' % (old_slot.slot_nr, old_slot)) - def drop_slot(self, slot=None, *args, **kwargs): - # TODO drop_slot is untested - old_slot = getattr(slot, 'slot_nr', slot) + def drop_slot(self, slot=None, drop_stack=False): + old_index = getattr(slot, 'slot_nr', slot) - action_id = self.inventory.drop_slot(slot, *args, **kwargs) + action_id = self.inventory.drop_slot(slot, drop_stack) if not action_id: raise TaskFailed('Drop slot failed: not clicked') yield 'inventory_click_response', check_key('action_id', action_id) - new_slot = self.inventory.window.slots[old_slot] - if old_slot is not None and new_slot.amount > 0: - raise TaskFailed('Drop slot failed: slot %i not empty' % old_slot) + new_slot = self.inventory.window.slots[old_index] + if drop_stack and old_index is not None and new_slot.amount > 0: + raise TaskFailed('Drop slot failed: slot %i not empty' % old_index) def creative_set_slot(self, slot_nr=None, slot_dict=None, slot=None): self.inventory.creative_set_slot(slot_nr, slot_dict, slot) From 904f314f6b2d297d34099b83f0d80d2a9722f5cd Mon Sep 17 00:00:00 2001 From: Gjum Date: Sat, 9 Jan 2016 20:13:24 +0100 Subject: [PATCH 06/18] Documentation --- spockbot/plugins/helpers/craft.py | 6 +++-- spockbot/plugins/helpers/inventory.py | 32 +++++++++++++++++++---- spockbot/plugins/tools/inventory_async.py | 4 ++- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/spockbot/plugins/helpers/craft.py b/spockbot/plugins/helpers/craft.py index 9f49582..f5411a2 100644 --- a/spockbot/plugins/helpers/craft.py +++ b/spockbot/plugins/helpers/craft.py @@ -19,8 +19,10 @@ def __init__(self, ploader, settings): def craft(self, item=None, meta=None, amount=1, recipe=None, parent=None): """ - Starts a ``craft_task``. Returns the recipe used for crafting. - Either ``item`` or ``recipe`` has to be given. + Starts a ``craft_task``. Either ``item`` or ``recipe`` has to be given. + + Returns: + Optional[Recipe]: The recipe used for crafting. """ if recipe: item, meta, _ = recipe.result diff --git a/spockbot/plugins/helpers/inventory.py b/spockbot/plugins/helpers/inventory.py index 35a3feb..6ad3169 100644 --- a/spockbot/plugins/helpers/inventory.py +++ b/spockbot/plugins/helpers/inventory.py @@ -36,12 +36,15 @@ def total_stored(self, wanted, slots=None): def find_slot(self, wanted, slots=None): """ - Returns the first slot containing the item or None if not found. Searches the given slots or, if not given, active hotbar slot, hotbar, inventory, open window in this order. Args: wanted: function(Slot) or Slot or itemID or (itemID, metadata) + + Returns: + Optional[Slot]: The first slot containing the item + or None if not found. """ for slot in self.find_slots(wanted, slots): return slot @@ -78,7 +81,15 @@ def select_active_slot(self, slot_or_hotbar_index): {'slot': slot_or_hotbar_index}) def click_slot(self, slot, right=False): - if isinstance(slot, int): # also allow slot nr + """ + Left-click or right-click the slot. + + Args: + slot (Slot): The clicked slot. Can be ``Slot`` instance or integer. + Set to ``inventory.cursor_slot`` or -999 + for clicking outside the window. + """ + if isinstance(slot, int): slot = self.window.slots[slot] button = constants.INV_BUTTON_RIGHT \ if right else constants.INV_BUTTON_LEFT @@ -111,7 +122,12 @@ def active_slot(self): @property def inv_slots_preferred(self): """ - The preferred order to search for items or empty slots. + List of all available inventory slots in the preferred search order. + Does not include the additional slots from the open window. + + 1. active slot + 2. remainder of the hotbar + 3. remainder of the persistent inventory """ slots = [self.active_slot] slots.extend(slot for slot in self.window.hotbar_slots @@ -265,8 +281,14 @@ def emit_click_response(*_): def send_click(self, click): """ - Returns the click's action ID if the click could be sent, - None if the previous click has not been received and confirmed yet. + Sends a click to the server if the previous click has been confirmed. + + Args: + click (BaseClick): The click to send. + + Returns: + the click's action ID if the click could be sent, + None if the previous click has not been received and confirmed yet. """ # only send if previous click got confirmed if self.last_click: diff --git a/spockbot/plugins/tools/inventory_async.py b/spockbot/plugins/tools/inventory_async.py index 012ef38..db1e773 100644 --- a/spockbot/plugins/tools/inventory_async.py +++ b/spockbot/plugins/tools/inventory_async.py @@ -62,9 +62,11 @@ def creative_set_slot(self, slot_nr=None, slot_dict=None, slot=None): def store_or_drop(self): """ Stores the cursor item or drops it if the inventory is full. - Returns the slot used to store it, or None if dropped. Tip: look directly up or down before calling this, so you can pick up the dropped item when the inventory frees up again. + + Returns: + Slot: The slot used to store it, or None if dropped. """ inv = self.inventory if inv.cursor_slot.is_empty: # nothing to drop From bea3a7c827377617420d1bbc41642ec85a01884c Mon Sep 17 00:00:00 2001 From: Gjum Date: Thu, 7 Jan 2016 20:34:35 +0100 Subject: [PATCH 07/18] Formatting --- spockbot/plugins/helpers/auxiliary.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/spockbot/plugins/helpers/auxiliary.py b/spockbot/plugins/helpers/auxiliary.py index 47f2f2e..9efd9a7 100644 --- a/spockbot/plugins/helpers/auxiliary.py +++ b/spockbot/plugins/helpers/auxiliary.py @@ -13,6 +13,7 @@ from spockbot.mcp import proto from spockbot.plugins.base import PluginBase + backend = default_backend() @@ -43,7 +44,7 @@ def handshake_and_login_start(self, _, __): 'protocol_version': proto.MC_PROTOCOL_VERSION, 'host': self.net.host, 'port': self.net.port, - 'next_state': proto.LOGIN_STATE + 'next_state': proto.LOGIN_STATE, }) self.net.push_packet('LOGIN>Login Start', {'name': self.auth.username}) @@ -54,13 +55,10 @@ def handle_encrypt_request(self, name, packet): self.auth.send_session_auth(pubkey_raw, packet.data['server_id']) pubkey = serialization.load_der_public_key(pubkey_raw, backend) encrypt = lambda data: pubkey.encrypt(data, padding.PKCS1v15()) # flake8: noqa - self.net.push_packet( - 'LOGIN>Encryption Response', - { - 'shared_secret': encrypt(self.auth.shared_secret), - 'verify_token': encrypt(packet.data['verify_token']), - } - ) + self.net.push_packet('LOGIN>Encryption Response', { + 'shared_secret': encrypt(self.auth.shared_secret), + 'verify_token': encrypt(packet.data['verify_token']), + }) self.net.enable_crypto(self.auth.shared_secret) # Keep Alive - Reflects data back to server @@ -70,6 +68,5 @@ def handle_keep_alive(self, name, packet): # You be dead def handle_death(self, name, data): - self.net.push_packet( - 'PLAY>Client Status', {'action': constants.CL_STATUS_RESPAWN} - ) + self.net.push_packet('PLAY>Client Status', + {'action': constants.CL_STATUS_RESPAWN}) From 487999944c4c6900bbdcf89262688a13908c77df Mon Sep 17 00:00:00 2001 From: Gjum Date: Fri, 8 Jan 2016 00:46:32 +0100 Subject: [PATCH 08/18] Implement {Block,Item}.__str__(), refactor Slot.__repr__() --- spockbot/mcdata/blocks.py | 4 ++++ spockbot/mcdata/items.py | 4 ++++ spockbot/mcdata/windows.py | 13 ++++--------- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/spockbot/mcdata/blocks.py b/spockbot/mcdata/blocks.py index 7089858..769aefa 100644 --- a/spockbot/mcdata/blocks.py +++ b/spockbot/mcdata/blocks.py @@ -51,6 +51,10 @@ def __init__(self, meta=None): if self.id in _block_exts: _block_exts[self.id](self) + def __str__(self): + return '%s %i:%i' % (self.display_name, self.id, + getattr(self, 'metadata', 0)) + def _convert_boundingbox(bb): if bb == 'block': diff --git a/spockbot/mcdata/items.py b/spockbot/mcdata/items.py index e7393da..f53ca2b 100644 --- a/spockbot/mcdata/items.py +++ b/spockbot/mcdata/items.py @@ -34,6 +34,10 @@ def __init__(self, meta=None): # TODO: apply other all possible variations self.display_name = self.variations[self.metadata]["display_name"] + def __str__(self): + return '%s %i:%i' % (self.display_name, self.id, + getattr(self, 'metadata', 0)) + def _make_item(item_dict): cls_name = '%sItem' % camel_case(str(item_dict['name'])) diff --git a/spockbot/mcdata/windows.py b/spockbot/mcdata/windows.py index f8ae10c..bd8bae9 100644 --- a/spockbot/mcdata/windows.py +++ b/spockbot/mcdata/windows.py @@ -40,7 +40,6 @@ def __init__(self, window, slot_nr, id=constants.INV_ITEMID_EMPTY, self.damage = damage self.amount = amount self.nbt = enchants - self.item = get_item_or_block(self.item_id, self.damage) or Item() def move_to_window(self, window, slot_nr): @@ -83,20 +82,16 @@ def __bool__(self): return not self.is_empty def __repr__(self): - vals = { - 'name': self.item.display_name, - 'max': self.item.stack_size, - } - vals.update(self.__dict__) if self.is_empty: s = 'empty' else: - s = '%(amount)i/%(max)i %(item_id)i:%(damage)i %(name)s' % vals + item = self.item + s = '%i/%i %s' % (self.amount, item.stack_size, str(item)) if self.slot_nr != -1: - s += ' at %(slot_nr)i' % self.__dict__ + s += ' at %i' % self.slot_nr if self.window: - s += ' in %(window)s' % self.__dict__ + s += ' in %s' % self.window return '' % s From 373514eebf256be9a3b7004079234b03fefdc849 Mon Sep 17 00:00:00 2001 From: Gjum Date: Sat, 9 Jan 2016 20:55:42 +0100 Subject: [PATCH 09/18] Default block/item metadata is 0 --- spockbot/mcdata/__init__.py | 2 +- spockbot/mcdata/blocks.py | 2 +- spockbot/mcdata/items.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spockbot/mcdata/__init__.py b/spockbot/mcdata/__init__.py index b9518b9..4b8931d 100644 --- a/spockbot/mcdata/__init__.py +++ b/spockbot/mcdata/__init__.py @@ -3,7 +3,7 @@ from spockbot.mcdata.utils import find_by -def get_item_or_block(find, meta=None, init=True): +def get_item_or_block(find, meta=0, init=True): ret = None if isinstance(find, int): # by id ret = find_by(find, items.items, blocks.blocks) diff --git a/spockbot/mcdata/blocks.py b/spockbot/mcdata/blocks.py index 769aefa..72fc47c 100644 --- a/spockbot/mcdata/blocks.py +++ b/spockbot/mcdata/blocks.py @@ -14,7 +14,7 @@ _block_exts = {} -def get_block(block, meta=None, init=True): +def get_block(block, meta=0, init=True): ret = None if isinstance(block, int): # by id ret = find_by(block, blocks) diff --git a/spockbot/mcdata/items.py b/spockbot/mcdata/items.py index f53ca2b..46b0587 100644 --- a/spockbot/mcdata/items.py +++ b/spockbot/mcdata/items.py @@ -8,7 +8,7 @@ items_name = {} -def get_item(item, meta=None, init=True): +def get_item(item, meta=0, init=True): ret = None if isinstance(item, int): # by id ret = find_by(item, items) From 80baa2ea371741c77b3fc7bf0e76cb52e4691059 Mon Sep 17 00:00:00 2001 From: Gjum Date: Fri, 8 Jan 2016 00:47:45 +0100 Subject: [PATCH 10/18] make_slot_check() now also takes names and Item instances --- spockbot/mcdata/windows.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/spockbot/mcdata/windows.py b/spockbot/mcdata/windows.py index bd8bae9..e19546d 100644 --- a/spockbot/mcdata/windows.py +++ b/spockbot/mcdata/windows.py @@ -5,6 +5,7 @@ from minecraft_data.v1_8 import windows_list from spockbot.mcdata import constants, get_item_or_block +from spockbot.mcdata.blocks import Block from spockbot.mcdata.items import Item from spockbot.mcdata.utils import camel_case, snake_case @@ -23,10 +24,17 @@ def make_slot_check(wanted): if isinstance(wanted, int): item, meta = wanted, None elif isinstance(wanted, Slot): - item, meta = wanted.item_id, wanted.damage - # TODO compare NBT - else: # wanted is list of (id, meta) - item, meta = wanted + item, meta = wanted.item_id, wanted.damage # TODO compare NBT + elif isinstance(wanted, (Item, Block)): + item, meta = wanted.id, wanted.metadata + elif isinstance(wanted, str): + item_or_block = get_item_or_block(wanted, init=True) + item, meta = item_or_block.id, item_or_block.metadata + else: # wanted is (id, meta) + try: + item, meta = wanted + except TypeError: + raise ValueError('Illegal args for make_slot_check(): %s' % wanted) return lambda slot: item == slot.item_id and meta in (None, slot.damage) From dfdc324661a1bd8ef7b3c7f47572ffbb61465aed Mon Sep 17 00:00:00 2001 From: Gjum Date: Sat, 9 Jan 2016 20:34:18 +0100 Subject: [PATCH 11/18] Fix dropping items --- spockbot/mcdata/windows.py | 40 +++++++++++++++------------ spockbot/plugins/helpers/inventory.py | 28 +++++++++++++++++-- 2 files changed, 48 insertions(+), 20 deletions(-) diff --git a/spockbot/mcdata/windows.py b/spockbot/mcdata/windows.py index e19546d..1447c23 100644 --- a/spockbot/mcdata/windows.py +++ b/spockbot/mcdata/windows.py @@ -199,8 +199,11 @@ def __init__(self, slot, button=constants.INV_BUTTON_LEFT): 'Clicking with button %s not implemented' % button) def get_packet(self, inv_plugin): + slot_nr = self.slot.slot_nr + if self.slot == inv_plugin.cursor_slot: + slot_nr = constants.INV_OUTSIDE_WINDOW return { - 'slot': self.slot.slot_nr, + 'slot': slot_nr, 'button': self.button, 'mode': 0, 'clicked_item': self.slot.get_dict(), @@ -209,13 +212,19 @@ def get_packet(self, inv_plugin): def apply(self, inv_plugin): clicked = self.slot cursor = inv_plugin.cursor_slot - if self.button == constants.INV_BUTTON_LEFT: + if clicked == cursor: + if self.button == constants.INV_BUTTON_LEFT: + clicked.amount = 0 + elif self.button == constants.INV_BUTTON_RIGHT: + clicked.amount -= 1 + self.cleanup_if_empty(clicked) + elif self.button == constants.INV_BUTTON_LEFT: if clicked.stacks_with(cursor): self.transfer(cursor, clicked, cursor.amount) else: self.swap_slots(cursor, clicked) elif self.button == constants.INV_BUTTON_RIGHT: - if cursor.item_id == constants.INV_ITEMID_EMPTY: + if cursor.is_empty: # transfer half, round up self.transfer(clicked, cursor, (clicked.amount + 1) // 2) elif clicked.is_empty or clicked.stacks_with(cursor): @@ -233,27 +242,24 @@ def __init__(self, slot, drop_stack=False): self.drop_stack = drop_stack def get_packet(self, inv_plugin): - if self.slot == inv_plugin.active_slot: - slot_nr = constants.INV_OUTSIDE_WINDOW # drop cursor slot - elif inv_plugin.cursor_slot.item_id != constants.INV_ITEMID_EMPTY: - return None # can't drop while holding an item - else: # default case - slot_nr = self.slot.slot_nr + if self.slot == inv_plugin.cursor_slot: + raise ValueError("Can't drop cursor slot, use SingleClick") + if not inv_plugin.cursor_slot.is_empty: + raise ValueError("Can't drop other slots: cursor slot is occupied") + return { - 'slot': slot_nr, + 'slot': self.slot.slot_nr, 'button': 1 if self.drop_stack else 0, 'mode': 4, 'clicked_item': inv_plugin.cursor_slot.get_dict(), } def apply(self, inv_plugin): - if inv_plugin.cursor_slot.is_empty: - if self.drop_stack: - self.slot.amount = 0 - else: - self.slot.amount -= 1 - self.cleanup_if_empty(self.slot) - # else: cursor not empty, can't drop while holding an item + if self.drop_stack: + self.slot.amount = 0 + else: + self.slot.amount -= 1 + self.cleanup_if_empty(self.slot) class Window(object): diff --git a/spockbot/plugins/helpers/inventory.py b/spockbot/plugins/helpers/inventory.py index 6ad3169..28b1697 100644 --- a/spockbot/plugins/helpers/inventory.py +++ b/spockbot/plugins/helpers/inventory.py @@ -86,7 +86,7 @@ def click_slot(self, slot, right=False): Args: slot (Slot): The clicked slot. Can be ``Slot`` instance or integer. - Set to ``inventory.cursor_slot`` or -999 + Set to ``inventory.cursor_slot`` for clicking outside the window. """ if isinstance(slot, int): @@ -96,10 +96,32 @@ def click_slot(self, slot, right=False): return self.send_click(windows.SingleClick(slot, button)) def drop_slot(self, slot=None, drop_stack=False): - if slot is None: # drop held item - slot = self.active_slot + """ + Drop one or all items of the slot. + + Does not wait for confirmation from the server. If you want that, + use a ``Task`` and ``yield inventory.async.drop_slot()`` instead. + + If ``slot`` is None, drops the ``cursor_slot`` or, if that's empty, + the currently held item (``active_slot``). + + Args: + slot (Optional[Slot]): The dropped slot. Can be None, integer, + or ``Slot`` instance. + + Returns: + int: The action ID of the click + """ + if slot is None: + if self.cursor_slot.is_empty: + slot = self.active_slot + else: + slot = self.cursor_slot elif isinstance(slot, int): # also allow slot nr slot = self.window.slots[slot] + if slot == self.cursor_slot: + # dropping items from cursor is done via normal click + return self.click_slot(self.cursor_slot, not drop_stack) return self.send_click(windows.DropClick(slot, drop_stack)) def close_window(self): From 8611c318ffc735a3e32db398585684e93bec24c6 Mon Sep 17 00:00:00 2001 From: Gjum Date: Fri, 8 Jan 2016 02:19:55 +0100 Subject: [PATCH 12/18] Allow setting task name in run_task(), add Task.last_child property --- spockbot/plugins/core/taskmanager.py | 6 ++++-- spockbot/plugins/tools/task.py | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/spockbot/plugins/core/taskmanager.py b/spockbot/plugins/core/taskmanager.py index 1c03215..bf91698 100644 --- a/spockbot/plugins/core/taskmanager.py +++ b/spockbot/plugins/core/taskmanager.py @@ -10,8 +10,10 @@ def __init__(self, ploader, settings): super(TaskManager, self).__init__(ploader, settings) ploader.provides('TaskManager', self) - def run_task(self, task, parent=None): + def run_task(self, task, parent=None, name=None): if not isinstance(task, Task): - task = Task(task, parent) + task = Task(task, parent, name) + if parent: + parent.last_child = task task.run(self) return task diff --git a/spockbot/plugins/tools/task.py b/spockbot/plugins/tools/task.py index a8edc2d..5689add 100644 --- a/spockbot/plugins/tools/task.py +++ b/spockbot/plugins/tools/task.py @@ -99,6 +99,7 @@ def __init__(self, task, parent=None, name=None): self.name = name or task.__name__ self.task = task self.parent = parent + self.last_child = None self.expected = {} # event -> check @property @@ -178,4 +179,4 @@ def parse_response(self, response): else: # unexpected self.expected.clear() raise ValueError('Illegal task yield argument of type %s: %s' - % type(response), response) + % (type(response), response)) From 6da771f5eea927a95df6f2ad19f6ce2c658b2448 Mon Sep 17 00:00:00 2001 From: Gjum Date: Fri, 8 Jan 2016 02:28:26 +0100 Subject: [PATCH 13/18] Add TaskFailed.__str__(): newline-separated failed tasks and errors --- spockbot/plugins/tools/task.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/spockbot/plugins/tools/task.py b/spockbot/plugins/tools/task.py index 5689add..d1b8ae8 100644 --- a/spockbot/plugins/tools/task.py +++ b/spockbot/plugins/tools/task.py @@ -79,6 +79,20 @@ def full_tasktrace(self): else: return self.tasktrace + def __str__(self): + """ + Newline-separated text with all failed tasks and all previous errors. + """ + s = self.prev_error.failures + '\n' if self.prev_error else '' + + s += '%s' % self.message + if self.args[1:]: + s += ' %s' % str(self.args[1:]) + + for task in self.tasktrace: + s += '\n in %s %s' % (task.task.__name__, task.name) + return s + class TaskCallback(object): def __init__(self, cb=None, eb=None): From e6c539e9e23222aae743dd1786c92a58b119a41c Mon Sep 17 00:00:00 2001 From: Gjum Date: Thu, 7 Jan 2016 22:50:46 +0100 Subject: [PATCH 14/18] Fix looking TODOs in interact plugin, add eye_pos property to clientinfo --- spockbot/mcdata/constants.py | 1 + spockbot/plugins/helpers/clientinfo.py | 7 ++++++- spockbot/plugins/helpers/interact.py | 9 ++++----- tests/plugins/helpers/test_interact.py | 18 +++++++++--------- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/spockbot/mcdata/constants.py b/spockbot/mcdata/constants.py index 575b708..c26f9d8 100644 --- a/spockbot/mcdata/constants.py +++ b/spockbot/mcdata/constants.py @@ -8,6 +8,7 @@ ########### CLIENT_TICK_RATE = 0.05 +PLAYER_EYE_HEIGHT = 1.62 PLAYER_HEIGHT = 1.80 PLAYER_WIDTH = 0.6 diff --git a/spockbot/plugins/helpers/clientinfo.py b/spockbot/plugins/helpers/clientinfo.py index 4d016f0..8216bf5 100644 --- a/spockbot/plugins/helpers/clientinfo.py +++ b/spockbot/plugins/helpers/clientinfo.py @@ -78,8 +78,9 @@ class ClientInfo(object): game_info (GameInfo): Information about the current world/server spawn_position (Position): Players initial position health (PlayerHealth): Player's health, food and saturation - position (PlayerPosition): Player's Current position + position (PlayerPosition): Player's current position player_list (dict): List of all players in the server + eye_pos (PlayerPosition): Player's eye position """ def __init__(self): @@ -94,6 +95,10 @@ def __init__(self): self.position = PlayerPosition() self.player_list = {} + @property + def eye_pos(self): + return self.position + (0, const.PLAYER_EYE_HEIGHT, 0) + def reset(self): """Resets the information in ClientInfo""" self.__init__() diff --git a/spockbot/plugins/helpers/interact.py b/spockbot/plugins/helpers/interact.py index e9c9f93..69d189a 100644 --- a/spockbot/plugins/helpers/interact.py +++ b/spockbot/plugins/helpers/interact.py @@ -84,11 +84,10 @@ def look_at_rel(self, delta): self.look(*delta.yaw_pitch) def look_at(self, pos): - delta = pos - self.clientinfo.position - delta.y -= constants.PLAYER_HEIGHT + delta = pos - self.clientinfo.eye_pos if delta.x or delta.z: self.look_at_rel(delta) - else: + else: # looking up or down, do not turn head self.look(self.clientinfo.position.yaw, delta.yaw_pitch.pitch) def _send_dig_block(self, status, pos=None, face=constants.FACE_Y_POS): @@ -102,7 +101,7 @@ def _send_dig_block(self, status, pos=None, face=constants.FACE_Y_POS): def start_digging(self, pos): if self.auto_look: - self.look_at(pos) # TODO look at block center + self.look_at(pos.floor().iadd(0.5, 0.5, 0.5)) self._send_dig_block(constants.DIG_START, pos) if self.auto_swing: self.swing_arm() @@ -144,7 +143,7 @@ def click_block(self, pos, face=1, cursor_pos=Vector3(8, 8, 8), """ if look_at_block and self.auto_look: # TODO look at cursor_pos - self.look_at(pos) + self.look_at(pos.floor().iadd(0.5, 0.5, 0.5)) self._send_click_block(pos, face, cursor_pos) if swing and self.auto_swing: self.swing_arm() diff --git a/tests/plugins/helpers/test_interact.py b/tests/plugins/helpers/test_interact.py index 4ca231f..144b964 100644 --- a/tests/plugins/helpers/test_interact.py +++ b/tests/plugins/helpers/test_interact.py @@ -1,7 +1,6 @@ from unittest import TestCase -from spockbot.mcdata.constants import \ - ATTACK_ENTITY, INTERACT_ENTITY, PLAYER_HEIGHT +from spockbot.mcdata import constants from spockbot.plugins.helpers.clientinfo import PlayerPosition from spockbot.plugins.helpers.interact import InteractPlugin from spockbot.vector import Vector3 @@ -53,6 +52,7 @@ class InventoryMock(object): class ClientInfoMock(object): eid = 123 position = PlayerPosition(1., 2., 3.) + eye_pos = position + (0, constants.PLAYER_EYE_HEIGHT, 0) class InteractPluginTest(TestCase): @@ -97,7 +97,7 @@ def test_look(self): self.assertAlmostEqual(ClientInfoMock.position.yaw, -90) self.assertAlmostEqual(ClientInfoMock.position.pitch, 0) - self.plug.look_at(Vector3(0, 2 + PLAYER_HEIGHT, 3)) + self.plug.look_at(Vector3(0, 2 + constants.PLAYER_EYE_HEIGHT, 3)) self.assertAlmostEqual(ClientInfoMock.position.yaw, 90) self.assertAlmostEqual(ClientInfoMock.position.pitch, 0) @@ -115,32 +115,32 @@ def test_activate_item(self): # TODO deactivate_item def test_entity(self): - entity = DataDict(eid=234, x=2, y=2 + PLAYER_HEIGHT, z=4) + entity = DataDict(eid=234, x=2, y=2 + constants.PLAYER_EYE_HEIGHT, z=4) self.plug.use_entity(entity) self.assertAlmostEqual(ClientInfoMock.position.yaw, -45) self.assertAlmostEqual(ClientInfoMock.position.pitch, 0) - self.assertEqual(NetMock.datas[-2].action, INTERACT_ENTITY) + self.assertEqual(NetMock.datas[-2].action, constants.INTERACT_ENTITY) self.assertEqual(NetMock.datas[-2].target, 234) self.assertEqual(NetMock.idents[-1], 'PLAY>Animation') self.plug.auto_look = False - entity = DataDict(eid=345, x=0, y=3 + PLAYER_HEIGHT, z=3) + entity = DataDict(eid=345, x=0, y=3 + constants.PLAYER_EYE_HEIGHT, z=3) self.plug.attack_entity(entity) # different pos, but look shouldn't have changed self.assertAlmostEqual(ClientInfoMock.position.yaw, -45) self.assertAlmostEqual(ClientInfoMock.position.pitch, 0) - self.assertEqual(NetMock.datas[-2].action, ATTACK_ENTITY) + self.assertEqual(NetMock.datas[-2].action, constants.ATTACK_ENTITY) self.assertEqual(NetMock.datas[-2].target, 345) self.assertEqual(NetMock.idents[-1], 'PLAY>Animation') self.plug.auto_look = True self.plug.auto_swing = False - entity = DataDict(eid=456, x=2, y=3 + PLAYER_HEIGHT, z=3) + entity = DataDict(eid=456, x=2, y=3 + constants.PLAYER_EYE_HEIGHT, z=3) self.plug.mount_vehicle(entity) self.assertAlmostEqual(ClientInfoMock.position.yaw, -90) self.assertAlmostEqual(ClientInfoMock.position.pitch, -45) - self.assertEqual(NetMock.datas[-1].action, INTERACT_ENTITY) + self.assertEqual(NetMock.datas[-1].action, constants.INTERACT_ENTITY) self.assertEqual(NetMock.datas[-1].target, 456) self.plug.auto_swing = True From d009b13cef8bd1230dda68d8ac26712fc6e17e89 Mon Sep 17 00:00:00 2001 From: Gjum Date: Thu, 7 Jan 2016 23:18:11 +0100 Subject: [PATCH 15/18] Allow radians in interact.look --- spockbot/plugins/helpers/interact.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/spockbot/plugins/helpers/interact.py b/spockbot/plugins/helpers/interact.py index 69d189a..ac17315 100644 --- a/spockbot/plugins/helpers/interact.py +++ b/spockbot/plugins/helpers/interact.py @@ -11,6 +11,7 @@ By default, the client sends swing and look packets like the vanilla client. This can be disabled by setting the ``auto_swing`` and ``auto_look`` flags. """ +import math from spockbot.mcdata import constants from spockbot.mcp import nbt from spockbot.mcp.proto import MC_SLOT @@ -69,16 +70,18 @@ def jump_horse(self, jump_boost=100): def open_inventory(self): self._entity_action(constants.ENTITY_ACTION_OPEN_INVENTORY) - def look(self, yaw=0.0, pitch=0.0): - """ - Turn the head. Both angles are in degrees. - """ - self.clientinfo.position.pitch = pitch - self.clientinfo.position.yaw = yaw + def look(self, yaw=0.0, pitch=0.0, radians=False): + if radians: + self.clientinfo.position.yaw = math.degrees(yaw) + self.clientinfo.position.pitch = math.degrees(pitch) + else: + self.clientinfo.position.yaw = yaw + self.clientinfo.position.pitch = pitch - def look_rel(self, d_yaw=0.0, d_pitch=0.0): + def look_rel(self, d_yaw=0.0, d_pitch=0.0, radians=False): self.look(self.clientinfo.position.yaw + d_yaw, - self.clientinfo.position.pitch + d_pitch) + self.clientinfo.position.pitch + d_pitch, + radians=radians) def look_at_rel(self, delta): self.look(*delta.yaw_pitch) From 26e7966e2161373c906d4614d3b9e864c6eed9b7 Mon Sep 17 00:00:00 2001 From: Gjum Date: Fri, 8 Jan 2016 18:59:09 +0100 Subject: [PATCH 16/18] Do not swing arm when right-clicking entity --- spockbot/plugins/helpers/interact.py | 7 +++++-- tests/plugins/helpers/test_interact.py | 15 ++++++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/spockbot/plugins/helpers/interact.py b/spockbot/plugins/helpers/interact.py index ac17315..5150b97 100644 --- a/spockbot/plugins/helpers/interact.py +++ b/spockbot/plugins/helpers/interact.py @@ -196,15 +196,18 @@ def use_entity(self, entity, cursor_pos=None, """ if self.auto_look: self.look_at(Vector3(entity)) # TODO look at cursor_pos - if cursor_pos is not None: + + if cursor_pos is not None and action == constants.INTERACT_ENTITY: action = constants.INTERACT_ENTITY_AT + packet = {'target': entity.eid, 'action': action} if action == constants.INTERACT_ENTITY_AT: packet['target_x'] = cursor_pos.x packet['target_y'] = cursor_pos.y packet['target_z'] = cursor_pos.z self.net.push_packet('PLAY>Use Entity', packet) - if self.auto_swing: + + if self.auto_swing and action == constants.ATTACK_ENTITY: self.swing_arm() def attack_entity(self, entity): diff --git a/tests/plugins/helpers/test_interact.py b/tests/plugins/helpers/test_interact.py index 144b964..36249bd 100644 --- a/tests/plugins/helpers/test_interact.py +++ b/tests/plugins/helpers/test_interact.py @@ -115,13 +115,22 @@ def test_activate_item(self): # TODO deactivate_item def test_entity(self): + # interact entity should not swing arm entity = DataDict(eid=234, x=2, y=2 + constants.PLAYER_EYE_HEIGHT, z=4) - self.plug.use_entity(entity) self.assertAlmostEqual(ClientInfoMock.position.yaw, -45) self.assertAlmostEqual(ClientInfoMock.position.pitch, 0) - self.assertEqual(NetMock.datas[-2].action, constants.INTERACT_ENTITY) - self.assertEqual(NetMock.datas[-2].target, 234) + self.assertEqual(NetMock.datas[-1].action, constants.INTERACT_ENTITY) + self.assertEqual(NetMock.datas[-1].target, 234) + self.assertEqual(len(NetMock.datas), 1) + + # attack entity should swing arm + entity = DataDict(eid=235, x=2, y=2 + constants.PLAYER_EYE_HEIGHT, z=4) + self.plug.use_entity(entity, action=constants.ATTACK_ENTITY) + self.assertAlmostEqual(ClientInfoMock.position.yaw, -45) + self.assertAlmostEqual(ClientInfoMock.position.pitch, 0) + self.assertEqual(NetMock.datas[-2].action, constants.ATTACK_ENTITY) + self.assertEqual(NetMock.datas[-2].target, 235) self.assertEqual(NetMock.idents[-1], 'PLAY>Animation') self.plug.auto_look = False From 2bce26e9dbe27705d28ce4df8dfb4fcf9b03af96 Mon Sep 17 00:00:00 2001 From: Gjum Date: Sun, 10 Jan 2016 00:40:46 +0100 Subject: [PATCH 17/18] Fix pep8 and test --- spockbot/plugins/helpers/interact.py | 1 + tests/plugins/helpers/test_interact.py | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/spockbot/plugins/helpers/interact.py b/spockbot/plugins/helpers/interact.py index 5150b97..4bdb466 100644 --- a/spockbot/plugins/helpers/interact.py +++ b/spockbot/plugins/helpers/interact.py @@ -12,6 +12,7 @@ This can be disabled by setting the ``auto_swing`` and ``auto_look`` flags. """ import math + from spockbot.mcdata import constants from spockbot.mcp import nbt from spockbot.mcp.proto import MC_SLOT diff --git a/tests/plugins/helpers/test_interact.py b/tests/plugins/helpers/test_interact.py index 36249bd..24547e6 100644 --- a/tests/plugins/helpers/test_interact.py +++ b/tests/plugins/helpers/test_interact.py @@ -122,7 +122,6 @@ def test_entity(self): self.assertAlmostEqual(ClientInfoMock.position.pitch, 0) self.assertEqual(NetMock.datas[-1].action, constants.INTERACT_ENTITY) self.assertEqual(NetMock.datas[-1].target, 234) - self.assertEqual(len(NetMock.datas), 1) # attack entity should swing arm entity = DataDict(eid=235, x=2, y=2 + constants.PLAYER_EYE_HEIGHT, z=4) From 351ded81320f864416c2d05b0ec11696756e04fd Mon Sep 17 00:00:00 2001 From: Gjum Date: Sun, 10 Jan 2016 03:31:29 +0100 Subject: [PATCH 18/18] Revert "Physics only sends pitch/yaw if they changed" This reverts commit 447d2961dfa179fe57416598f78a409f901d7a01. This is a pointless comparison, server doesn't complain when we send Position and Look and we'll spend a longer time calculating this than we gain in the marginal improvement to serverbound traffic (which is already minimal) - nickelpro --- spockbot/plugins/helpers/physics.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/spockbot/plugins/helpers/physics.py b/spockbot/plugins/helpers/physics.py index e63bd8d..f1465d9 100644 --- a/spockbot/plugins/helpers/physics.py +++ b/spockbot/plugins/helpers/physics.py @@ -82,7 +82,6 @@ def __init__(self, ploader, settings): self.world, BoundingBox(const.PLAYER_WIDTH, const.PLAYER_HEIGHT) ) self.pos = self.clientinfo.position - self.prev_dict = None self.skip_tick = False self.pc = PhysicsCore(self.pos, self.vec, self.clientinfo.abilities) ploader.provides('Physics', self.pc) @@ -99,13 +98,8 @@ def resume_physics(self, _=None, __=None): self.event.reg_event_handler('physics_tick', self.physics_tick) def client_tick(self, name, data): - current_dict = self.clientinfo.position.get_dict() - if self.prev_dict and self.prev_dict['yaw'] == current_dict['yaw'] \ - and self.prev_dict['pitch'] == current_dict['pitch']: - self.net.push_packet('PLAY>Player Position', current_dict) - else: - self.net.push_packet('PLAY>Player Position and Look', current_dict) - self.prev_dict = current_dict + self.net.push_packet('PLAY>Player Position and Look', + self.clientinfo.position.get_dict()) def physics_tick(self, _, __): if self.skip_tick: