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

Interact plugin #85

Merged
merged 22 commits into from
Aug 19, 2015
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 15 additions & 10 deletions spock/plugins/__init__.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
from spock.plugins.core import auth, event, net, ticker, timer
from spock.plugins.helpers import clientinfo, entities, inventory, keepalive, \
move, physics, respawn, start, world
from spock.plugins.helpers import clientinfo, entities, interact, inventory,\
keepalive, move, physics, respawn, start, world

DefaultPlugins = [
core_plugins = [
('auth', auth.AuthPlugin),
('event', event.EventPlugin),
('net', net.NetPlugin),
('auth', auth.AuthPlugin),
('ticker', ticker.TickerPlugin),
('timers', timer.TimerPlugin),
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe this should be timer instead of timers? The rest are singular

Copy link
Member Author

Choose a reason for hiding this comment

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

Problem is that every plugin requires it with "Timers". This would have to be changed everywhere.

But I agree, keeping it consistent would be better.

Copy link
Member

Choose a reason for hiding this comment

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

Thats not how that value works, that value is just for the settings dict that gets passed into Client, so that can be changed without much consequence.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'd still like it to be the same as in Timer's pl_announce.

('start', start.StartPlugin),
('keepalive', keepalive.KeepalivePlugin),
('respawn', respawn.RespawnPlugin),
('move', move.MovementPlugin),
('world', world.WorldPlugin),
]
helper_plugins = [
('clientinfo', clientinfo.ClientInfoPlugin),
('entities', entities.EntityPlugin),
Copy link
Contributor

Choose a reason for hiding this comment

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

Same for this entity

('physics', physics.PhysicsPlugin),
('interact', interact.InteractPlugin),
('inventory', inventory.InventoryPlugin),
('keepalive', keepalive.KeepalivePlugin),
('move', move.MovementPlugin),
('physics', physics.PhysicsPlugin),
('respawn', respawn.RespawnPlugin),
('start', start.StartPlugin),
('world', world.WorldPlugin),
]
default_plugins = core_plugins + helper_plugins
DefaultPlugins = default_plugins # for compatibility
257 changes: 257 additions & 0 deletions spock/plugins/helpers/interact.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
"""
Interact with the world:
- swing the arm, sneak, sprint, jump with a horse, leave the bed
- chat, whisper (private message)
- look around
- dig/place/use blocks
- use the held (active) item
- use/attack entities
- steer vehicles

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.
"""
from spock.plugins.base import PluginBase
from spock.utils import pl_announce
from spock.vector import Vector3

PLAYER_HEIGHT = 1.74 # TODO get from physics or where else it belongs

INTERACT_ENTITY = 0
ATTACK_ENTITY = 1
INTERACT_ENTITY_AT = 2

ENTITY_ACTION_SNEAK = 0
ENTITY_ACTION_UNSNEAK = 1
ENTITY_ACTION_LEAVE_BED = 2
ENTITY_ACTION_START_SPRINT = 3
ENTITY_ACTION_STOP_SPRINT = 4
ENTITY_ACTION_JUMP_HORSE = 5
ENTITY_ACTION_OPEN_INVENTORY = 6

# the six faces of a block
FACE_Y_NEG = 0
FACE_Y_POS = 1
FACE_Z_NEG = 2
FACE_Z_POS = 3
FACE_X_NEG = 4
FACE_X_POS = 5

DIG_START = 0
DIG_CANCEL = 1
DIG_FINISH = 2
DIG_DROP_STACK = 3
DIG_DROP_ITEM = 4
DIG_DEACTIVATE_ITEM = 5


@pl_announce('Interact')
class InteractPlugin(PluginBase):
requires = ('ClientInfo', 'Inventory', 'Net')

def __init__(self, ploader, settings):
super(InteractPlugin, self).__init__(ploader, settings)
ploader.provides('Interact', self)

self.sneaking = False
self.sprinting = False
self.dig_pos_dict = {'x': 0, 'y': 0, 'z': 0}

self.auto_swing = True # move arm when clicking
self.auto_look = True # look at clicked things

def swing_arm(self):
self.net.push_packet('PLAY>Animation', {})

def _entity_action(self, action, jump_boost=100):
entity_id = self.clientinfo.eid
self.net.push_packet('PLAY>Entity Action', {
'eid': entity_id,
'action': action,
'jump_boost': jump_boost,
})

def leave_bed(self):
self._entity_action(ENTITY_ACTION_LEAVE_BED)

def sneak(self, sneak=True):
self._entity_action(ENTITY_ACTION_SNEAK
if sneak else ENTITY_ACTION_UNSNEAK)
self.sneaking = sneak

def unsneak(self):
self.sneak(False)

def sprint(self, sprint=True):
self._entity_action(ENTITY_ACTION_START_SPRINT
if sprint else ENTITY_ACTION_STOP_SPRINT)
self.sprinting = sprint

def unsprint(self):
self.sprint(False)

def jump_horse(self, jump_boost=100):
self._entity_action(ENTITY_ACTION_JUMP_HORSE, jump_boost)

def chat(self, message):
self.net.push_packet('PLAY>Chat Message', {'message': message})

def whisper(self, player, message):
self.chat('/tell %s %s' % (player, message))

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
self.net.push_packet('PLAY>Player Look', {
'yaw': int(yaw),
'pitch': int(pitch),
'on_ground': self.clientinfo.position.on_ground
})

def look_rel(self, d_yaw=0.0, d_pitch=0.0):
self.look(self.clientinfo.position.yaw + d_yaw,
self.clientinfo.position.pitch + d_pitch)

def look_at_rel(self, delta):
self.look(*delta.yaw_pitch)

def look_at(self, pos):
delta = pos - self.clientinfo.position - Vector3(0, PLAYER_HEIGHT, 0)
self.look_at_rel(delta)

def _send_dig_block(self, status, pos=None, face=FACE_Y_POS):
if status == DIG_START:
self.dig_pos_dict = pos.get_dict().copy()
self.net.push_packet('PLAY>Player Digging', {
'status': status,
'location': self.dig_pos_dict,
'face': face,
})

def start_digging(self, pos):
if self.auto_look:
self.look_at(pos) # TODO look at block center
self._send_dig_block(DIG_START, pos)
if self.auto_swing:
self.swing_arm()
# TODO send swing animation until done or stopped

def cancel_digging(self):
self._send_dig_block(DIG_CANCEL)

def finish_digging(self):
self._send_dig_block(DIG_FINISH)

def dig_block(self, pos):
"""
Not cancelable.
"""
self.start_digging(pos)
self.finish_digging()

def _send_click_block(self, pos, face=1, cursor_pos=Vector3(8, 8, 8)):
self.net.push_packet('PLAY>Player Block Placement', {
'location': pos.get_dict(),
'direction': face,
'held_item': self.inventory.active_slot.get_dict(),
'cur_pos_x': int(cursor_pos.x),
'cur_pos_y': int(cursor_pos.y),
'cur_pos_z': int(cursor_pos.z),
})

def click_block(self, pos, face=1, cursor_pos=Vector3(8, 8, 8),
look_at_block=True, swing=True):
"""
Click on a block.
Examples: push button, open window, make redstone ore glow
:param face: side of the block on which the block is placed on
:param cursor_pos: where to click inside the block, each dimension 0-15
"""
if look_at_block and self.auto_look:
# TODO look at cursor_pos
self.look_at(pos)
self._send_click_block(pos, face, cursor_pos)
if swing and self.auto_swing:
self.swing_arm()

def place_block(self, pos, face=1, cursor_pos=Vector3(8, 8, 8),
sneak=True, look_at_block=True, swing=True):
"""
Place a block next to pos. If the block at pos is air, place at pos.
"""
sneaking_before = self.sneaking
if sneak:
self.sneak()
self.click_block(pos, face, cursor_pos, look_at_block, swing)
if sneak:
self.sneak(sneaking_before)

def use_bucket(self, pos): # TODO
"""
Using buckets is different from placing blocks.
See "Special note on using buckets"
in http://wiki.vg/Protocol#Player_Block_Placement
"""
raise NotImplementedError(self.use_bucket.__doc__)

def activate_item(self):
"""
Use (hold right-click) the item in the active slot.
Examples: pull the bow, start eating once, throw an egg.
"""
self._send_click_block(pos=Vector3(-1, 255, -1),
face=-1,
cursor_pos=Vector3(-1, -1, -1))

def deactivate_item(self):
"""
Stop using (release right-click) the item in the active slot.
Examples: shoot the bow, stop eating.
"""
self._send_dig_block(DIG_DEACTIVATE_ITEM)

def use_entity(self, entity, cursor_pos=None, action=INTERACT_ENTITY):
"""
Uses (right-click) an entity to open its window.
Setting `cursor_pos` sets `action` to "interact at".
"""
if self.auto_look:
self.look_at(Vector3(entity)) # TODO look at cursor_pos
if cursor_pos is not None:
action = INTERACT_ENTITY_AT
packet = {'target': entity.eid, 'action': action}
if action == 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:
self.swing_arm()

def attack_entity(self, entity):
self.use_entity(entity, action=ATTACK_ENTITY)

def mount_vehicle(self, entity):
self.use_entity(entity)

def steer_vehicle(self, sideways=0.0, forward=0.0,
jump=False, unmount=False):
flags = 0
if jump:
flags += 1
if unmount:
flags += 2
self.net.push_packet('PLAY>Steer Vehicle', {
'sideways': sideways,
'forward': forward,
'flags': flags,
})

def unmount_vehicle(self):
self.steer_vehicle(unmount=True)

def jump_vehicle(self):
self.steer_vehicle(jump=True)