Compare commits

..

No commits in common. "8fabe5859a795dfb1f34b81b9791c058a738c34b" and "100b4da80defd63d8010363d82e057543b4c7a54" have entirely different histories.

19 changed files with 325 additions and 347 deletions

21
bot.py
View File

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

View File

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

36
custom/managers/chat.py Normal file
View File

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

97
custom/managers/chunks.py Normal file
View File

@ -0,0 +1,97 @@
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])

32
custom/managers/data.py Normal file
View File

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

View File

View File

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

@ -6,120 +6,7 @@ from minecraft.networking.types import (
multi_attribute_alias, Vector, UnsignedLong multi_attribute_alias, Vector, UnsignedLong
) )
from protocol.types import Nbt from ....types.nbt 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): class ChunkDataPacket(Packet):
id = 0x20 id = 0x20

View File

View File

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

View File

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

15
main.py
View File

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

View File

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

View File

@ -1,11 +1,8 @@
import re
import time import time
import importlib import importlib
from panda3d.core import LPoint3f from panda3d.core import LPoint3f
from minecraft.networking.packets import Packet, clientbound, serverbound
import utils import utils
importlib.reload(utils) importlib.reload(utils)
import path import path
@ -18,6 +15,8 @@ def handle_join_game(packet, g):
def handle_block_change(packet, g): def handle_block_change(packet, g):
l = g.local_state l = g.local_state
print('block change:')
print(packet)
if packet.block_state_id == 3887: if packet.block_state_id == 3887:
try: try:
@ -48,52 +47,3 @@ def handle_position_and_look(packet, g):
print(packet) print(packet)
p = LPoint3f(x=packet.x, y=packet.y, z=packet.z) p = LPoint3f(x=packet.x, y=packet.y, z=packet.z)
g.pos = p 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)

View File

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