class Client(object): def __init__(self, protocol_version=47): self.protocol_version = protocol_version self.proto = fastmc.proto.protocol(protocol_version) self._send_queue = Queue.Queue(10) self._recv_condition = threading.Condition() self.world = World() self.bot = Robot(self.proto, self._send_queue, self._recv_condition, self.world) self._sock = None self._mc_sock = None self._sock_generation = 0 self.writer = None self.reader = None self.in_buf = fastmc.proto.ReadBuffer() self.parent_pid = os.getppid() self.last_keepalive = time.time() self._action_num_counter = ThreadSafeCounter(0) self._threads = {} self._thread_funcs = { 'reader': self._do_read_thread, 'writer': self._do_send_thread, 'position_update': self._do_send_position, } self._active_threads = set(self._thread_funcs.keys()) self._post_send_hooks = { (fastmc.proto.HANDSHAKE, self.proto.HandshakeServerboundHandshake.id): self.set_to_login_state, (fastmc.proto.LOGIN, self.proto.LoginServerboundEncryptionResponse.id): self.set_sock_cipher, (fastmc.proto.PLAY, self.proto.PlayServerboundHeldItemChange.id): self.set_held_item, } self.interesting = [ #self.proto.PlayClientboundWindowProperty.id, #self.proto.PlayClientboundChunkData.id, #self.proto.PlayClientboundSpawnObject.id, #self.proto.PlayClientboundEntityMetadata.id, #self.proto.PlayClientboundEntityEquipment.id, #self.proto.PlayClientboundPlayerPositionAndLook.id, #self.proto.PlayClientboundChatMesage.id, #self.proto.PlayClientboundPlayerListItem.id, #self.proto.PlayClientboundOpenWindow.id, #self.proto.PlayClientboundCloseWindow.id, #self.proto.PlayClientboundWindowItem.id, #self.proto.PlayClientboundSetSlot.id, #self.proto.PlayClientboundConfirmTransaction.id, #self.proto.PlayClientboundUpdateBlockEntity.id, ] self._handlers = { (fastmc.proto.LOGIN, self.proto.LoginClientboundEncryptionRequest.id): self.on_login_encryption_request, (fastmc.proto.LOGIN, self.proto.LoginClientboundLoginSuccess.id): self.on_login_login_success, (fastmc.proto.LOGIN, self.proto.LoginClientboundSetCompression.id): self.on_login_set_compression, (fastmc.proto.PLAY, self.proto.PlayClientboundKeepAlive.id): self.on_keepalive, (fastmc.proto.PLAY, self.proto.PlayClientboundSetCompression.id): self.on_set_compression, (fastmc.proto.PLAY, self.proto.PlayClientboundChatMesage.id): self.on_chat_message, (fastmc.proto.PLAY, self.proto.PlayClientboundHealthUpdate.id): self.on_health_update, (fastmc.proto.PLAY, self.proto.PlayClientboundSpawnObject.id): self.on_spawn_object, (fastmc.proto.PLAY, self.proto.PlayClientboundSpawnMob.id): self.on_spawn_mob, (fastmc.proto.PLAY, self.proto.PlayClientboundEntityVelocity.id): self.on_entity_velocity, (fastmc.proto.PLAY, self.proto.PlayClientboundEntityRelativeMove.id): self.on_entity_relative_move, (fastmc.proto.PLAY, self.proto.PlayClientboundEntityLook.id): self.on_entity_look, (fastmc.proto.PLAY, self.proto.PlayClientboundEntityLookAndRelativeMove.id): self.on_entity_look_and_relative_move, (fastmc.proto.PLAY, self.proto.PlayClientboundEntityTeleport.id): self.on_entity_teleport, (fastmc.proto.PLAY, self.proto.PlayClientboundEntityMetadata.id): self.on_entity_metadata, (fastmc.proto.PLAY, self.proto.PlayClientboundDestroyEntities.id): self.on_destroy_entities, (fastmc.proto.PLAY, self.proto.PlayClientboundSetExperience.id): self.on_set_experience, (fastmc.proto.PLAY, self.proto.PlayClientboundChunkData.id): self.on_chunk_data, (fastmc.proto.PLAY, self.proto.PlayClientboundMultiBlockChange.id): self.on_multi_block_change, (fastmc.proto.PLAY, self.proto.PlayClientboundBlockChange.id): self.on_block_change, (fastmc.proto.PLAY, self.proto.PlayClientboundMapChunkBulk.id): self.on_map_chunk_bulk, (fastmc.proto.PLAY, self.proto.PlayClientboundPlayerPositionAndLook.id): self.on_player_position_and_look, (fastmc.proto.PLAY, self.proto.PlayClientboundSpawnPlayer.id): self.on_spawn_player, (fastmc.proto.PLAY, self.proto.PlayClientboundHeldItemChange.id): self.on_held_item_change, (fastmc.proto.PLAY, self.proto.PlayClientboundOpenWindow.id): self.on_open_window, (fastmc.proto.PLAY, self.proto.PlayClientboundCloseWindow.id): self.on_close_window, (fastmc.proto.PLAY, self.proto.PlayClientboundSetSlot.id): self.on_set_slot, (fastmc.proto.PLAY, self.proto.PlayClientboundWindowItem.id): self.on_window_item, (fastmc.proto.PLAY, self.proto.PlayClientboundWindowProperty.id): self.on_window_property, (fastmc.proto.PLAY, self.proto.PlayClientboundConfirmTransaction.id): self.on_confirm_transaction, (fastmc.proto.PLAY, self.proto.PlayClientboundUpdateBlockEntity.id): self.on_update_block_entity, (fastmc.proto.PLAY, self.proto.PlayClientboundPlayerListItem.id): self.on_player_list_item, } def set_to_login_state(self, **kwargs): self.reader.switch_state(fastmc.proto.LOGIN) self.writer.switch_state(fastmc.proto.LOGIN) def set_sock_cipher(self, **kwargs): self._mc_sock.set_cipher( fastmc.auth.generated_cipher(self._shared_secret), fastmc.auth.generated_cipher(self._shared_secret), ) def set_held_item(self, slot): self.bot._held_slot_num = slot def connect(self, host, username, password, port=25565, auth=True): if auth: self._session = fastmc.auth.Session.from_credentials( username, password) self.username = self._session.player_ign else: self._session = None self.username = username #self._sock = gevent.socket.create_connection((host, port)) self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self._sock.connect((host, port)) self._mc_sock = fastmc.proto.MinecraftSocket(self._sock) self.reader, self.writer = fastmc.proto.Endpoint.client_pair( self.protocol_version) self.start_threads() self.send_login_request(host, port) log.info('logging in') while self.reader.state != fastmc.proto.PLAY: time.sleep(.1) self.send(self.proto.PlayServerboundHeldItemChange.id, slot=0) def send_login_request(self, host, port=25565): self.send(self.proto.HandshakeServerboundHandshake.id, version=self.writer.protocol.version, addr=host, port=port, state=fastmc.proto.LOGIN, ) while self.writer.state != fastmc.proto.LOGIN: time.sleep(.5) self.send(self.proto.LoginServerboundLoginStart.id, name=self.username ) def respawn(self): self.send(self.proto.PlayServerboundClientStatus.id, action_id=0) def start_threads(self): for name, func in self._thread_funcs.iteritems(): thread = threading.Thread(target=func, name=name) thread.daemon = True thread.start() self._threads[name] = thread ############################################################################## # Thread functions def _do_read_thread(self): try: my_generation = self._sock_generation while my_generation == self._sock_generation: self.recv_packet() finally: os.kill(self.parent_pid, 0) def _do_send_thread(self): try: my_generation = self._sock_generation sock = self._mc_sock queue = self._send_queue writer = self.writer while my_generation == self._sock_generation and self._mc_sock is not None: packet_id, kwargs = queue.get() out_buf = fastmc.proto.WriteBuffer() writer.write(out_buf, packet_id, **kwargs) sock.send(out_buf) hook = self._post_send_hooks.get( (self.writer.state, packet_id)) if hook: hook(**kwargs) finally: os.kill(self.parent_pid, 0) def _do_send_position(self): try: while self.bot.x is None: time.sleep(0.01) my_generation = self._sock_generation while my_generation == self._sock_generation and self._mc_sock is not None: self.send(self.proto.PlayServerboundPlayerPositionAndLook.id, x=self.bot.x, y=self.bot.y, z=self.bot.z, yaw=self.bot.yaw, pitch=0, on_ground=self.bot.on_ground) time.sleep(0.01) finally: os.kill(self.parent_pid, 0) ############################################################################## def recv_packet(self): if self._mc_sock is None: log.debug('no mc sock') return sock = self._mc_sock reader = self.reader in_buf = self.in_buf data = sock.recv() if not data: return in_buf.append(data) while True: pkt, pkt_raw = reader.read(in_buf) if pkt is None: break if pkt.id in self.interesting: print pkt print log.debug('handler: (%s, %s)', reader.state, pkt.id) handler = self._handlers.get((reader.state, pkt.id)) if handler: handler(pkt) else: self.on_unhandled(pkt) with self._recv_condition: self._recv_condition.notifyAll() def wait_for(self, what, timeout=10): start = time.time() with self._recv_condition: while not what() and time.time() - start < timeout: self._recv_condition.wait(timeout=1) return what() def send(self, packet_id, **kwargs): self._send_queue.put((packet_id, kwargs)) def on_login_encryption_request(self, pkt): if pkt.public_key != '': rsa_key = fastmc.auth.decode_public_key(pkt.public_key) shared_secret = fastmc.auth.generate_shared_secret() self._shared_secret = shared_secret response_token = fastmc.auth.encrypt_with_public_key( pkt.challenge_token, rsa_key ) encrypted_shared_secret = fastmc.auth.encrypt_with_public_key( shared_secret, rsa_key ) server_hash = fastmc.auth.make_server_hash( pkt.server_id, shared_secret, rsa_key, ) fastmc.auth.join_server(self._session, server_hash) self.send(self.proto.LoginServerboundEncryptionResponse.id, shared_secret=encrypted_shared_secret, response_token=response_token, ) else: self.send(self.proto.LoginServerboundEncryptionResponse.id, shared_secret='', response_token=pkt.challenge_token, ) def on_login_login_success(self, pkt): self.reader.switch_state(fastmc.proto.PLAY) self.writer.switch_state(fastmc.proto.PLAY) def on_login_set_compression(self, pkt): log.debug('setting compression threshold: %d', pkt.threshold) self.reader.set_compression_threshold(pkt.threshold) self.writer.set_compression_threshold(pkt.threshold) def on_keepalive(self, pkt): self.send(self.proto.PlayServerboundKeepAlive.id, keepalive_id=pkt.keepalive_id ) self.last_keepalive = time.time() def on_set_compression(self, pkt): log.debug('setting reader compression threshold: %d', pkt.threshold) self.reader.set_compression_threshold(pkt.threshold) def on_chat_message(self, pkt): def parse_chat_json(json): if json.get('translate') == 'chat.type.text': message_list = [] sender = '' for section in json.get('with', []): if isinstance(section, basestring): message_list.append(section) elif isinstance(section, dict): sender = section.get('text') return '<{}> {}'.format(sender, ' '.join(message_list)) elif json.get('translate') in ['multiplayer.player.joined', 'multiplayer.player.left']: event = json.get( 'translate', '').replace('multiplayer.player.', 'player ') player = json.get('with', [{}])[0].get('text', 'UNKNOWN') return '{}: {}'.format(event, player) #log.info('chat: %s', str(pkt.chat)) clean_message = parse_chat_json(pkt.chat) if clean_message: log.info('chat: %s', clean_message) def on_health_update(self, pkt): self.bot.health = pkt.health self.bot.food = pkt.food self.bot._food_saturation = pkt.food_saturation def on_spawn_object(self, pkt): self.world.objects[pkt.eid] = Object( pkt.eid, pkt.type, pkt.x, pkt.y, pkt.z, pkt.pitch, pkt.yaw, pkt.data) def on_spawn_mob(self, pkt): self.world.entities[pkt.eid] = Entity( pkt.eid, pkt.type, pkt.x, pkt.y, pkt.z, pkt.pitch, pkt.head_pitch, pkt.yaw, pkt.velocity_x, pkt.velocity_y, pkt.velocity_z, pkt.metadata) def on_entity_velocity(self, pkt): if pkt.eid in self.world.entities: self.world.entities[pkt.eid].velocity_x = pkt.velocity_x self.world.entities[pkt.eid].velocity_y = pkt.velocity_y self.world.entities[pkt.eid].velocity_z = pkt.velocity_z def on_entity_relative_move(self, pkt): if pkt.eid in self.world.entities: self.world.entities[pkt.eid].move(pkt.dx, pkt.dy, pkt.dz) elif pkt.eid in self.world.objects: self.world.objects[pkt.eid].move(pkt.dx, pkt.dy, pkt.dz) elif pkt.eid in self.world.players: self.world.players[pkt.eid].move(pkt.dx, pkt.dy, pkt.dz) def on_entity_look(self, pkt): if pkt.eid in self.world.entities: self.world.entities[pkt.eid].look(pkt.yaw, pkt.pitch) elif pkt.eid in self.world.objects: self.world.objects[pkt.eid].look(pkt.yaw, pkt.pitch) elif pkt.eid in self.world.players: self.world.players[pkt.eid].look(pkt.yaw, pkt.pitch) def on_entity_look_and_relative_move(self, pkt): if pkt.eid in self.world.entities: self.world.entities[pkt.eid].move(pkt.dx, pkt.dy, pkt.dz) self.world.entities[pkt.eid].look(pkt.yaw, pkt.pitch) elif pkt.eid in self.world.objects: self.world.objects[pkt.eid].move(pkt.dx, pkt.dy, pkt.dz) self.world.objects[pkt.eid].look(pkt.yaw, pkt.pitch) elif pkt.eid in self.world.players: self.world.players[pkt.eid].move(pkt.dx, pkt.dy, pkt.dz) self.world.players[pkt.eid].look(pkt.yaw, pkt.pitch) def on_entity_teleport(self, pkt): if pkt.eid in self.world.entities: self.world.entities[pkt.eid].teleport(pkt.x, pkt.y, pkt.z, pkt.yaw, pkt.pitch) elif pkt.eid in self.world.objects: self.world.objects[pkt.eid].teleport(pkt.x, pkt.y, pkt.z, pkt.yaw, pkt.pitch) elif pkt.eid in self.world.players: self.world.players[pkt.eid].teleport(pkt.x, pkt.y, pkt.z, pkt.yaw, pkt.pitch) def on_entity_metadata(self, pkt): if pkt.eid in self.world.entities: self.world.entities[pkt.eid].metadata.update(pkt.metadata) elif pkt.eid in self.world.objects: self.world.objects[pkt.eid].metadata.update(pkt.metadata) elif pkt.eid in self.world.players: self.world.players[pkt.eid].metadata.update(pkt.metadata) def on_destroy_entities(self, pkt): for eid in pkt.eids: if eid in self.world.entities: del self.world.entities[eid] elif eid in self.world.objects: del self.world.objects[eid] elif eid in self.world.players: del self.world.players[eid] def on_set_experience(self, pkt): self.bot._xp_bar = pkt.bar self.bot._xp_total = pkt.total_exp self.bot.xp_level = pkt.level def on_chunk_data(self, pkt): self.world.unpack_chunk_from_fastmc( pkt.chunk_x, pkt.chunk_z, pkt.continuous, pkt.primary_bitmap, pkt.data ) def on_multi_block_change(self, pkt): for change in pkt.changes: self.world.put( change.x + pkt.chunk_x * 16, change.y, change.x + pkt.chunk_x * 16, 'block_data', change.block_id ) def on_block_change(self, pkt): self.world.put( pkt.location.x, pkt.location.y, pkt.location.z, 'block_data', pkt.block_id ) def on_map_chunk_bulk(self, pkt): self.world.unpack_from_fastmc(pkt.bulk) def on_held_item_change(self, pkt): self.bot._held_slot_num = pkt.slot def on_player_position_and_look(self, pkt): self.bot.move_corrected_by_server.set() self.bot.teleport(pkt.x, pkt.y, pkt.z, pkt.yaw, pkt.pitch) def on_spawn_player(self, pkt): self.world.players[pkt.eid] = PlayerEntity( pkt.eid, pkt.uuid, self.world.player_data.get(pkt.uuid, {}).get('name'), pkt.x, pkt.y, pkt.z, pkt.yaw, pkt.pitch, pkt.current_item, pkt.metadata, ) def on_open_window(self, pkt): self.bot._open_window_id = pkt.window_id if pkt.window_id not in self.bot.windows: self.bot.windows[pkt.window_id] = Window(pkt.window_id, self._action_num_counter, self._send_queue, self.proto, self._recv_condition, _type=pkt.type, title=pkt.title ) else: self.bot.windows[pkt.window_id]._type = pkt.type self.bot.windows[pkt.window_id].title = pkt.title def on_close_window(self, pkt): self.bot._open_window_id = 0 for _id in self.bot.windows.keys(): if _id != 0: del self.bot.windows[_id] def on_set_slot(self, pkt): if pkt.window_id == -1 and pkt.slot == -1: self.bot.windows[ self.bot.open_window._id].set_cursor_slot(pkt.slot) elif pkt.window_id in self.bot.windows: self.bot.windows[pkt.window_id].set_slot(pkt.slot, pkt.item) def on_window_item(self, pkt): if pkt.window_id not in self.bot.windows: self.bot.windows[pkt.window_id] = Window(pkt.window_id, self._action_num_counter, self._send_queue, self.proto, self._recv_condition, slots=pkt.slots, ) else: self.bot.windows[pkt.window_id].set_slots(pkt.slots) def on_window_property(self, pkt): if pkt.window_id not in self.bot.windows: self.bot.windows[pkt.window_id] = Window(pkt.window_id, self._action_num_counter, self._send_queue, self.proto, self._recv_condition ) else: self.bot.windows[pkt.window_id].set_property( pkt.property, pkt.value) def on_confirm_transaction(self, pkt): if not pkt.accepted: self.send(self.proto.PlayServerboundConfirmTransaction.id, window_id=pkt.window_id, action_num=pkt.action_num, accepted=pkt.accepted) if pkt.window_id in self.bot.windows: window = self.bot.windows[pkt.window_id] window._confirmations[pkt.action_num] = pkt.accepted def on_player_list_item(self, pkt): if pkt.list_actions.action == 0: for player_data in pkt.list_actions.players: self.world.player_data.update({ player_data.uuid: {'name': player_data.name} }) for eid in self.world.players: if self.world.players[eid].uuid == player_data.uuid: self.world.players[eid].name = player_data.name def on_update_block_entity(self, pkt): self.bot.world.block_entities[tuple(pkt.location)] = BlockEntity( tuple(pkt.location), pkt.nbt ) def on_unhandled(self, pkt): pass