pyCraft
pyCraft copied to clipboard
ChunckDataPacket Listener
I tried to create my ChuckDataPacket listener in 1.8, but the callback was never call, I don't understand why.
connection.register_packet_listener(recv_packet, ChunckData ,early=True)
class ChunckDataPacket(Packet):
@staticmethod
def get_id(context):
return 0x21
packet_name = "chunck data"
get_definition = staticmethod(lambda context: [
{'x': Integer},
{'z': Integer},
{'groud_up_continuous': Boolean},
{'bit_mask': UnsignedShort},
{'size': VarInt},
{'data': VarIntPrefixedByteArray},
])
What's the problem? Someone can help me? Thanks.
What is the ChunckData
in connection.register_packet_listener(recv_packet, ChunckData ,early=True)
? The name of the class defined in your other code is ChunckDataPacket
.
(It should, by the way, be "Chunk" and not "Chunck".)
@joodicator Doesn't the packet also need to be added into the clientbound_packets
for the reactor? Maybe we should add an easier way to do this?
@ammaraskar Ah, yes, you are right.
@Graphir67 A clientbound play state packet such as Chunk Data should be added to minecraft.networking.packets.clientbound.play.get_packets
in order to be processed.
@ammaraskar This would be good to change, I agree, because there have also been problems in the past with users trying to send a clientbound packet over the network to the server. If each packet class had metadata specifying its state and direction, then the sets of packets currently returned by get_packets
could be generated automatically from Packet.__subclasses__()
. Moreoever, Connection.write_packet
and Connection.register_packet_listener
could also check that the direction and state are valid.
The protocol could be as follows:
# minecraft/networking/types/enum.py
class State(Enum):
HANDSHAKE = 0
STATUS = 1
LOGIN = 2
PLAYING = 3
class Bound(Enum):
CLIENT = 0
SERVER = 1
# minecraft/networking/packets/clientbound/play/__init__.py
from minecraft.networking.types import State, Bound
class ClientboundPlayPacket(Packet):
packet_bound = Bound.CLIENT
packet_state = State.PLAY
class ChunkDataPacket(ClientboundPlayPacket):
@staticmethod
def get_id(context):
# A return value of None means that the packet is not implemented in this version.
return 0x21 if context.protocol_version == 47 else None
# ...
Then STATE_STATUS
and STATE_PLAYING
in minecraft/networking/connection.py
could be replaced with State.STATUS
and State.LOGIN
, and each get_packets
function could be replaced with something like this:
# minecraft/networking/packets/Packet.py
def get_packets_for(bound, state, context):
def packets(cls):
for subcls in cls.__subclasses__():
if (cls.packet_state, cls.packet_bound) == (state, bound)
and cls.get_id(context) is not None:
yield subcls
for packet in packets(subcls):
yield packet
return set(packets(Packet))
# minecraft/networking/packets/clientbound/play/__init__.py
from functools import partial
from minecraft.networking.packet import get_packets_for
# This function binding is retained for backward compatibility:
get_packets = partial(get_packets_for, Bound.CLIENT, State.PLAY)
(I realise it would also be possible to use e.g. ClientboundPlayPacket.__subclasses__()
directly, but I think the "duck typing" of accessing class attributes directly is more Pythonic.)
I can remember that a long time ago I tried to implement the chunk-data packet too. This is what I ended up with: chunk_data_packet.py
Absolutely not sure, if anything is close to being correct. i think the main reason why I stopped was that it was just a pain to test and the declining personal interest in minecraft.
If there is a broader interest in this packet, I would be willing to provide some help or even start to look into this again myself...just a general question for the chunk data that changed quite dramatically over the years: would it be ok to just provide a PR for some versions?
Also https://wiki.vg/Chunk_Format#Deserializing might be more helpful than my yibberish code.
just a general question for the chunk data that changed quite dramatically over the years: would it be ok to just provide a PR for some versions?
My initial plan was to keep the pyCraft core relatively simple, just handle the bare minimum for connecting etc and keep it easy to add new packets for users of the library. Personally I think parsing the chunk packets and keeping the map in memory is sufficiently complex that it would be best implemented as an optional package on top of pyCraft.
Edit: but obviously, I'm not the primary user now, I would like joodicator's opinion on the matter as well as yours since you're actively using the library.
I think it would make sense to decode the main packet fields listed here, but perhaps decoding the data part itself would be a bit much. As @ammaraskar notes, this could be done in another package.
If the CPU load of decoding chunk data packets even to this limited extent is a concern, then I think it would be possible to optimise Connection
so that it only decodes packets for which there is a matching listener (or are used by the current reactor).
I have been reluctant to add these kinds of packets to the library myself, because I do not use them in practice, and hence I might not have the best ideas as to what the most useful API is. It would be good if there were some kind of application which actually uses the chunk packets, to guide their design.
Regarding the addition of packets that do not support all of pyCraft's versions, I would say this is acceptable as long as all release versions are supported, and it doesn't seem like this would be very difficult for just the main fields of the Chunk Data packets.
thanks for your fast and clean reply , that solve the prob
For reference, here's how I added packets to the clientbound stuff without having to edit the library. The code is formatted grossly but it works
Any idea how to monkey-patch get_packets nowadays? I'm trying:
def get_packets(old_get_packets):
def wrapper(func, context):
packets = func(context)
packets.add(AcknowledgePlayerDiggingPacket)
packets.add(BlockBreakAnimationPacket)
return packets
return staticmethod(lambda x: wrapper(old_get_packets, x))
minecraft.networking.packets.clientbound.play.get_packets = get_packets(minecraft.networking.packets.clientbound.play.get_packets)
... but with no luck. wrapper()
never gets called.
Any idea how to monkey-patch get_packets nowadays? I'm trying:
def get_packets(old_get_packets): def wrapper(func, context): packets = func(context) packets.add(AcknowledgePlayerDiggingPacket) packets.add(BlockBreakAnimationPacket) return packets return staticmethod(lambda x: wrapper(old_get_packets, x)) minecraft.networking.packets.clientbound.play.get_packets = get_packets(minecraft.networking.packets.clientbound.play.get_packets)
... but with no luck.
wrapper()
never gets called.
Hey, @tannercollin ! May be, you should use this instead:
minecraft.networking.connection.PlayingReactor.get_clientbound_packets = get_packets(minecraft.networking.connection.PlayingReactor.get_clientbound_packets)
Hey guys! I am trying to implement ChunkDataPacket to see what block is situated and where. I have figured out how to listen to these Packets, but I don't know how to use their data to determine the blocks and their position. Can you help me, please? This would help my project a lot. Here is the ChunkDataPacket:
from collections import defaultdict, namedtuple
from minecraft.networking.packets import Packet
from minecraft.networking.types import (
Integer, Boolean, VarInt, VarIntPrefixedByteArray, TrailingByteArray,
UnsignedShort
)
class ChunkDataPacket(Packet):
@staticmethod
def get_id(context):
return 0x22 if context.protocol_version >= 389 else \
0x21 if context.protocol_version >= 345 else \
0x20 if context.protocol_version >= 332 else \
0x21 if context.protocol_version >= 318 else \
0x20 if context.protocol_version >= 70 else \
0x21
packet_name = "chunk data"
def read(self, file_object):
"""
A chunk is 16x256x16 (x y z) blocks.
Each chunk has 16 chunk sections where each section represents 16x16x16 blocks.
The number of chunk sections is equal to the number of bits set in Primary Bit Mask.
Chunk:
Section:
Block Data: 2 bytes per block. Total 8192 bytes. format: blockId << 4 | meta
Emitted Light: 4 bits per block (1/2 byte). Total 2048 bytes
Skylight: 4 bits per block (1/2 byte). Total 2048 bytes (only included in overworld)
Biome: 1 byte per block column. 256 bytes. (only included if all sections are in chunk)
"""
self.chunk_x = Integer.read(file_object)
self.chunk_z = Integer.read(file_object)
self.full = Boolean.read(file_object)
if self.context.protocol_version >= 107:
self.mask = VarInt.read(file_object)
else:
self.mask = UnsignedShort.read(file_object)
# size of data in bytes
self.data_size = VarInt.read(file_object)
self.read_chunk_column(file_object)
# TODO
# self.num_block_entities = VarInt.read(file_object)
# self.block_entities = TrailingByteArray.read(file_object)
def read_chunk_column(self, file_object):
chunk_height = 256
section_height, section_width = (16, 16)
Block = namedtuple('Block', 'x y z')
# section_count = bin(self.mask & 0xFFFF).count('1')
for section_y in range(chunk_height // section_height):
if self.mask & (1 << section_y):
# read block data - 2 bytes per block
for y in range(section_height):
for z in range(section_width):
for x in range(section_width):
block = Block(x, y, z)
block_data = UnsignedShort.read(file_object.bytes.getbuffer().tobytes())
return block, block_data
It seems to me that the data to operate with is file_object.bytes.getbuffer().tobytes()
(line57)
It usually looks something like:
b'\x00"\x00\x00\x04\x01\x00\x01t\x80\x00\x0bp\x1e\x00\x00\x00\x00\x00'
or
b'\x00"\x00\x00\x04\x01\x00\x00u\xc0\x007P\x19\x00\x00\x00\x00\x00
and etc.
So the question is can I parse block_data from this value with some transformations(because UnsignedShort requires 2 bytes to read, whereas file_object.bytes.getbuffer().tobytes() has about 15) ?
P.S. I used @TheSnoozer code as reference here
THANKS!
Maybe https://wiki.vg/Chunk_Format helps? I would also recommend to review "Full implementations"to see how other projects might have implemented what you need.
Maybe https://wiki.vg/Chunk_Format helps? I would also recommend to review "Full implementations"to see how other projects might have implemented what you need.
Hi, @TheSnoozer ! I've got a solution! I just used this world downloader And it downloads region files of the server, so then I can parse chunk data from them. I used anvil-parser Thanks, anyways!