Skip to content

Nbt #15

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

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open

Nbt #15

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions minecraft/managers/chunks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
from math import floor

from ..networking.packets import clientbound

class ChunksManager:

def __init__(self, data_manager):
self.data = data_manager
self.chunks = {}
self.biomes = {}

def handle_block(self, block_packet):
self.set_block_at(block_packet.location.x, block_packet.location.y, block_packet.location.z, block_packet.block_state_id)
#self.print_chunk(self.get_chunk(floor(block_packet.location.x/16), floor(block_packet.location.y/16), floor(block_packet.location.z/16)), block_packet.location.y%16)
#print('Block %s at %s'%(blocks_states[block_packet.block_state_id], block_packet.location))

def handle_multiblock(self, multiblock_packet):
for b in multiblock_packet.records:
self.handle_block(b)

def handle_chunk(self, chunk_packet):
print(f"Chunk: {chunk_packet}")
for i in chunk_packet.chunks:
self.chunks[(chunk_packet.x, i, chunk_packet.z)] = chunk_packet.chunks[i]
self.biomes[(chunk_packet.x, None, chunk_packet.z)] = chunk_packet.biomes # FIXME

def register(self, connection):
# connection.register_packet_listener(self.handle_block, clientbound.play.BlockChangePacket)
# connection.register_packet_listener(self.handle_multiblock, clientbound.play.MultiBlockChangePacket)
connection.register_packet_listener(self.handle_chunk, clientbound.play.ChunkDataPacket)

def get_chunk(self, x, y, z):
index = (x, y, z)
if not index in self.chunks:
raise ChunkNotLoadedException(index)
return self.chunks[index]

def get_loaded_area(self, ignore_empty=False):
first = next(iter(self.chunks.keys()))
x0 = x1 = first[0]
y0 = y1 = first[1]
z0 = z1 = first[2]
for k in self.chunks.keys():
if ignore_empty and self.chunks[k].empty:
continue
x0 = min(x0, k[0])
x1 = max(x1, k[0])
y0 = min(y0, k[1])
y1 = max(y1, k[1])
z0 = min(z0, k[2])
z1 = max(z1, k[2])
return ((x0,y0,z0),(x1,y1,z1))

def get_block_at(self, x, y, z):
c = self.get_chunk(floor(x/16), floor(y/16), floor(z/16))
return c.get_block_at(x%16, y%16, z%16)

def set_block_at(self, x, y, z, block):
c = self.get_chunk(floor(x/16), floor(y/16), floor(z/16))
c.set_block_at(x%16, y%16, z%16, block)

def print_chunk(self, chunk, y_slice):
print("This is chunk %d %d %d at slice %d:"%(chunk.x, chunk.y, chunk.z, y_slice))
print("+%s+"%("-"*16))
# for z in range(16):
# missing = []
# print("|", end="")
# for x in range(16):
# sid = chunk.get_block_at(x, y_slice, z)
# bloc = self.data.blocks_states[sid]
# if bloc == "minecraft:air" or bloc == "minecraft:cave_air":
# c = " "
# elif bloc == "minecraft:grass_block" or bloc == "minecraft:dirt":
# c = "-"
# elif bloc == "minecraft:water":
# c = "~"
# elif bloc == "minecraft:lava":
# c = "!"
# elif bloc == "minecraft:bedrock":
# c = "_"
# elif bloc == "minecraft:stone":
# c = "X"
# else:
# missing.append(bloc)
# c = "?"

# print(c, end="")
# print("| %s"%(",".join(missing)))
# print("+%s+"%("-"*16))
if chunk.entities:
print("Entities in slice: %s"%(", ".join([x['id'].decode() for x in chunk.entities])))


class ChunkNotLoadedException(Exception):
def __str__(self):
pos = self.args[0]
return "Chunk at %d %d %d not loaded (yet?)"%(pos[0], pos[1], pos[2])

36 changes: 36 additions & 0 deletions minecraft/managers/data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import os
import json


class DataManager:

def __init__(self, directory):
self.blocks = {}
self.blocks_states = {}
self.blocks_properties = {}
self.registries = {}
self.biomes = {}
self.entity_type = {}

if not os.path.isdir(directory):
raise FileNotFoundError("%s is not a valid directory")

if not os.path.isfile("%s/registries.json" % (directory)):
raise FileNotFoundError(
"%s is not a valid minecraft data directory")

with open("%s/blocks.json" % (directory)) as f:
blocks = json.loads(f.read())
for x in blocks:
for s in blocks[x]['states']:
self.blocks_states[s['id']] = x
self.blocks_properties[s['id']] = s.get('properties', {})

with open("%s/registries.json" % (directory)) as f:
registries = json.loads(f.read())
for x in registries["minecraft:biome"]["entries"]:
self.biomes[registries["minecraft:biome"]
["entries"][x]["protocol_id"]] = x
for x in registries["minecraft:entity_type"]["entries"]:
self.entity_type[registries["minecraft:entity_type"]
["entries"][x]["protocol_id"]] = x
4 changes: 3 additions & 1 deletion minecraft/networking/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ class ConnectionContext(object):

def __init__(self, **kwds):
self.protocol_version = kwds.get("protocol_version")

# self.protocol_version = 340

def protocol_earlier(self, other_pv):
"""Returns True if the protocol version of this context was published
earlier than 'other_pv', or else False."""
Expand Down Expand Up @@ -447,6 +448,7 @@ def connect(self):
# It is important that this is set correctly even when connecting
# in status mode, as some servers, e.g. SpigotMC with the
# ProtocolSupport plugin, use it to determine the correct response.
# self.context.protocol_version = 340
self.context.protocol_version \
= max(self.allowed_proto_versions,
key=PROTOCOL_VERSION_INDICES.get)
Expand Down
6 changes: 5 additions & 1 deletion minecraft/networking/packets/clientbound/play/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
from .join_game_and_respawn_packets import JoinGamePacket, RespawnPacket
from .destroy_entities_packet import DestroyEntitiesPacket
from .spawn_mob_packet import SpawnMobPacket
from .chunk_data import ChunkDataPacket

# from .unknown_packet import UnknownPacket

# Formerly known as state_playing_clientbound.

Expand Down Expand Up @@ -58,7 +61,8 @@ def get_packets(context):
BlockActionPacket,
EntityHeadLookPacket,
ResourcePackSendPacket,
NBTQueryPacket
NBTQueryPacket,
ChunkDataPacket
}

if context.protocol_earlier_eq(47):
Expand Down
137 changes: 137 additions & 0 deletions minecraft/networking/packets/clientbound/play/chunk_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
from math import floor

from minecraft.networking.packets import Packet, PacketBuffer
from minecraft.networking.types import (
VarInt, Integer, Boolean, UnsignedByte, Long, Short,
multi_attribute_alias, Vector, UnsignedLong, Nbt
)


class ChunkDataPacket(Packet):
@staticmethod
def get_id(context):
return 0x3C # FIXME

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:
for i in range(1024):
self.biomes.append(Integer.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 = []

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
for i in range(4096):
l1 = int((i*self.bpb)/64)
offset = (i*self.bpb) % 64
l2 = int(((i+1)*self.bpb-1)/64)
n = longs[l1] >> offset
if l2 > l1:
n |= longs[l2] << (64-offset)
n &= mask
if self.palette:
n = self.palette[n]
self.blocks.append(n)

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
66 changes: 66 additions & 0 deletions minecraft/networking/packets/clientbound/play/unknown_packet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from minecraft.networking.types import (
Vector, Float, Byte, Integer, PrefixedArray, multi_attribute_alias, Type,
VarInt,
)


from minecraft.networking.types import (
Type, Boolean, UnsignedByte, Byte, Short, UnsignedShort,
Integer, FixedPoint, FixedPointInteger, Angle, VarInt, VarLong,
Long, UnsignedLong, Float, Double, ShortPrefixedByteArray,
VarIntPrefixedByteArray, TrailingByteArray, String, UUID,
Position, NBT, PrefixedArray,
Type, VarInt, VarLong, UnsignedLong, Integer, UnsignedByte, Position,
Vector, MutableRecord, PrefixedArray, Boolean, attribute_alias, String
)

from minecraft.networking.packets import Packet


class UnknownPacket(Packet):
@staticmethod
def get_id(context):
# return 0x20
# return 0x16
# return 0x4E
return 0x3F

# 13 --> [unknown packet] 0x3C Packet
# 19 --> [unknown packet] 0x16 Packet
# 66 --> [unknown packet] 0x21 Packet
# 81 --> [unknown packet] 0x20 Packet
# 137 --> [unknown packet] 0x1E Packet
# 472 --> [unknown packet] 0x4C Packet
# 1029 --> [unknown packet] 0x27 Packet - No

packet_name = 'unknown'

class Record(Vector, Type):
__slots__ = ()

@classmethod
def read(cls, file_object):
return cls(*(Byte.read(file_object) for i in range(3)))

@classmethod
def send(cls, record, socket):
for coord in record:
Byte.send(coord, socket)

@staticmethod
def get_definition(context):
return [
{'entity_id': VarInt},
{'y': Angle},
{'z': Short}
# {'player_motion_x': VarInt},
# {'player_motion_y': Float},
# {'player_motion_z': Float},
]

# Access the 'x', 'y', 'z' fields as a Vector tuple.
position = multi_attribute_alias(Vector, 'x', 'y', 'z')

# Access the 'player_motion_{x,y,z}' fields as a Vector tuple.
player_motion = multi_attribute_alias(
Vector, 'player_motion_x', 'player_motion_y', 'player_motion_z')
Loading