from math import floor from minecraft.networking.packets import Packet, PacketBuffer from minecraft.networking.types import ( VarInt, Integer, UnsignedByte, Position, Vector, MutableRecord, attribute_alias, multi_attribute_alias, Long, Boolean, VarLong, Short, UnsignedLong, Byte, BlockFace, String, UUID, Angle, Double, Float, Direction, PositionAndLook ) from protocol.types import Nbt, Slot, Entry, Trade import blocks class ChunkDataPacket(Packet): id = 0x20 packet_name = 'chunk data' fields = 'x', 'bit_mask_y', 'z', 'full_chunk' def read(self, file_object): self.x = Integer.read(file_object) self.z = Integer.read(file_object) self.full_chunk = Boolean.read(file_object) self.bit_mask_y = VarInt.read(file_object) self.heightmaps = Nbt.read(file_object) self.biomes = [] if self.full_chunk: biomes_length = VarInt.read(file_object) for i in range(biomes_length): self.biomes.append(VarInt.read(file_object)) size = VarInt.read(file_object) self.data = file_object.read(size) size_entities = VarInt.read(file_object) self.entities = [] for i in range(size_entities): self.entities.append(Nbt.read(file_object)) self.decode_chunk_data() def write_fields(self, packet_buffer): Integer.send(self.x, packet_buffer) Integer.send(self.z, packet_buffer) Boolean.send(self.full_chunk, packet_buffer) VarInt.send(self.bit_mask_y, packet_buffer) Nbt.send(self.heightmaps, packet_buffer) if self.full_chunk: for i in range(1024): Integer.send(self.biomes[i], packet_buffer) VarInt.send(len(self.data), packet_buffer) packet_buffer.send(self.data) VarInt.send(len(self.entities), packet_buffer) for e in self.entities: Nbt.send(e, packet_buffer) def decode_chunk_data(self): packet_data = PacketBuffer() packet_data.send(self.data) packet_data.reset_cursor() self.chunks = {} for i in range(16): #0-15 self.chunks[i] = Chunk(self.x, i, self.z) if self.bit_mask_y & (1 << i): self.chunks[i].read(packet_data) for e in self.entities: y = e['y'] self.chunks[floor(y/16)].entities.append(e) class Chunk: position = multi_attribute_alias(Vector, 'x', 'y', 'z') def __init__(self, x, y, z, empty=True): self.x = x self.y = y self.z = z self.empty = empty self.entities = [] self.sub_index = {} def __repr__(self): return 'Chunk(%r, %r, %r)' % (self.x, self.y, self.z) def read(self, file_object): self.empty = False self.block_count = Short.read(file_object) self.bpb = UnsignedByte.read(file_object) if self.bpb <= 4: self.bpb = 4 if self.bpb <= 8: # Indirect palette self.palette = [] size = VarInt.read(file_object) for i in range(size): self.palette.append(VarInt.read(file_object)) else: # Direct palette self.palette = None size = VarInt.read(file_object) longs = [] for i in range(size): longs.append(UnsignedLong.read(file_object)) self.blocks = [] mask = (1 << self.bpb)-1 bits_used = int(64 / self.bpb) * self.bpb for i in range(4096): l1 = int((i*self.bpb)/bits_used) offset = (i*self.bpb)%bits_used n = longs[l1] >> offset n &= mask if self.palette: n = self.palette[n] self.blocks.append(n) if n in blocks.INDEXED_IDS: if n not in self.sub_index: self.sub_index[n] = [] self.sub_index[n].append(i) def write_fields(self, packet_buffer): pass # TODO def get_block_at(self, x, y, z): if self.empty: return 0 return self.blocks[x+y*256+z*16] def set_block_at(self, x, y, z, block): if self.empty: self.init_empty() self.blocks[x+y*256+z*16] = block def init_empty(self): self.blocks = [] for i in range(4096): self.blocks.append(0) self.empty = False @property def origin(self): return self.position*16 class AcknowledgePlayerDiggingPacket(Packet): id = 0x07 packet_name = 'acknowledge player digging' definition = [ {'location': Position}, {'block': VarInt}, {'status': VarInt}, {'successful': Boolean}, ] class BlockBreakAnimationPacket(Packet): id = 0x08 packet_name = 'block break animation' definition = [ {'entity_id': VarInt}, {'location': Position}, {'destroy_stage': Byte}, ] class SetSlotPacket(Packet): id = 0x15 packet_name = 'set slot' definition = [ {'window_id': Byte}, {'slot': Short}, {'slot_data': Slot}, ] class PlayerDiggingPacket(Packet): # used when player mines / breaks blocks # https://wiki.vg/Protocol#Player_Digging id = 0x1B packet_name = 'player digging' definition = [ {'status': VarInt}, {'location': Position}, {'face': VarInt}, ] STARTED = 0 CANCELLED = 1 FINISHED = 2 # PlayerBlockPlacementPacket.Face is an alias for BlockFace. Face = BlockFace class PickItemPacket(Packet): # used when player picks item (middle click) # https://wiki.vg/Protocol#Pick_Item id = 0x18 packet_name = 'pick item' definition = [ {'slot_to_use': VarInt}, ] class HeldItemChangePacket(Packet): # Sent when the player changes the slot selection # https://wiki.vg/Protocol#Held_Item_Change_.28serverbound.29 id = 0x25 packet_name = 'held item change' definition = [ {'slot': Short}, ] class OpenWindowPacket(Packet): # Sent to the client when it should open an inventory, such as a chest, workbench, or furnace # https://wiki.vg/Protocol#Open_Window id = 0x2D packet_name = 'open window' definition = [ {'window_id': VarInt}, {'window_type': VarInt}, {'window_title': String}, ] class CloseWindowPacket(Packet): # Sent by the client when closing a window # https://wiki.vg/Protocol#Close_Window_.28serverbound.29 id = 0x0A packet_name = 'close window' definition = [ {'window_id': UnsignedByte}, ] class ClickWindowPacket(Packet): # Sent by the player when it clicks on a slot in a window # https://wiki.vg/Protocol#Click_Window id = 0x09 packet_name = 'click window' definition = [ {'window_id': UnsignedByte}, {'slot': Short}, {'button': Byte}, {'action_number': Short}, {'mode': VarInt}, {'clicked_item': Slot}, ] class ClientWindowConfirmationPacket(Packet): # Sent by the server indicating whether a request from the client was accepted # https://wiki.vg/Protocol#Window_Confirmation_.28clientbound.29 id = 0x11 packet_name = 'client window confirmation' definition = [ {'window_id': Byte}, {'action_number': Short}, {'accepted': Boolean}, ] class ServerWindowConfirmationPacket(Packet): # Sent by the client to confirm an unaccepted click # https://wiki.vg/Protocol#Window_Confirmation_.28serverbound.29 id = 0x07 packet_name = 'client window confirmation' definition = [ {'window_id': Byte}, {'action_number': Short}, {'accepted': Boolean}, ] class EntityMetadataPacket(Packet): # Updates one or more metadata properties for an existing entity # https://wiki.vg/Protocol#Entity_Metadata id = 0x44 packet_name = 'entity metadata' fields = 'entity_id', 'metadata' class Entry: __slots__ = 'index', 'type', 'value' def read(self, file_object): self.entity_id = VarInt.read(file_object) self.metadata = [] for _ in range(99): entry = Entry.read(file_object, self.context) if not entry: break self.metadata.append(entry) class SpawnLivingEntityPacket(Packet): # Sent by the server when a living entity is spawned # https://wiki.vg/Protocol#Spawn_Entity id = 0x02 packet_name = 'spawn living entity' definition = [ {'entity_id': VarInt}, {'entity_uuid': UUID}, {'type': VarInt}, {'x': Double}, {'y': Double}, {'z': Double}, {'yaw': Angle}, {'pitch': Angle}, {'head_pitch': Angle}, {'velocity_x': Short}, {'velocity_y': Short}, {'velocity_z': Short}, ] class EntityTeleport(Packet): # Sent by the server when an entity moves more than 8 blocks # https://wiki.vg/Protocol#Entity_Teleport id = 0x56 packet_name = 'entity teleport' definition = [ {'entity_id': VarInt}, {'x': Double}, {'y': Double}, {'z': Double}, {'yaw': Angle}, {'pitch': Angle}, {'on_ground': Boolean}, ] class EntityPositionRotationPacket(Packet): # Sent by the server when an entity rotates and moves # https://wiki.vg/Protocol#Entity_Position_and_Rotation id = 0x28 packet_name = 'entity position and rotation' definition = [ {'entity_id': VarInt}, {'delta_x': Short}, {'delta_y': Short}, {'delta_z': Short}, {'yaw': Angle}, {'pitch': Angle}, {'on_ground': Boolean}, ] class DestroyEntitiesPacket(Packet): # Sent by the server when a list of entities is to be destroyed on the client # https://wiki.vg/Protocol#Destroy_Entities id = 0x36 packet_name = 'destroy entities' fields = 'count', 'entity_ids' def read(self, file_object): self.count = VarInt.read(file_object) self.entity_ids = [] for _ in range(self.count): eid = VarInt.read(file_object) self.entity_ids.append(eid) class EntityActionPacket(Packet): # Sent by the client when it performs an action # https://wiki.vg/Protocol#Entity_Action id = 0x1C packet_name = 'entity action' definition = [ {'entity_id': VarInt}, {'action_id': VarInt}, {'jump_boost': VarInt}, ] class InteractEntityPacket(Packet): # Sent when the client attacks or right-clicks another entity # https://wiki.vg/Protocol#Interact_Entity id = 0x0E packet_name = 'interact entity' definition = [ {'entity_id': VarInt}, {'type': VarInt}, #{'target_x': Float}, #{'target_y': Float}, #{'target_z': Float}, {'hand': VarInt}, {'sneaking': Boolean}, ] class TradeListPacket(Packet): # The list of trades a villager NPC is offering. # https://wiki.vg/Protocol#Trade_List id = 0x26 packet_name = 'trade list' fields = ( 'window_id', 'size', 'trades', 'villager_level', 'experience', 'is_regular_villager', 'can_restock', ) def read(self, file_object): self.window_id = VarInt.read(file_object) self.size = Byte.read(file_object) self.trades = [] for _ in range(self.size): trade = Trade.read(file_object) self.trades.append(trade) self.villager_level = VarInt.read(file_object) self.experience = VarInt.read(file_object) self.is_regular_villager = Boolean.read(file_object) self.can_restock = Boolean.read(file_object) class SelectTradePacket(Packet): # Sent when a player selects a specific trade offered by a villager # https://wiki.vg/Protocol#Select_Trade id = 0x23 packet_name = 'select trade entity' definition = [ {'selected_slot': VarInt}, ]