Compare commits

...

3 Commits

Author SHA1 Message Date
8fabe5859a Simplify directory structure 2020-09-08 20:13:15 -06:00
e444cf6677 Improve exception handling 2020-09-08 19:52:50 -06:00
a76d02d53c Add support for chat commands 2020-09-08 17:51:27 -06:00
19 changed files with 347 additions and 325 deletions

21
bot.py
View File

@ -13,14 +13,14 @@ SERVER = os.environ['SERVER']
import monkey_patch # must be before any possible pyCraft imports
from custom.managers import DataManager, ChunksManager, ChatManager
from minecraft import authentication
from minecraft.exceptions import YggdrasilError
from minecraft.networking.connection import Connection
from minecraft.networking.packets import Packet, clientbound, serverbound
from custom.networking.packets.clientbound.play.block_change_packet import BlockChangePacket
from protocol.managers import DataManager, ChunksManager, ChatManager, ChunkNotLoadedException
from protocol.packets import BlockChangePacket
from bunch import Bunch
from panda3d.core import LPoint3f, LVector3f
@ -53,7 +53,7 @@ def tick(global_state):
try:
g.chunks.get_block_at(*utils.pint(p))
except chunks.ChunkNotLoadedException:
except ChunkNotLoadedException:
return
#l.jobstate.run()
@ -161,7 +161,7 @@ def bot(global_state):
g = global_state
g.local_state = Bunch()
if 'mcdata' not in g:
if not g.mcdata:
g.mcdata = DataManager('./mcdata')
if not g.connection:
@ -175,19 +175,16 @@ def bot(global_state):
g.connection = Connection(SERVER, 25565, auth_token=auth_token)
g.chunks = ChunksManager(g.mcdata)
g.chunks.register(g.connection)
g.chat = ChatManager()
g.chat.register(g.connection)
g.connection.connect()
def packet_wrapper(handler):
def wrapper(packet):
print('Wrapper:', handler)
handler(packet, g)
return wrapper
g.chunks.register(g.connection)
h1 = packet_wrapper(packet_handlers.handle_join_game)
g.connection.register_packet_listener(h1, clientbound.play.JoinGamePacket)
@ -197,6 +194,10 @@ def bot(global_state):
h3 = packet_wrapper(packet_handlers.handle_block_change)
g.connection.register_packet_listener(h3, BlockChangePacket)
g.chat = ChatManager(g)
h4 = packet_wrapper(packet_handlers.handle_chat)
g.chat.set_handler(h4)
try:
while not g.pos:
time.sleep(TICK)
@ -207,9 +208,7 @@ def bot(global_state):
time.sleep(TICK)
print('Chunks loaded.')
print('init..')
init(g)
print('done init')
while g.running:
tick(g)

View File

@ -1,3 +0,0 @@
from .data import DataManager
from .chunks import ChunksManager
from .chat import ChatManager

View File

@ -1,36 +0,0 @@
import json
from minecraft.networking.packets import clientbound, serverbound
class ChatManager:
def __init__(self):
return
def translate_chat(self, data):
if isinstance(data, str):
return data
elif 'extra' in data:
return "".join([self.translate_chat(x) for x in data['extra']])
elif 'text' in data:
return data['text']
else:
return "?"
def print_chat(self, chat_packet):
# TODO: Replace with handler
try:
print("[%s] %s"%(chat_packet.field_string('position'), self.translate_chat(json.loads(chat_packet.json_data))))
except Exception as ex:
print("Exception %r on message (%s): %s" % (ex, chat_packet.field_string('position'), chat_packet.json_data))
def register(self, connection):
connection.register_packet_listener(self.print_chat, clientbound.play.ChatMessagePacket)
def send(self, connection, text):
if not text:
# Prevents connection bug when sending empty chat message
return
packet = serverbound.play.ChatPacket()
packet.message = text
connection.write_packet(packet)

View File

@ -1,97 +0,0 @@
from math import floor
from ..networking.packets.clientbound.play import block_change_packet, chunk_data
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):
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, block_change_packet.BlockChangePacket)
connection.register_packet_listener(self.handle_multiblock, block_change_packet.MultiBlockChangePacket)
connection.register_packet_listener(self.handle_chunk, chunk_data.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])

View File

@ -1,32 +0,0 @@
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

View File

@ -1,112 +0,0 @@
from minecraft.networking.packets import Packet
from minecraft.networking.types import (
VarInt, Integer, UnsignedByte, Position, Vector, MutableRecord,
attribute_alias, multi_attribute_alias, Long, Boolean, VarLong,
)
class BlockChangePacket(Packet):
id = 0x0B
packet_name = 'block change'
definition = [
{'location': Position},
{'block_state_id': VarInt}]
block_state_id = 0
# For protocols < 347: an accessor for (block_state_id >> 4).
@property
def blockId(self):
return self.block_state_id >> 4
@blockId.setter
def blockId(self, block_id):
self.block_state_id = (self.block_state_id & 0xF) | (block_id << 4)
# For protocols < 347: an accessor for (block_state_id & 0xF).
@property
def blockMeta(self):
return self.block_state_id & 0xF
@blockMeta.setter
def blockMeta(self, meta):
self.block_state_id = (self.block_state_id & ~0xF) | (meta & 0xF)
# This alias is retained for backward compatibility.
blockStateId = attribute_alias('block_state_id')
class MultiBlockChangePacket(Packet):
id = 0x3B
packet_name = 'multi block change'
fields = 'chunk_x', 'chunk_z', 'records'
# Access the 'chunk_x' and 'chunk_z' fields as a tuple.
chunk_pos = multi_attribute_alias(tuple, 'chunk_x', 'chunk_z')
class Record(MutableRecord):
__slots__ = 'x', 'y', 'z', 'block_state_id', 'location'
def __init__(self, **kwds):
self.block_state_id = 0
super(MultiBlockChangePacket.Record, self).__init__(**kwds)
# Access the 'x', 'y', 'z' fields as a Vector of ints.
position = multi_attribute_alias(Vector, 'x', 'y', 'z')
# For protocols < 347: an accessor for (block_state_id >> 4).
@property
def blockId(self):
return self.block_state_id >> 4
@blockId.setter
def blockId(self, block_id):
self.block_state_id = self.block_state_id & 0xF | block_id << 4
# For protocols < 347: an accessor for (block_state_id & 0xF).
@property
def blockMeta(self):
return self.block_state_id & 0xF
@blockMeta.setter
def blockMeta(self, meta):
self.block_state_id = self.block_state_id & ~0xF | meta & 0xF
# This alias is retained for backward compatibility.
blockStateId = attribute_alias('block_state_id')
def read(self, file_object, parent):
data = VarLong.read(file_object)
self.block_state_id = int(data >> 12)
self.x = int(data >> 8 & 0xf)
self.z = int(data >> 4 & 0xf)
self.y = int(data & 0xf)
# Absolute position in world to be compatible with BlockChangePacket
self.location = Vector(self.position.x + parent.chunk_x*16, self.position.y, self.position.z + parent.chunk_z*16)
def write(self, packet_buffer):
raise
UnsignedByte.send(self.x << 4 | self.z & 0xF, packet_buffer)
UnsignedByte.send(self.y, packet_buffer)
VarInt.send(self.block_state_id, packet_buffer)
def read(self, file_object):
coords = Long.read(file_object)
self.chunk_x = int(coords >> 42 & 0x3fffff)
self.chunk_z = int(coords >> 20 & 0x3fffff)
self.chunk_y = int(coords & 0xfffff)
self.unknown = Boolean.read(file_object)
array_size = VarInt.read(file_object)
self.records = []
for i in range(array_size):
record = self.Record()
record.read(file_object, self)
self.records.append(record)
def write_fields(self, packet_buffer):
raise
Integer.send(self.chunk_x, packet_buffer)
Integer.send(self.chunk_z, packet_buffer)
VarInt.send(len(self.records), packet_buffer)
for record in self.records:
record.write(packet_buffer)

View File

@ -1,12 +0,0 @@
from minecraft.networking.types.basic import Type, Byte, Short, Integer, Long, Float, Double, ShortPrefixedByteArray
class IntegerPrefixedByteArray(Type):
@staticmethod
def read(file_object):
length = Integer.read(file_object)
return struct.unpack(str(length) + "s", file_object.read(length))[0]
@staticmethod
def send(value, socket):
Integer.send(len(value), socket)
socket.send(value)

15
main.py
View File

@ -1,6 +1,7 @@
import importlib
import threading
import time
import traceback
from flask import Flask
app = Flask(__name__)
@ -15,6 +16,7 @@ global_state = Bunch()
g = global_state
g.local_state = False
g.connection = False
g.mcdata = False
g.pos = False
@app.route('/')
@ -42,9 +44,16 @@ def main():
try:
while True:
g.running = True
bot.bot(global_state)
importlib.reload(bot)
try:
g.running = True
bot.bot(global_state)
importlib.reload(bot)
except BaseException as e:
g.running = True
traceback.print_exc()
print('Locking...')
while g.running:
time.sleep(1)
except KeyboardInterrupt:
observer.stop()
observer.join()

View File

@ -1,17 +1,17 @@
import minecraft.networking.packets
from custom.networking.packets.clientbound.play import chunk_data, block_change_packet
from protocol import packets
def get_packets(old_get_packets):
def wrapper(func, context):
print('Monkey-patch worked.')
packets = func(context)
mc_packets = func(context)
# add any custom packets here
packets.add(chunk_data.ChunkDataPacket)
packets.add(block_change_packet.BlockChangePacket)
packets.add(block_change_packet.MultiBlockChangePacket)
mc_packets.add(packets.ChunkDataPacket)
mc_packets.add(packets.BlockChangePacket)
mc_packets.add(packets.MultiBlockChangePacket)
return packets
return mc_packets
return lambda x: wrapper(old_get_packets, x)
minecraft.networking.packets.clientbound.play.get_packets = get_packets(minecraft.networking.packets.clientbound.play.get_packets)

View File

@ -1,8 +1,11 @@
import re
import time
import importlib
from panda3d.core import LPoint3f
from minecraft.networking.packets import Packet, clientbound, serverbound
import utils
importlib.reload(utils)
import path
@ -15,8 +18,6 @@ def handle_join_game(packet, g):
def handle_block_change(packet, g):
l = g.local_state
print('block change:')
print(packet)
if packet.block_state_id == 3887:
try:
@ -47,3 +48,52 @@ def handle_position_and_look(packet, g):
print(packet)
p = LPoint3f(x=packet.x, y=packet.y, z=packet.z)
g.pos = p
def handle_chat(message, g):
source, text = message
reply = None
match = re.match(r'<(\w+)> (.*)', text)
if match:
sender, text = match.groups()
else:
return
if text.startswith('! '):
text = text[2:]
elif text.startswith('!'):
text = text[1:]
else:
return
if ' ' in text:
command = text.split(' ', 1)[0]
data = text.split(' ', 1)[1]
else:
command = text
if command == 'ping':
reply = 'pong'
if command == 'echo' and data:
reply = data
if command == 'respawn':
packet = serverbound.play.ClientStatusPacket()
packet.action_id = serverbound.play.ClientStatusPacket.RESPAWN
g.connection.write_packet(packet)
reply = 'ok'
if command == 'pos':
reply = str(utils.pint(g.pos))[1:-1]
if command == 'afk':
reply = '/afk'
if command == 'error':
reply = 'ok'
raise
if reply:
print(reply)
g.chat.send(reply)

140
protocol/managers.py Normal file
View File

@ -0,0 +1,140 @@
import os
from math import floor
import json
from minecraft.networking.packets import clientbound, serverbound
from protocol import packets
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
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):
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, packets.BlockChangePacket)
connection.register_packet_listener(self.handle_multiblock, packets.MultiBlockChangePacket)
connection.register_packet_listener(self.handle_chunk, packets.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)
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])
class ChatManager:
def __init__(self, global_state):
self.g = global_state
self.handler = None
self.g.connection.register_packet_listener(self.print_chat, clientbound.play.ChatMessagePacket)
def translate_chat(self, data):
if isinstance(data, str):
return data
elif 'extra' in data:
return ''.join([self.translate_chat(x) for x in data['extra']])
elif 'text' in data:
return data['text']
elif 'translate' in data:
return data['translate']
else:
print(data)
return '?'
def print_chat(self, chat_packet):
try:
source = chat_packet.field_string('position')
text = self.translate_chat(json.loads(chat_packet.json_data))
print('[%s] %s'%(source, text))
except Exception as ex:
print('Exception %r on message (%s): %s' % (ex, chat_packet.field_string('position'), chat_packet.json_data))
if self.handler:
self.handler((source, text))
def set_handler(self, func):
self.handler = func
def send(self, text):
if not text:
# Prevents connection bug when sending empty chat message
return
packet = serverbound.play.ChatPacket()
packet.message = text
self.g.connection.write_packet(packet)

View File

@ -6,7 +6,120 @@ from minecraft.networking.types import (
multi_attribute_alias, Vector, UnsignedLong
)
from ....types.nbt import Nbt
from protocol.types import Nbt
from minecraft.networking.packets import Packet
from minecraft.networking.types import (
VarInt, Integer, UnsignedByte, Position, Vector, MutableRecord,
attribute_alias, multi_attribute_alias, Long, Boolean, VarLong,
)
class BlockChangePacket(Packet):
id = 0x0B
packet_name = 'block change'
definition = [
{'location': Position},
{'block_state_id': VarInt}]
block_state_id = 0
# For protocols < 347: an accessor for (block_state_id >> 4).
@property
def blockId(self):
return self.block_state_id >> 4
@blockId.setter
def blockId(self, block_id):
self.block_state_id = (self.block_state_id & 0xF) | (block_id << 4)
# For protocols < 347: an accessor for (block_state_id & 0xF).
@property
def blockMeta(self):
return self.block_state_id & 0xF
@blockMeta.setter
def blockMeta(self, meta):
self.block_state_id = (self.block_state_id & ~0xF) | (meta & 0xF)
# This alias is retained for backward compatibility.
blockStateId = attribute_alias('block_state_id')
class MultiBlockChangePacket(Packet):
id = 0x3B
packet_name = 'multi block change'
fields = 'chunk_x', 'chunk_z', 'records'
# Access the 'chunk_x' and 'chunk_z' fields as a tuple.
chunk_pos = multi_attribute_alias(tuple, 'chunk_x', 'chunk_z')
class Record(MutableRecord):
__slots__ = 'x', 'y', 'z', 'block_state_id', 'location'
def __init__(self, **kwds):
self.block_state_id = 0
super(MultiBlockChangePacket.Record, self).__init__(**kwds)
# Access the 'x', 'y', 'z' fields as a Vector of ints.
position = multi_attribute_alias(Vector, 'x', 'y', 'z')
# For protocols < 347: an accessor for (block_state_id >> 4).
@property
def blockId(self):
return self.block_state_id >> 4
@blockId.setter
def blockId(self, block_id):
self.block_state_id = self.block_state_id & 0xF | block_id << 4
# For protocols < 347: an accessor for (block_state_id & 0xF).
@property
def blockMeta(self):
return self.block_state_id & 0xF
@blockMeta.setter
def blockMeta(self, meta):
self.block_state_id = self.block_state_id & ~0xF | meta & 0xF
# This alias is retained for backward compatibility.
blockStateId = attribute_alias('block_state_id')
def read(self, file_object, parent):
data = VarLong.read(file_object)
self.block_state_id = int(data >> 12)
self.x = int(data >> 8 & 0xf)
self.z = int(data >> 4 & 0xf)
self.y = int(data & 0xf)
# Absolute position in world to be compatible with BlockChangePacket
self.location = Vector(self.position.x + parent.chunk_x*16, self.position.y, self.position.z + parent.chunk_z*16)
def write(self, packet_buffer):
raise
UnsignedByte.send(self.x << 4 | self.z & 0xF, packet_buffer)
UnsignedByte.send(self.y, packet_buffer)
VarInt.send(self.block_state_id, packet_buffer)
def read(self, file_object):
coords = Long.read(file_object)
self.chunk_x = int(coords >> 42 & 0x3fffff)
self.chunk_z = int(coords >> 20 & 0x3fffff)
self.chunk_y = int(coords & 0xfffff)
self.unknown = Boolean.read(file_object)
array_size = VarInt.read(file_object)
self.records = []
for i in range(array_size):
record = self.Record()
record.read(file_object, self)
self.records.append(record)
def write_fields(self, packet_buffer):
raise
Integer.send(self.chunk_x, packet_buffer)
Integer.send(self.chunk_z, packet_buffer)
VarInt.send(len(self.records), packet_buffer)
for record in self.records:
record.write(packet_buffer)
class ChunkDataPacket(Packet):
id = 0x20

View File

@ -1,16 +1,23 @@
"""Contains definition for minecraft's NBT format.
"""
from __future__ import division
from minecraft.networking.types.basic import Type, Byte, Short, Integer, Long, Float, Double, ShortPrefixedByteArray
import struct
from minecraft.networking.types.utility import Vector
from minecraft.networking.types.basic import Type, Byte, Short, Integer, Long, Float, Double, ShortPrefixedByteArray
from .basic import IntegerPrefixedByteArray
__all__ = (
'Nbt',
)
class IntegerPrefixedByteArray(Type):
@staticmethod
def read(file_object):
length = Integer.read(file_object)
return struct.unpack(str(length) + "s", file_object.read(length))[0]
@staticmethod
def send(value, socket):
Integer.send(len(value), socket)
socket.send(value)
TAG_End = 0
TAG_Byte = 1
@ -26,9 +33,7 @@ TAG_Compound = 10
TAG_Int_Array = 11
TAG_Long_Array = 12
class Nbt(Type):
@staticmethod
def read(file_object):
type_id = Byte.read(file_object)
@ -91,5 +96,3 @@ class Nbt(Type):
def send(value, socket):
# TODO
pass