mirror of
https://github.com/ammaraskar/pyCraft.git
synced 2024-11-21 17:56:30 +01:00
Fix support for Minecraft 20w06a to 1.16.2 (protocols 701 to 751)
This commit is contained in:
parent
b79f8b30eb
commit
4c35517157
@ -60,6 +60,7 @@ Requirements
|
||||
------------
|
||||
- `cryptography <https://github.com/pyca/cryptography#cryptography>`_
|
||||
- `requests <http://docs.python-requests.org/en/latest/>`_
|
||||
- `pynbt <https://github.com/TkTech/PyNBT>`_
|
||||
|
||||
The requirements are also stored in ``requirements.txt``
|
||||
|
||||
|
@ -242,13 +242,28 @@ SUPPORTED_MINECRAFT_VERSIONS = {
|
||||
'20w19a': 715,
|
||||
'20w20a': 716,
|
||||
'20w20b': 717,
|
||||
'1.16 Pre-release 2': 722,
|
||||
'1.16 Pre-release 3': 725,
|
||||
'1.16 Pre-release 4': 727,
|
||||
'1.16 Pre-release 5': 729,
|
||||
'1.16 Release Candidate 1':734,
|
||||
'20w21a': 718,
|
||||
'20w22a': 719,
|
||||
'1.16-pre1': 721,
|
||||
'1.16-pre2': 722,
|
||||
'1.16-pre3': 725,
|
||||
'1.16-pre4': 727,
|
||||
'1.16-pre5': 729,
|
||||
'1.16-pre6': 730,
|
||||
'1.16-pre7': 732,
|
||||
'1.16-pre8': 733,
|
||||
'1.16-rc1': 734,
|
||||
'1.16': 735,
|
||||
'1.16.1': 736,
|
||||
'20w27a': 738,
|
||||
'20w28a': 740,
|
||||
'20w29a': 741,
|
||||
'20w30a': 743,
|
||||
'1.16.2-pre1': 744,
|
||||
'1.16.2-pre2': 746,
|
||||
'1.16.2-pre3': 748,
|
||||
'1.16.2-rc1': 749,
|
||||
'1.16.2-rc2': 750,
|
||||
'1.16.2': 751,
|
||||
}
|
||||
|
||||
@ -270,9 +285,11 @@ def initglobals():
|
||||
|
||||
This allows the SUPPORTED_MINECRAFT_VERSIONS dict to be updated, then the
|
||||
other globals can be updated as well, to allow for dynamic version support.
|
||||
All updates are done by reference to allow this to work else where in the code.
|
||||
All updates are done by reference to allow this to work else where in the
|
||||
code.
|
||||
'''
|
||||
global RELEASE_MINECRAFT_VERSIONS, SUPPORTED_PROTOCOL_VERSIONS, RELEASE_PROTOCOL_VERSIONS
|
||||
global RELEASE_MINECRAFT_VERSIONS, SUPPORTED_PROTOCOL_VERSIONS
|
||||
global RELEASE_PROTOCOL_VERSIONS
|
||||
|
||||
import re
|
||||
|
||||
@ -288,4 +305,4 @@ def initglobals():
|
||||
RELEASE_PROTOCOL_VERSIONS.sort()
|
||||
|
||||
|
||||
initglobals()
|
||||
initglobals()
|
||||
|
@ -3,10 +3,9 @@ from minecraft.networking.packets import (
|
||||
)
|
||||
|
||||
from minecraft.networking.types import (
|
||||
Integer, FixedPointInteger, Angle, UnsignedByte, Byte, Boolean, UUID,
|
||||
Short, VarInt, Double, Float, String, Enum, Difficulty, Dimension,
|
||||
GameMode, Long, Vector, Direction, PositionAndLook,
|
||||
multi_attribute_alias,
|
||||
FixedPointInteger, Angle, UnsignedByte, Byte, Boolean, UUID, Short, VarInt,
|
||||
Double, Float, String, Enum, Difficulty, Long, Vector, Direction,
|
||||
PositionAndLook, multi_attribute_alias,
|
||||
)
|
||||
|
||||
from .combat_event_packet import CombatEventPacket
|
||||
@ -18,6 +17,7 @@ from .block_change_packet import BlockChangePacket, MultiBlockChangePacket
|
||||
from .explosion_packet import ExplosionPacket
|
||||
from .sound_effect_packet import SoundEffectPacket
|
||||
from .face_player_packet import FacePlayerPacket
|
||||
from .join_game_and_respawn_packets import JoinGamePacket, RespawnPacket
|
||||
|
||||
|
||||
# Formerly known as state_playing_clientbound.
|
||||
@ -64,8 +64,8 @@ def get_packets(context):
|
||||
class KeepAlivePacket(AbstractKeepAlivePacket):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x1F if context.protocol_version >= 751 else \
|
||||
0x20 if context.protocol_version >= 722 else \
|
||||
return 0x1F if context.protocol_version >= 741 else \
|
||||
0x20 if context.protocol_version >= 721 else \
|
||||
0x21 if context.protocol_version >= 550 else \
|
||||
0x20 if context.protocol_version >= 471 else \
|
||||
0x21 if context.protocol_version >= 389 else \
|
||||
@ -76,47 +76,10 @@ class KeepAlivePacket(AbstractKeepAlivePacket):
|
||||
0x00
|
||||
|
||||
|
||||
class JoinGamePacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x24 if context.protocol_version >= 751 else \
|
||||
0x25 if context.protocol_version >= 722 else \
|
||||
0x26 if context.protocol_version >= 550 else \
|
||||
0x25 if context.protocol_version >= 389 else \
|
||||
0x24 if context.protocol_version >= 345 else \
|
||||
0x23 if context.protocol_version >= 332 else \
|
||||
0x24 if context.protocol_version >= 318 else \
|
||||
0x23 if context.protocol_version >= 107 else \
|
||||
0x01
|
||||
|
||||
packet_name = "join game"
|
||||
get_definition = staticmethod(lambda context: [
|
||||
{'entity_id': Integer},
|
||||
{'is_hardcore': Boolean} if context.protocol_version >= 751 else {},
|
||||
{'game_mode': UnsignedByte},
|
||||
{'previous_game_mode': UnsignedByte} if context.protocol_version >= 722 else {},
|
||||
{'dimension': Integer if context.protocol_version >= 108 else Byte},
|
||||
{'hashed_seed': Long} if context.protocol_version >= 552 else {},
|
||||
{'difficulty': UnsignedByte} if context.protocol_version < 464 else {},
|
||||
{'max_players': UnsignedByte},
|
||||
{'level_type': String} if context.protocol_version < 716 else {},
|
||||
{'render_distance': VarInt} if context.protocol_version >= 468 else {},
|
||||
{'reduced_debug_info': Boolean},
|
||||
{'respawn_screen': Boolean} if context.protocol_version >= 571 else {},
|
||||
{'is_debug': Boolean} if context.protocol_version >= 716 else {},
|
||||
{'is_flat': Boolean} if context.protocol_version >= 716 else {},
|
||||
])
|
||||
|
||||
# These aliases declare the Enum type corresponding to each field:
|
||||
Difficulty = Difficulty
|
||||
GameMode = GameMode
|
||||
Dimension = Dimension
|
||||
|
||||
|
||||
class ServerDifficultyPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x0D if context.protocol_version >= 722 else \
|
||||
return 0x0D if context.protocol_version >= 721 else \
|
||||
0x0E if context.protocol_version >= 550 else \
|
||||
0x0D if context.protocol_version >= 332 else \
|
||||
0x0E if context.protocol_version >= 318 else \
|
||||
@ -136,7 +99,7 @@ class ServerDifficultyPacket(Packet):
|
||||
class ChatMessagePacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x0E if context.protocol_version >= 722 else \
|
||||
return 0x0E if context.protocol_version >= 721 else \
|
||||
0x0F if context.protocol_version >= 550 else \
|
||||
0x0E if context.protocol_version >= 343 else \
|
||||
0x0F if context.protocol_version >= 332 else \
|
||||
@ -145,9 +108,11 @@ class ChatMessagePacket(Packet):
|
||||
0x02
|
||||
|
||||
packet_name = "chat message"
|
||||
definition = [
|
||||
get_definition = staticmethod(lambda context: [
|
||||
{'json_data': String},
|
||||
{'position': Byte}]
|
||||
{'position': Byte},
|
||||
{'sender': UUID} if context.protocol_version >= 718 else {},
|
||||
])
|
||||
|
||||
class Position(Enum):
|
||||
CHAT = 0 # A player-initiated chat message.
|
||||
@ -158,8 +123,8 @@ class ChatMessagePacket(Packet):
|
||||
class DisconnectPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x19 if context.protocol_version >= 751 else \
|
||||
0x1A if context.protocol_version >= 722 else \
|
||||
return 0x19 if context.protocol_version >= 741 else \
|
||||
0x1A if context.protocol_version >= 721 else \
|
||||
0x1B if context.protocol_version >= 550 else \
|
||||
0x1A if context.protocol_version >= 471 else \
|
||||
0x1B if context.protocol_version >= 345 else \
|
||||
@ -185,7 +150,7 @@ class SetCompressionPacket(Packet):
|
||||
class SpawnPlayerPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x04 if context.protocol_version >= 722 else \
|
||||
return 0x04 if context.protocol_version >= 721 else \
|
||||
0x05 if context.protocol_version >= 67 else \
|
||||
0x0C
|
||||
|
||||
@ -221,7 +186,7 @@ class SpawnPlayerPacket(Packet):
|
||||
class EntityVelocityPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x46 if context.protocol_version >= 722 else \
|
||||
return 0x46 if context.protocol_version >= 721 else \
|
||||
0x47 if context.protocol_version >= 707 else \
|
||||
0x46 if context.protocol_version >= 550 else \
|
||||
0x45 if context.protocol_version >= 471 else \
|
||||
@ -249,10 +214,15 @@ class EntityVelocityPacket(Packet):
|
||||
class EntityPositionDeltaPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x27 if context.protocol_version >= 751 else \
|
||||
0x28 if context.protocol_version >= 722 else \
|
||||
0x29 if context.protocol_version >= 578 else \
|
||||
0xFF
|
||||
return 0x27 if context.protocol_version >= 741 else \
|
||||
0x28 if context.protocol_version >= 721 else \
|
||||
0x29 if context.protocol_version >= 550 else \
|
||||
0x28 if context.protocol_version >= 389 else \
|
||||
0x27 if context.protocol_version >= 345 else \
|
||||
0x26 if context.protocol_version >= 318 else \
|
||||
0x25 if context.protocol_version >= 94 else \
|
||||
0x26 if context.protocol_version >= 70 else \
|
||||
0x15
|
||||
|
||||
packet_name = "entity position delta"
|
||||
get_definition = staticmethod(lambda context: [
|
||||
@ -267,9 +237,19 @@ class EntityPositionDeltaPacket(Packet):
|
||||
class TimeUpdatePacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x4E if context.protocol_version >= 722 else \
|
||||
0x4F if context.protocol_version >= 578 else \
|
||||
0xFF
|
||||
return 0x4E if context.protocol_version >= 721 else \
|
||||
0x4F if context.protocol_version >= 550 else \
|
||||
0x4E if context.protocol_version >= 471 else \
|
||||
0x4A if context.protocol_version >= 461 else \
|
||||
0x4B if context.protocol_version >= 451 else \
|
||||
0x4A if context.protocol_version >= 389 else \
|
||||
0x49 if context.protocol_version >= 352 else \
|
||||
0x48 if context.protocol_version >= 345 else \
|
||||
0x47 if context.protocol_version >= 336 else \
|
||||
0x46 if context.protocol_version >= 318 else \
|
||||
0x44 if context.protocol_version >= 94 else \
|
||||
0x43 if context.protocol_version >= 70 else \
|
||||
0x03
|
||||
|
||||
packet_name = "time update"
|
||||
get_definition = staticmethod(lambda context: [
|
||||
@ -281,7 +261,7 @@ class TimeUpdatePacket(Packet):
|
||||
class UpdateHealthPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x49 if context.protocol_version >= 722 else \
|
||||
return 0x49 if context.protocol_version >= 721 else \
|
||||
0x4A if context.protocol_version >= 707 else \
|
||||
0x49 if context.protocol_version >= 550 else \
|
||||
0x48 if context.protocol_version >= 471 else \
|
||||
@ -305,47 +285,11 @@ class UpdateHealthPacket(Packet):
|
||||
])
|
||||
|
||||
|
||||
class RespawnPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x39 if context.protocol_version >= 751 else \
|
||||
0x3A if context.protocol_version >= 722 else \
|
||||
0x3B if context.protocol_version >= 550 else \
|
||||
0x3A if context.protocol_version >= 471 else \
|
||||
0x38 if context.protocol_version >= 461 else \
|
||||
0x39 if context.protocol_version >= 451 else \
|
||||
0x38 if context.protocol_version >= 389 else \
|
||||
0x37 if context.protocol_version >= 352 else \
|
||||
0x36 if context.protocol_version >= 345 else \
|
||||
0x35 if context.protocol_version >= 336 else \
|
||||
0x34 if context.protocol_version >= 332 else \
|
||||
0x35 if context.protocol_version >= 318 else \
|
||||
0x33 if context.protocol_version >= 70 else \
|
||||
0x07
|
||||
|
||||
packet_name = 'respawn'
|
||||
get_definition = staticmethod(lambda context: [
|
||||
{'dimension': Integer},
|
||||
{'difficulty': UnsignedByte} if context.protocol_version < 464 else {},
|
||||
{'hashed_seed': Long} if context.protocol_version >= 552 else {},
|
||||
{'game_mode': UnsignedByte},
|
||||
{'level_type': String} if context.protocol_version < 716 else {},
|
||||
{'is_debug': Boolean} if context.protocol_version >= 716 else {},
|
||||
{'is_flat': Boolean} if context.protocol_version >= 716 else {},
|
||||
{'copy_metadata': Boolean},
|
||||
])
|
||||
|
||||
# These aliases declare the Enum type corresponding to each field:
|
||||
Difficulty = Difficulty
|
||||
Dimension = Dimension
|
||||
GameMode = GameMode
|
||||
|
||||
|
||||
class PluginMessagePacket(AbstractPluginMessagePacket):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x17 if context.protocol_version >= 751 else \
|
||||
0x18 if context.protocol_version >= 722 else \
|
||||
return 0x17 if context.protocol_version >= 741 else \
|
||||
0x18 if context.protocol_version >= 721 else \
|
||||
0x19 if context.protocol_version >= 550 else \
|
||||
0x18 if context.protocol_version >= 471 else \
|
||||
0x19 if context.protocol_version >= 345 else \
|
||||
@ -358,7 +302,7 @@ class PluginMessagePacket(AbstractPluginMessagePacket):
|
||||
class PlayerListHeaderAndFooterPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x53 if context.protocol_version >= 722 else \
|
||||
return 0x53 if context.protocol_version >= 721 else \
|
||||
0x54 if context.protocol_version >= 550 else \
|
||||
0x53 if context.protocol_version >= 471 else \
|
||||
0x5F if context.protocol_version >= 461 else \
|
||||
@ -380,8 +324,8 @@ class PlayerListHeaderAndFooterPacket(Packet):
|
||||
class EntityLookPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x29 if context.protocol_version >= 751 else \
|
||||
0x2A if context.protocol_version >= 722 else \
|
||||
return 0x29 if context.protocol_version >= 741 else \
|
||||
0x2A if context.protocol_version >= 721 else \
|
||||
0x2B if context.protocol_version >= 550 else \
|
||||
0x2A if context.protocol_version >= 389 else \
|
||||
0x29 if context.protocol_version >= 345 else \
|
||||
|
@ -1,14 +1,15 @@
|
||||
from minecraft.networking.packets import Packet
|
||||
from minecraft.networking.types import (
|
||||
VarInt, Integer, UnsignedByte, Position, Vector, MutableRecord,
|
||||
attribute_alias, multi_attribute_alias,
|
||||
Type, VarInt, VarLong, Long, Integer, UnsignedByte, Position, Vector,
|
||||
MutableRecord, PrefixedArray, Boolean, attribute_alias,
|
||||
multi_attribute_alias,
|
||||
)
|
||||
|
||||
|
||||
class BlockChangePacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x0B if context.protocol_version >= 722 else \
|
||||
return 0x0B if context.protocol_version >= 721 else \
|
||||
0x0C if context.protocol_version >= 550 else \
|
||||
0x0B if context.protocol_version >= 332 else \
|
||||
0x0C if context.protocol_version >= 318 else \
|
||||
@ -47,8 +48,8 @@ class BlockChangePacket(Packet):
|
||||
class MultiBlockChangePacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x3B if context.protocol_version >= 751 else \
|
||||
0x0F if context.protocol_version >= 722 else \
|
||||
return 0x3B if context.protocol_version >= 741 else \
|
||||
0x0F if context.protocol_version >= 721 else \
|
||||
0x10 if context.protocol_version >= 550 else \
|
||||
0x0F if context.protocol_version >= 343 else \
|
||||
0x10 if context.protocol_version >= 332 else \
|
||||
@ -58,12 +59,23 @@ class MultiBlockChangePacket(Packet):
|
||||
|
||||
packet_name = 'multi block change'
|
||||
|
||||
fields = 'chunk_x', 'chunk_z', 'records'
|
||||
# Only used in protocol 741 and later.
|
||||
class ChunkSectionPos(Vector, Type):
|
||||
@classmethod
|
||||
def read(cls, file_object):
|
||||
value = Long.read(file_object)
|
||||
x = value >> 42
|
||||
z = (value >> 20) & 0x3FFFFF
|
||||
y = value & 0xFFFFF
|
||||
return cls(x, y, z)
|
||||
|
||||
# Access the 'chunk_x' and 'chunk_z' fields as a tuple.
|
||||
chunk_pos = multi_attribute_alias(tuple, 'chunk_x', 'chunk_z')
|
||||
@classmethod
|
||||
def send(cls, pos, socket):
|
||||
x, y, z = pos
|
||||
value = (x & 0x3FFFFF) << 42 | (z & 0x3FFFFF) << 20 | y & 0xFFFFF
|
||||
Long.send(value, socket)
|
||||
|
||||
class Record(MutableRecord):
|
||||
class Record(MutableRecord, Type):
|
||||
__slots__ = 'x', 'y', 'z', 'block_state_id'
|
||||
|
||||
def __init__(self, **kwds):
|
||||
@ -94,30 +106,47 @@ class MultiBlockChangePacket(Packet):
|
||||
# This alias is retained for backward compatibility.
|
||||
blockStateId = attribute_alias('block_state_id')
|
||||
|
||||
def read(self, file_object):
|
||||
h_position = UnsignedByte.read(file_object)
|
||||
self.x, self.z = h_position >> 4, h_position & 0xF
|
||||
self.y = UnsignedByte.read(file_object)
|
||||
self.block_state_id = VarInt.read(file_object)
|
||||
@classmethod
|
||||
def read_with_context(cls, file_object, context):
|
||||
record = cls()
|
||||
if context.protocol_version >= 741:
|
||||
value = VarLong.read(file_object)
|
||||
record.block_state_id = value >> 12
|
||||
record.x = (value >> 8) & 0xF
|
||||
record.z = (value >> 4) & 0xF
|
||||
record.y = value & 0xF
|
||||
else:
|
||||
h_position = UnsignedByte.read(file_object)
|
||||
record.x = h_position >> 4
|
||||
record.z = h_position & 0xF
|
||||
record.y = UnsignedByte.read(file_object)
|
||||
record.block_state_id = VarInt.read(file_object)
|
||||
return record
|
||||
|
||||
def write(self, packet_buffer):
|
||||
UnsignedByte.send(self.x << 4 | self.z & 0xF, packet_buffer)
|
||||
UnsignedByte.send(self.y, packet_buffer)
|
||||
VarInt.send(self.block_state_id, packet_buffer)
|
||||
@classmethod
|
||||
def send_with_context(self, record, socket, context):
|
||||
if context.protocol_version >= 741:
|
||||
value = record.block_state_id << 12 | \
|
||||
(record.x & 0xF) << 8 | \
|
||||
(record.z & 0xF) << 4 | \
|
||||
record.y & 0xF
|
||||
VarLong.send(value, socket)
|
||||
else:
|
||||
UnsignedByte.send(record.x << 4 | record.z & 0xF, socket)
|
||||
UnsignedByte.send(record.y, socket)
|
||||
VarInt.send(record.block_state_id, socket)
|
||||
|
||||
def read(self, file_object):
|
||||
self.chunk_x = Integer.read(file_object)
|
||||
self.chunk_z = Integer.read(file_object)
|
||||
records_count = VarInt.read(file_object)
|
||||
self.records = []
|
||||
for i in range(records_count):
|
||||
record = self.Record()
|
||||
record.read(file_object)
|
||||
self.records.append(record)
|
||||
get_definition = staticmethod(lambda context: [
|
||||
{'chunk_section_pos': MultiBlockChangePacket.ChunkSectionPos},
|
||||
{'invert_trust_edges': Boolean}
|
||||
if context.protocol_version >= 748 else {}, # Provisional field name.
|
||||
{'records': PrefixedArray(VarInt, MultiBlockChangePacket.Record)},
|
||||
] if context.protocol_version >= 741 else [
|
||||
{'chunk_x': Integer},
|
||||
{'chunk_z': Integer},
|
||||
{'records': PrefixedArray(VarInt, MultiBlockChangePacket.Record)},
|
||||
])
|
||||
|
||||
def write_fields(self, packet_buffer):
|
||||
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)
|
||||
# Access the 'chunk_x' and 'chunk_z' fields as a tuple.
|
||||
# Only used prior to protocol 741.
|
||||
chunk_pos = multi_attribute_alias(tuple, 'chunk_x', 'chunk_z')
|
||||
|
@ -8,8 +8,8 @@ from minecraft.networking.types import (
|
||||
class CombatEventPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x31 if context.protocol_version >= 751 else \
|
||||
0x32 if context.protocol_version >= 722 else \
|
||||
return 0x31 if context.protocol_version >= 741 else \
|
||||
0x32 if context.protocol_version >= 721 else \
|
||||
0x33 if context.protocol_version >= 550 else \
|
||||
0x32 if context.protocol_version >= 471 else \
|
||||
0x30 if context.protocol_version >= 451 else \
|
||||
|
@ -1,5 +1,5 @@
|
||||
from minecraft.networking.types import (
|
||||
Vector, Float, Byte, Integer, multi_attribute_alias,
|
||||
Vector, Float, Byte, Integer, PrefixedArray, multi_attribute_alias, Type,
|
||||
)
|
||||
from minecraft.networking.packets import Packet
|
||||
|
||||
@ -7,8 +7,8 @@ from minecraft.networking.packets import Packet
|
||||
class ExplosionPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x1B if context.protocol_version >= 751 else \
|
||||
0x1C if context.protocol_version >= 722 else \
|
||||
return 0x1B if context.protocol_version >= 741 else \
|
||||
0x1C if context.protocol_version >= 721 else \
|
||||
0x1D if context.protocol_version >= 550 else \
|
||||
0x1C if context.protocol_version >= 471 else \
|
||||
0x1E if context.protocol_version >= 389 else \
|
||||
@ -21,8 +21,27 @@ class ExplosionPacket(Packet):
|
||||
|
||||
packet_name = 'explosion'
|
||||
|
||||
fields = 'x', 'y', 'z', 'radius', 'records', \
|
||||
'player_motion_x', 'player_motion_y', 'player_motion_z'
|
||||
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)
|
||||
|
||||
definition = [
|
||||
{'x': Float},
|
||||
{'y': Float},
|
||||
{'z': Float},
|
||||
{'radius': Float},
|
||||
{'records': PrefixedArray(Integer, Record)},
|
||||
{'player_motion_x': Float},
|
||||
{'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')
|
||||
@ -30,37 +49,3 @@ class ExplosionPacket(Packet):
|
||||
# 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')
|
||||
|
||||
class Record(Vector):
|
||||
__slots__ = ()
|
||||
|
||||
def read(self, file_object):
|
||||
self.x = Float.read(file_object)
|
||||
self.y = Float.read(file_object)
|
||||
self.z = Float.read(file_object)
|
||||
self.radius = Float.read(file_object)
|
||||
records_count = Integer.read(file_object)
|
||||
self.records = []
|
||||
for i in range(records_count):
|
||||
rec_x = Byte.read(file_object)
|
||||
rec_y = Byte.read(file_object)
|
||||
rec_z = Byte.read(file_object)
|
||||
record = ExplosionPacket.Record(rec_x, rec_y, rec_z)
|
||||
self.records.append(record)
|
||||
self.player_motion_x = Float.read(file_object)
|
||||
self.player_motion_y = Float.read(file_object)
|
||||
self.player_motion_z = Float.read(file_object)
|
||||
|
||||
def write_fields(self, packet_buffer):
|
||||
Float.send(self.x, packet_buffer)
|
||||
Float.send(self.y, packet_buffer)
|
||||
Float.send(self.z, packet_buffer)
|
||||
Float.send(self.radius, packet_buffer)
|
||||
Integer.send(len(self.records), packet_buffer)
|
||||
for record in self.records:
|
||||
Byte.send(record.x, packet_buffer)
|
||||
Byte.send(record.y, packet_buffer)
|
||||
Byte.send(record.z, packet_buffer)
|
||||
Float.send(self.player_motion_x, packet_buffer)
|
||||
Float.send(self.player_motion_y, packet_buffer)
|
||||
Float.send(self.player_motion_z, packet_buffer)
|
||||
|
@ -8,8 +8,8 @@ from minecraft.networking.packets import Packet
|
||||
class FacePlayerPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x33 if context.protocol_version >= 751 else \
|
||||
0x34 if context.protocol_version >= 722 else \
|
||||
return 0x33 if context.protocol_version >= 741 else \
|
||||
0x34 if context.protocol_version >= 721 else \
|
||||
0x35 if context.protocol_version >= 550 else \
|
||||
0x34 if context.protocol_version >= 471 else \
|
||||
0x32 if context.protocol_version >= 451 else \
|
||||
|
@ -0,0 +1,208 @@
|
||||
import pynbt
|
||||
|
||||
from minecraft.networking.packets import Packet
|
||||
from minecraft.networking.types import (
|
||||
NBT, Integer, Boolean, UnsignedByte, String, Byte, Long, VarInt,
|
||||
PrefixedArray, Difficulty, GameMode, Dimension,
|
||||
)
|
||||
|
||||
|
||||
def nbt_to_snbt(tag):
|
||||
'''Convert a pyNBT tag to SNBT ("stringified NBT") format.'''
|
||||
scalars = {
|
||||
pynbt.TAG_Byte: 'b',
|
||||
pynbt.TAG_Short: 's',
|
||||
pynbt.TAG_Int: '',
|
||||
pynbt.TAG_Long: 'l',
|
||||
pynbt.TAG_Float: 'f',
|
||||
pynbt.TAG_Double: 'd',
|
||||
}
|
||||
if type(tag) in scalars:
|
||||
return repr(tag.value) + scalars[type(tag)]
|
||||
|
||||
arrays = {
|
||||
pynbt.TAG_Byte_Array: 'B',
|
||||
pynbt.TAG_Int_Array: 'I',
|
||||
pynbt.TAG_Long_Array: 'L',
|
||||
}
|
||||
if type(tag) in arrays:
|
||||
return '[' + arrays[type(tag)] + ';' + \
|
||||
','.join(map(repr, tag.value)) + ']'
|
||||
|
||||
if isinstance(tag, pynbt.TAG_String):
|
||||
return repr(tag.value)
|
||||
|
||||
if isinstance(tag, pynbt.TAG_List):
|
||||
return '[' + ','.join(map(nbt_to_snbt, tag.value)) + ']'
|
||||
|
||||
if isinstance(tag, pynbt.TAG_Compound):
|
||||
return '{' + ','.join(n + ':' + nbt_to_snbt(v)
|
||||
for (n, v) in tag.items()) + '}'
|
||||
|
||||
raise TypeError('Unknown NBT tag type: %r' % type(tag))
|
||||
|
||||
|
||||
class AbstractDimensionPacket(Packet):
|
||||
''' The abstract superclass of JoinGamePacket and RespawnPacket, containing
|
||||
common definitions relating to their 'dimension' field.
|
||||
'''
|
||||
def field_string(self, field):
|
||||
# pylint: disable=no-member
|
||||
if self.context.protocol_version >= 748 and field == 'dimension':
|
||||
return nbt_to_snbt(self.dimension)
|
||||
elif self.context.protocol_version < 718 and field == 'dimension':
|
||||
return Dimension.name_from_value(self.dimension)
|
||||
return super(AbstractDimensionPacket, self).field_string(field)
|
||||
|
||||
|
||||
class JoinGamePacket(AbstractDimensionPacket):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x24 if context.protocol_version >= 741 else \
|
||||
0x25 if context.protocol_version >= 721 else \
|
||||
0x26 if context.protocol_version >= 550 else \
|
||||
0x25 if context.protocol_version >= 389 else \
|
||||
0x24 if context.protocol_version >= 345 else \
|
||||
0x23 if context.protocol_version >= 332 else \
|
||||
0x24 if context.protocol_version >= 318 else \
|
||||
0x23 if context.protocol_version >= 107 else \
|
||||
0x01
|
||||
|
||||
packet_name = "join game"
|
||||
get_definition = staticmethod(lambda context: [
|
||||
{'entity_id': Integer},
|
||||
{'is_hardcore': Boolean} if context.protocol_version >= 738 else {},
|
||||
{'game_mode': UnsignedByte},
|
||||
{'previous_game_mode': UnsignedByte}
|
||||
if context.protocol_version >= 730 else {},
|
||||
{'world_names': PrefixedArray(VarInt, String)}
|
||||
if context.protocol_version >= 722 else {},
|
||||
{'dimension_codec': NBT}
|
||||
if context.protocol_version >= 718 else {},
|
||||
{'dimension':
|
||||
NBT if context.protocol_version >= 748 else
|
||||
String if context.protocol_version >= 718 else
|
||||
Integer if context.protocol_version >= 108 else
|
||||
Byte},
|
||||
{'world_name': String} if context.protocol_version >= 722 else {},
|
||||
{'hashed_seed': Long} if context.protocol_version >= 552 else {},
|
||||
{'difficulty': UnsignedByte} if context.protocol_version < 464 else {},
|
||||
{'max_players':
|
||||
VarInt if context.protocol_version >= 749 else UnsignedByte},
|
||||
{'level_type': String} if context.protocol_version < 716 else {},
|
||||
{'render_distance': VarInt} if context.protocol_version >= 468 else {},
|
||||
{'reduced_debug_info': Boolean},
|
||||
{'respawn_screen': Boolean} if context.protocol_version >= 571 else {},
|
||||
{'is_debug': Boolean} if context.protocol_version >= 716 else {},
|
||||
{'is_flat': Boolean} if context.protocol_version >= 716 else {},
|
||||
])
|
||||
|
||||
# These aliases declare the Enum type corresponding to each field:
|
||||
Difficulty = Difficulty
|
||||
GameMode = GameMode
|
||||
|
||||
# Accesses the 'game_mode' field appropriately depending on the protocol.
|
||||
# Can be set or deleted when 'context' is undefined.
|
||||
@property
|
||||
def game_mode(self):
|
||||
if self.context.protocol_version >= 738:
|
||||
return self._game_mode_738
|
||||
else:
|
||||
return self._game_mode_0
|
||||
|
||||
@game_mode.setter
|
||||
def game_mode(self, value):
|
||||
self._game_mode_738 = value
|
||||
self._game_mode_0 = value
|
||||
|
||||
@game_mode.deleter
|
||||
def game_mode(self):
|
||||
del self._game_mode_738
|
||||
del self._game_mode_0
|
||||
|
||||
# Accesses the 'is_hardcore' field, or its equivalent in older protocols.
|
||||
# Can be set or deleted when 'context' is undefined.
|
||||
@property
|
||||
def is_hardcore(self):
|
||||
if self.context.protocol_version >= 738:
|
||||
return self._is_hardcore
|
||||
else:
|
||||
return bool(self._game_mode_0 & GameMode.HARDCORE)
|
||||
|
||||
@is_hardcore.setter
|
||||
def is_hardcore(self, value):
|
||||
self._is_hardcore = value
|
||||
self._game_mode_0 = \
|
||||
getattr(self, '_game_mode_0', 0) | GameMode.HARDCORE \
|
||||
if value else \
|
||||
getattr(self, '_game_mode_0', 0) & ~GameMode.HARDCORE
|
||||
|
||||
@is_hardcore.deleter
|
||||
def is_hardcore(self):
|
||||
if hasattr(self, '_is_hardcore'):
|
||||
del self._is_hardcore
|
||||
if hasattr(self, '_game_mode_0'):
|
||||
self._game_mode_0 &= ~GameMode.HARDCORE
|
||||
|
||||
# Accesses the component of the 'game_mode' field without any hardcore bit,
|
||||
# version-independently. Can be set or deleted when 'context' is undefined.
|
||||
@property
|
||||
def pure_game_mode(self):
|
||||
if self.context.protocol_version >= 738:
|
||||
return self._game_mode_738
|
||||
else:
|
||||
return self._game_mode_0 & ~GameMode.HARDCORE
|
||||
|
||||
@pure_game_mode.setter
|
||||
def pure_game_mode(self, value):
|
||||
self._game_mode_738 = value
|
||||
self._game_mode_0 = \
|
||||
value & ~GameMode.HARDCORE | \
|
||||
getattr(self, '_game_mode_0', 0) & GameMode.HARDCORE
|
||||
|
||||
def field_string(self, field):
|
||||
if field == 'dimension_codec':
|
||||
# pylint: disable=no-member
|
||||
return nbt_to_snbt(self.dimension_codec)
|
||||
return super(JoinGamePacket, self).field_string(field)
|
||||
|
||||
|
||||
class RespawnPacket(AbstractDimensionPacket):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x39 if context.protocol_version >= 741 else \
|
||||
0x3A if context.protocol_version >= 721 else \
|
||||
0x3B if context.protocol_version >= 550 else \
|
||||
0x3A if context.protocol_version >= 471 else \
|
||||
0x38 if context.protocol_version >= 461 else \
|
||||
0x39 if context.protocol_version >= 451 else \
|
||||
0x38 if context.protocol_version >= 389 else \
|
||||
0x37 if context.protocol_version >= 352 else \
|
||||
0x36 if context.protocol_version >= 345 else \
|
||||
0x35 if context.protocol_version >= 336 else \
|
||||
0x34 if context.protocol_version >= 332 else \
|
||||
0x35 if context.protocol_version >= 318 else \
|
||||
0x33 if context.protocol_version >= 70 else \
|
||||
0x07
|
||||
|
||||
packet_name = 'respawn'
|
||||
get_definition = staticmethod(lambda context: [
|
||||
{'dimension':
|
||||
NBT if context.protocol_version >= 748 else
|
||||
String if context.protocol_version >= 718 else
|
||||
Integer},
|
||||
{'world_name': String} if context.protocol_version >= 719 else {},
|
||||
{'difficulty': UnsignedByte} if context.protocol_version < 464 else {},
|
||||
{'hashed_seed': Long} if context.protocol_version >= 552 else {},
|
||||
{'game_mode': UnsignedByte},
|
||||
{'previous_game_mode': UnsignedByte}
|
||||
if context.protocol_version >= 730 else {},
|
||||
{'level_type': String} if context.protocol_version < 716 else {},
|
||||
{'is_debug': Boolean} if context.protocol_version >= 716 else {},
|
||||
{'is_flat': Boolean} if context.protocol_version >= 716 else {},
|
||||
{'copy_metadata': Boolean} if context.protocol_version >= 714 else {},
|
||||
])
|
||||
|
||||
# These aliases declare the Enum type corresponding to each field:
|
||||
Difficulty = Difficulty
|
||||
GameMode = GameMode
|
@ -8,8 +8,8 @@ from minecraft.networking.types import (
|
||||
class MapPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x25 if context.protocol_version >= 751 else \
|
||||
0x26 if context.protocol_version >= 722 else \
|
||||
return 0x25 if context.protocol_version >= 741 else \
|
||||
0x26 if context.protocol_version >= 721 else \
|
||||
0x27 if context.protocol_version >= 550 else \
|
||||
0x26 if context.protocol_version >= 389 else \
|
||||
0x25 if context.protocol_version >= 345 else \
|
||||
|
@ -9,8 +9,8 @@ from minecraft.networking.types import (
|
||||
class PlayerListItemPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x32 if context.protocol_version >= 751 else \
|
||||
0x33 if context.protocol_version >= 722 else \
|
||||
return 0x32 if context.protocol_version >= 741 else \
|
||||
0x33 if context.protocol_version >= 721 else \
|
||||
0x34 if context.protocol_version >= 550 else \
|
||||
0x33 if context.protocol_version >= 471 else \
|
||||
0x31 if context.protocol_version >= 451 else \
|
||||
|
@ -9,8 +9,8 @@ from minecraft.networking.types import (
|
||||
class PlayerPositionAndLookPacket(Packet, BitFieldEnum):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x34 if context.protocol_version >= 751 else \
|
||||
0x35 if context.protocol_version >= 722 else \
|
||||
return 0x34 if context.protocol_version >= 741 else \
|
||||
0x35 if context.protocol_version >= 721 else \
|
||||
0x36 if context.protocol_version >= 550 else \
|
||||
0x35 if context.protocol_version >= 471 else \
|
||||
0x33 if context.protocol_version >= 451 else \
|
||||
|
@ -9,7 +9,7 @@ __all__ = 'SoundEffectPacket',
|
||||
class SoundEffectPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x51 if context.protocol_version >= 722 else \
|
||||
return 0x51 if context.protocol_version >= 721 else \
|
||||
0x52 if context.protocol_version >= 550 else \
|
||||
0x51 if context.protocol_version >= 471 else \
|
||||
0x4D if context.protocol_version >= 461 else \
|
||||
|
@ -5,7 +5,7 @@ from minecraft.networking.packets import (
|
||||
from minecraft.networking.types import (
|
||||
Double, Float, Boolean, VarInt, String, Byte, Position, Enum,
|
||||
RelativeHand, BlockFace, Vector, Direction, PositionAndLook,
|
||||
multi_attribute_alias, Short
|
||||
multi_attribute_alias,
|
||||
)
|
||||
|
||||
from .client_settings_packet import ClientSettingsPacket
|
||||
@ -126,7 +126,8 @@ class TeleportConfirmPacket(Packet):
|
||||
class AnimationPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x2B if context.protocol_version >= 719 else \
|
||||
return 0x2C if context.protocol_version >= 738 else \
|
||||
0x2B if context.protocol_version >= 712 else \
|
||||
0x2A if context.protocol_version >= 468 else \
|
||||
0x29 if context.protocol_version >= 464 else \
|
||||
0x27 if context.protocol_version >= 389 else \
|
||||
@ -200,7 +201,8 @@ class PlayerBlockPlacementPacket(Packet):
|
||||
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x2D if context.protocol_version >= 712 else \
|
||||
return 0x2E if context.protocol_version >= 738 else \
|
||||
0x2D if context.protocol_version >= 712 else \
|
||||
0x2C if context.protocol_version >= 468 else \
|
||||
0x2B if context.protocol_version >= 464 else \
|
||||
0x29 if context.protocol_version >= 389 else \
|
||||
@ -238,7 +240,8 @@ class PlayerBlockPlacementPacket(Packet):
|
||||
class UseItemPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x2E if context.protocol_version >= 719 else \
|
||||
return 0x2F if context.protocol_version >= 738 else \
|
||||
0x2E if context.protocol_version >= 712 else \
|
||||
0x2D if context.protocol_version >= 468 else \
|
||||
0x2C if context.protocol_version >= 464 else \
|
||||
0x2A if context.protocol_version >= 389 else \
|
||||
|
@ -4,29 +4,33 @@ These definitions and methods are used by the packet definitions
|
||||
"""
|
||||
import struct
|
||||
import uuid
|
||||
import io
|
||||
|
||||
from .utility import Vector
|
||||
import pynbt
|
||||
|
||||
from .utility import Vector, class_and_instancemethod
|
||||
|
||||
|
||||
__all__ = (
|
||||
'Type', 'Boolean', 'UnsignedByte', 'Byte', 'Short', 'UnsignedShort',
|
||||
'Integer', 'FixedPointInteger', 'Angle', 'VarInt', 'Long',
|
||||
'Integer', 'FixedPointInteger', 'Angle', 'VarInt', 'VarLong', 'Long',
|
||||
'UnsignedLong', 'Float', 'Double', 'ShortPrefixedByteArray',
|
||||
'VarIntPrefixedByteArray', 'TrailingByteArray', 'String', 'UUID',
|
||||
'Position',
|
||||
'Position', 'NBT', 'PrefixedArray',
|
||||
)
|
||||
|
||||
|
||||
class Type(object):
|
||||
# pylint: disable=no-self-argument
|
||||
__slots__ = ()
|
||||
|
||||
@classmethod
|
||||
def read_with_context(cls, file_object, _context):
|
||||
return cls.read(file_object)
|
||||
@class_and_instancemethod
|
||||
def read_with_context(cls_or_self, file_object, _context):
|
||||
return cls_or_self.read(file_object)
|
||||
|
||||
@classmethod
|
||||
def send_with_context(cls, value, socket, _context):
|
||||
return cls.send(value, socket)
|
||||
@class_and_instancemethod
|
||||
def send_with_context(cls_or_self, value, socket, _context):
|
||||
return cls_or_self.send(value, socket)
|
||||
|
||||
@classmethod
|
||||
def read(cls, file_object):
|
||||
@ -130,12 +134,13 @@ class Angle(Type):
|
||||
|
||||
|
||||
class VarInt(Type):
|
||||
@staticmethod
|
||||
def read(file_object):
|
||||
max_bytes = 5
|
||||
|
||||
@classmethod
|
||||
def read(cls, file_object):
|
||||
number = 0
|
||||
# Limit of 5 bytes, otherwise its possible to cause
|
||||
# a DOS attack by sending VarInts that just keep
|
||||
# going
|
||||
# Limit of 'cls.max_bytes' bytes, otherwise its possible to cause
|
||||
# a DOS attack by sending VarInts that just keep going
|
||||
bytes_encountered = 0
|
||||
while True:
|
||||
byte = file_object.read(1)
|
||||
@ -148,7 +153,7 @@ class VarInt(Type):
|
||||
break
|
||||
|
||||
bytes_encountered += 1
|
||||
if bytes_encountered > 5:
|
||||
if bytes_encountered > cls.max_bytes:
|
||||
raise ValueError("Tried to read too long of a VarInt")
|
||||
return number
|
||||
|
||||
@ -171,6 +176,10 @@ class VarInt(Type):
|
||||
raise ValueError("Integer too large")
|
||||
|
||||
|
||||
class VarLong(VarInt):
|
||||
max_bytes = 10
|
||||
|
||||
|
||||
# Maps (maximum integer value -> size of VarInt in bytes)
|
||||
VARINT_SIZE_TABLE = {
|
||||
2 ** 7: 1,
|
||||
@ -323,3 +332,48 @@ class Position(Type, Vector):
|
||||
if context.protocol_version >= 443 else
|
||||
(x & 0x3FFFFFF) << 38 | (y & 0xFFF) << 26 | (z & 0x3FFFFFF))
|
||||
UnsignedLong.send(value, socket)
|
||||
|
||||
|
||||
class NBT(Type):
|
||||
@staticmethod
|
||||
def read(file_object):
|
||||
return pynbt.NBTFile(io=file_object)
|
||||
|
||||
@staticmethod
|
||||
def send(value, socket):
|
||||
buffer = io.BytesIO()
|
||||
pynbt.NBTFile(value=value).save(buffer)
|
||||
socket.send(buffer.getvalue())
|
||||
|
||||
|
||||
class PrefixedArray(Type):
|
||||
__slots__ = 'length_type', 'element_type'
|
||||
|
||||
def __init__(self, length_type, element_type):
|
||||
self.length_type = length_type
|
||||
self.element_type = element_type
|
||||
|
||||
def read(self, file_object):
|
||||
return self.__read(file_object, self.element_type.read)
|
||||
|
||||
def send(self, value, socket):
|
||||
return self.__send(value, socket, self.element_type.send)
|
||||
|
||||
def read_with_context(self, file_object, context):
|
||||
def element_read(file_object):
|
||||
return self.element_type.read_with_context(file_object, context)
|
||||
return self.__read(file_object, element_read)
|
||||
|
||||
def send_with_context(self, value, socket, context):
|
||||
def element_send(value, socket):
|
||||
return self.element_type.send_with_context(value, socket, context)
|
||||
return self.__send(value, socket, element_send)
|
||||
|
||||
def __read(self, file_object, element_read):
|
||||
length = self.length_type.read(file_object)
|
||||
return [element_read(file_object) for i in range(length)]
|
||||
|
||||
def __send(self, value, socket, element_send):
|
||||
self.length_type.send(len(value), socket)
|
||||
for element in value:
|
||||
element_send(element, socket)
|
||||
|
@ -99,13 +99,22 @@ class Dimension(Enum):
|
||||
OVERWORLD = 0
|
||||
END = 1
|
||||
|
||||
from_identifier_dict = {
|
||||
'minecraft:the_nether': NETHER,
|
||||
'minecraft:overworld': OVERWORLD,
|
||||
'minecraft:the_end': END,
|
||||
}
|
||||
|
||||
to_identifier_dict = {e: i for (i, e) in from_identifier_dict.items()}
|
||||
|
||||
|
||||
# Designation of a player's gamemode.
|
||||
class GameMode(Enum):
|
||||
class GameMode(BitFieldEnum):
|
||||
SURVIVAL = 0
|
||||
CREATIVE = 1
|
||||
ADVENTURE = 2
|
||||
SPECTATOR = 3
|
||||
HARDCORE = 8 # Only used prior to protocol 738.
|
||||
|
||||
|
||||
# Currently designates an entity's feet or eyes.
|
||||
|
@ -1,13 +1,16 @@
|
||||
"""Minecraft data types that are used by packets, but don't have a specific
|
||||
network representation.
|
||||
"""
|
||||
import types
|
||||
|
||||
from collections import namedtuple
|
||||
from itertools import chain
|
||||
|
||||
|
||||
__all__ = (
|
||||
'Vector', 'MutableRecord', 'Direction', 'PositionAndLook', 'descriptor',
|
||||
'attribute_alias', 'multi_attribute_alias',
|
||||
'overridable_descriptor', 'overridable_property', 'attribute_alias',
|
||||
'multi_attribute_alias',
|
||||
)
|
||||
|
||||
|
||||
@ -142,23 +145,58 @@ def multi_attribute_alias(container, *arg_names, **kwd_names):
|
||||
return alias
|
||||
|
||||
|
||||
class descriptor(object):
|
||||
"""Behaves identically to the builtin 'property' function of Python,
|
||||
except that the getter, setter and deleter functions given by the
|
||||
user are used as the raw __get__, __set__ and __delete__ functions
|
||||
as defined in Python's descriptor protocol.
|
||||
class overridable_descriptor:
|
||||
"""As 'descriptor' (defined below), except that only a getter can be
|
||||
defined, and the resulting descriptor has no '__set__' or '__delete__'
|
||||
methods defined; hence, attributes defined via this class can be
|
||||
overridden by attributes of instances of the class in which it occurs.
|
||||
"""
|
||||
__slots__ = '_fget', '_fset', '_fdel'
|
||||
__slots__ = '_fget',
|
||||
|
||||
def __init__(self, fget=None, fset=None, fdel=None):
|
||||
def __init__(self, fget=None):
|
||||
self._fget = fget if fget is not None else self._default_get
|
||||
self._fset = fset if fset is not None else self._default_set
|
||||
self._fdel = fdel if fdel is not None else self._default_del
|
||||
|
||||
def getter(self, fget):
|
||||
self._fget = fget
|
||||
return self
|
||||
|
||||
@staticmethod
|
||||
def _default_get(instance, owner):
|
||||
raise AttributeError('unreadable attribute')
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
return self._fget(self, instance, owner)
|
||||
|
||||
|
||||
class overridable_property(overridable_descriptor):
|
||||
"""As the builtin 'property' decorator of Python, except that only
|
||||
a getter is defined and the resulting descriptor is a non-data
|
||||
descriptor, overridable by attributes of instances of the class
|
||||
in which the property occurs. See also 'overridable_descriptor' above.
|
||||
"""
|
||||
def __get__(self, instance, _owner):
|
||||
return self._fget(instance)
|
||||
|
||||
|
||||
class descriptor(overridable_descriptor):
|
||||
"""Behaves identically to the builtin 'property' decorator of Python,
|
||||
except that the getter, setter and deleter functions given by the
|
||||
user are used as the raw __get__, __set__ and __delete__ functions
|
||||
as defined in Python's descriptor protocol.
|
||||
|
||||
Since an instance of this class always havs '__set__' and '__delete__'
|
||||
defined, it is a "data descriptor", so its binding behaviour cannot be
|
||||
overridden in instances of the class in which it occurs. See
|
||||
https://docs.python.org/3/reference/datamodel.html#descriptor-invocation
|
||||
for more information. See also 'overridable_descriptor' above.
|
||||
"""
|
||||
__slots__ = '_fset', '_fdel'
|
||||
|
||||
def __init__(self, fget=None, fset=None, fdel=None):
|
||||
super(descriptor, self).__init__(fget=fget)
|
||||
self._fset = fset if fset is not None else self._default_set
|
||||
self._fdel = fdel if fdel is not None else self._default_del
|
||||
|
||||
def setter(self, fset):
|
||||
self._fset = fset
|
||||
return self
|
||||
@ -167,10 +205,6 @@ class descriptor(object):
|
||||
self._fdel = fdel
|
||||
return self
|
||||
|
||||
@staticmethod
|
||||
def _default_get(instance, owner):
|
||||
raise AttributeError('unreadable attribute')
|
||||
|
||||
@staticmethod
|
||||
def _default_set(instance, value):
|
||||
raise AttributeError("can't set attribute")
|
||||
@ -179,9 +213,6 @@ class descriptor(object):
|
||||
def _default_del(instance):
|
||||
raise AttributeError("can't delete attribute")
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
return self._fget(self, instance, owner)
|
||||
|
||||
def __set__(self, instance, value):
|
||||
return self._fset(self, instance, value)
|
||||
|
||||
@ -189,6 +220,26 @@ class descriptor(object):
|
||||
return self._fdel(self, instance)
|
||||
|
||||
|
||||
class class_and_instancemethod:
|
||||
""" A decorator for functions defined in a class namespace which are to be
|
||||
accessed as both class and instance methods: retrieving the method from
|
||||
a class will return a bound class method (like the built-in
|
||||
'classmethod' decorator), but retrieving the method from an instance
|
||||
will return a bound instance method (as if the function were not
|
||||
decorated). Therefore, the first argument of the decorated function may
|
||||
be either a class or an instance, depending on how it was called.
|
||||
"""
|
||||
|
||||
__slots__ = '_func',
|
||||
|
||||
def __init__(self, func):
|
||||
self._func = func
|
||||
|
||||
def __get__(self, inst, owner=None):
|
||||
bind_to = owner if inst is None else inst
|
||||
return types.MethodType(self._func, bind_to)
|
||||
|
||||
|
||||
Direction = namedtuple('Direction', ('yaw', 'pitch'))
|
||||
|
||||
|
||||
|
@ -1,2 +1,3 @@
|
||||
cryptography>=1.5
|
||||
requests
|
||||
pynbt
|
||||
|
@ -1,3 +1,5 @@
|
||||
import pynbt
|
||||
|
||||
from minecraft import SUPPORTED_MINECRAFT_VERSIONS
|
||||
from minecraft.networking import connection
|
||||
from minecraft.networking import types
|
||||
@ -98,10 +100,46 @@ class FakeClientHandler(object):
|
||||
|
||||
def handle_play_start(self):
|
||||
# Called upon entering the play state.
|
||||
self.write_packet(clientbound.play.JoinGamePacket(
|
||||
entity_id=0, game_mode=0, dimension=0, hashed_seed=12345,
|
||||
difficulty=2, max_players=1, level_type='default',
|
||||
reduced_debug_info=False, render_distance=9, respawn_screen=False))
|
||||
packet = clientbound.play.JoinGamePacket(
|
||||
entity_id=0, is_hardcore=False, game_mode=0, previous_game_mode=0,
|
||||
world_names=['minecraft:overworld'],
|
||||
world_name='minecraft:overworld',
|
||||
hashed_seed=12345, difficulty=2, max_players=1,
|
||||
level_type='default', reduced_debug_info=False, render_distance=9,
|
||||
respawn_screen=False, is_debug=False, is_flat=False)
|
||||
|
||||
if self.server.context.protocol_version >= 748:
|
||||
packet.dimension = pynbt.TAG_Compound({
|
||||
'natural': pynbt.TAG_Byte(1),
|
||||
'effects': pynbt.TAG_String('minecraft:overworld'),
|
||||
}, '')
|
||||
packet.dimension_codec = pynbt.TAG_Compound({
|
||||
'minecraft:dimension_type': pynbt.TAG_Compound({
|
||||
'type': pynbt.TAG_String('minecraft:dimension_type'),
|
||||
'value': pynbt.TAG_List(pynbt.TAG_Compound, [
|
||||
pynbt.TAG_Compound(packet.dimension),
|
||||
]),
|
||||
}),
|
||||
'minecraft:worldgen/biome': pynbt.TAG_Compound({
|
||||
'type': pynbt.TAG_String('minecraft:worldgen/biome'),
|
||||
'value': pynbt.TAG_List(pynbt.TAG_Compound, [
|
||||
pynbt.TAG_Compound({
|
||||
'id': pynbt.TAG_Int(1),
|
||||
'name': pynbt.TAG_String('minecraft:plains'),
|
||||
}),
|
||||
pynbt.TAG_Compound({
|
||||
'id': pynbt.TAG_Int(2),
|
||||
'name': pynbt.TAG_String('minecraft:desert'),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
}, '')
|
||||
elif self.server.context.protocol_version >= 718:
|
||||
packet.dimension = 'minecraft:overworld'
|
||||
else:
|
||||
packet.dimension = types.Dimension.OVERWORLD
|
||||
|
||||
self.write_packet(packet)
|
||||
|
||||
def handle_play_packet(self, packet):
|
||||
# Called upon each packet received after handle_play_start() returns.
|
||||
@ -178,7 +216,8 @@ class FakeClientHandler(object):
|
||||
try:
|
||||
self.handle_connection()
|
||||
packet = self.read_packet()
|
||||
assert isinstance(packet, serverbound.handshake.HandShakePacket)
|
||||
assert isinstance(packet, serverbound.handshake.HandShakePacket), \
|
||||
type(packet)
|
||||
self.handle_handshake(packet)
|
||||
if packet.next_state == 1:
|
||||
self._run_status()
|
||||
|
@ -101,3 +101,56 @@ class ClassMemberAliasesTest(unittest.TestCase):
|
||||
packet = clientbound.play.BlockChangePacket(blockId=bi, blockMeta=bm)
|
||||
self.assertEqual((packet.blockId, packet.blockMeta), (bi, bm))
|
||||
self.assertEqual(packet.blockStateId, packet.block_state_id)
|
||||
|
||||
def test_join_game_packet(self):
|
||||
GameMode = types.GameMode
|
||||
context = ConnectionContext()
|
||||
for pure_game_mode in (GameMode.SURVIVAL, GameMode.CREATIVE,
|
||||
GameMode.ADVENTURE, GameMode.SPECTATOR):
|
||||
for is_hardcore in (False, True):
|
||||
context.protocol_version = 70
|
||||
game_mode = \
|
||||
pure_game_mode | GameMode.HARDCORE \
|
||||
if is_hardcore else pure_game_mode
|
||||
|
||||
packet = clientbound.play.JoinGamePacket()
|
||||
packet.game_mode = game_mode
|
||||
packet.context = context
|
||||
self.assertEqual(packet.pure_game_mode, pure_game_mode)
|
||||
self.assertEqual(packet.is_hardcore, is_hardcore)
|
||||
|
||||
del packet.context
|
||||
del packet.is_hardcore
|
||||
packet.context = context
|
||||
self.assertEqual(packet.game_mode, packet.pure_game_mode)
|
||||
|
||||
del packet.context
|
||||
del packet.game_mode
|
||||
packet.context = context
|
||||
self.assertFalse(hasattr(packet, 'is_hardcore'))
|
||||
|
||||
packet = clientbound.play.JoinGamePacket()
|
||||
packet.pure_game_mode = game_mode
|
||||
packet.is_hardcore = is_hardcore
|
||||
packet.context = context
|
||||
self.assertEqual(packet.game_mode, game_mode)
|
||||
|
||||
context.protocol_version = 738
|
||||
game_mode = pure_game_mode | GameMode.HARDCORE
|
||||
|
||||
packet = clientbound.play.JoinGamePacket()
|
||||
packet.game_mode = game_mode
|
||||
packet.is_hardcore = is_hardcore
|
||||
packet.context = context
|
||||
self.assertEqual(packet.game_mode, game_mode)
|
||||
self.assertEqual(packet.pure_game_mode, game_mode)
|
||||
self.assertEqual(packet.is_hardcore, is_hardcore)
|
||||
|
||||
del packet.context
|
||||
packet.is_hardcore = is_hardcore
|
||||
packet.context = context
|
||||
self.assertEqual(packet.game_mode, game_mode)
|
||||
self.assertEqual(packet.pure_game_mode, game_mode)
|
||||
|
||||
with self.assertRaises(AttributeError):
|
||||
del packet.pure_game_mode
|
||||
|
@ -186,11 +186,9 @@ class TestReadWritePackets(unittest.TestCase):
|
||||
packet = clientbound.play.CombatEventPacket(
|
||||
event=clientbound.play.CombatEventPacket.EndCombatEvent(
|
||||
duration=415, entity_id=91063502))
|
||||
self.assertEqual(
|
||||
str(packet),
|
||||
'CombatEventPacket(event=EndCombatEvent('
|
||||
'duration=415, entity_id=91063502))'
|
||||
)
|
||||
self.assertEqual(str(packet),
|
||||
'CombatEventPacket(event=EndCombatEvent('
|
||||
'duration=415, entity_id=91063502))')
|
||||
self._test_read_write_packet(packet)
|
||||
|
||||
packet = clientbound.play.CombatEventPacket(
|
||||
@ -205,26 +203,32 @@ class TestReadWritePackets(unittest.TestCase):
|
||||
|
||||
def test_multi_block_change_packet(self):
|
||||
Record = clientbound.play.MultiBlockChangePacket.Record
|
||||
packet = clientbound.play.MultiBlockChangePacket(
|
||||
chunk_x=167, chunk_z=15, records=[
|
||||
Record(x=1, y=2, z=3, blockId=56, blockMeta=13),
|
||||
Record(position=Vector(1, 2, 3), block_state_id=909),
|
||||
Record(position=(1, 2, 3), blockStateId=909)])
|
||||
self.assertEqual(packet.records[0].blockId, 56)
|
||||
self.assertEqual(packet.records[0].blockMeta, 13)
|
||||
self.assertEqual(packet.records[0].blockStateId, 909)
|
||||
self.assertEqual(packet.records[0].position, Vector(1, 2, 3))
|
||||
self.assertEqual(packet.chunk_pos, (packet.chunk_x, packet.chunk_z))
|
||||
|
||||
self.assertEqual(
|
||||
str(packet),
|
||||
'MultiBlockChangePacket(chunk_x=167, chunk_z=15, records=['
|
||||
'Record(x=1, y=2, z=3, block_state_id=909), '
|
||||
'Record(x=1, y=2, z=3, block_state_id=909), '
|
||||
'Record(x=1, y=2, z=3, block_state_id=909)])'
|
||||
)
|
||||
for protocol_version in TEST_VERSIONS:
|
||||
context = ConnectionContext()
|
||||
context.protocol_version = protocol_version
|
||||
packet = clientbound.play.MultiBlockChangePacket(context)
|
||||
|
||||
self._test_read_write_packet(packet)
|
||||
if protocol_version >= 741:
|
||||
packet.chunk_section_pos = Vector(167, 17, 33)
|
||||
packet.invert_trust_edges = False
|
||||
else:
|
||||
packet.chunk_x, packet.chunk_z = 167, 17
|
||||
self.assertEqual(packet.chunk_pos, (167, 17))
|
||||
|
||||
packet.records = [
|
||||
Record(x=1, y=2, z=3, blockId=56, blockMeta=13),
|
||||
Record(position=Vector(1, 2, 3), block_state_id=909),
|
||||
Record(position=(1, 2, 3), blockStateId=909),
|
||||
]
|
||||
|
||||
for i in range(3):
|
||||
self.assertEqual(packet.records[i].blockId, 56)
|
||||
self.assertEqual(packet.records[i].blockMeta, 13)
|
||||
self.assertEqual(packet.records[i].blockStateId, 909)
|
||||
self.assertEqual(packet.records[i].position, Vector(1, 2, 3))
|
||||
|
||||
self._test_read_write_packet(packet, context)
|
||||
|
||||
def test_spawn_object_packet(self):
|
||||
for protocol_version in TEST_VERSIONS:
|
||||
|
Loading…
Reference in New Issue
Block a user