mirror of
https://github.com/ammaraskar/pyCraft.git
synced 2024-11-21 09:45:28 +01:00
Merge branch 'v1.16' into master
This commit is contained in:
commit
3c84c2a429
3
.gitignore
vendored
3
.gitignore
vendored
@ -81,5 +81,8 @@ target/
|
||||
# sftp configuration file
|
||||
sftp-config.json
|
||||
|
||||
### Visual Studio
|
||||
.vscode
|
||||
|
||||
### pyCraft ###
|
||||
credentials
|
||||
|
@ -3,9 +3,7 @@ python: 3.8
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- python: 2.7
|
||||
env: TOX_ENV=py27
|
||||
- python: pypy
|
||||
- python: pypy3
|
||||
env: TOX_ENV=pypy
|
||||
- python: 3.5
|
||||
env: TOX_ENV=py35
|
||||
|
@ -31,6 +31,7 @@ pyCraft is compatible with the following Minecraft releases:
|
||||
* 1.13, 1.13.1, 1.13.2
|
||||
* 1.14, 1.14.1, 1.14.2, 1.14.3, 1.14.4
|
||||
* 1.15, 1.15.1, 1.15.2
|
||||
* 1.16, 1.16.1, 1.16.2
|
||||
|
||||
In addition, some development snapshots and pre-release versions are supported:
|
||||
`<minecraft/__init__.py>`_ contains a full list of supported Minecraft versions
|
||||
@ -50,7 +51,6 @@ Supported Python versions
|
||||
-------------------------
|
||||
pyCraft is compatible with (at least) the following Python implementations:
|
||||
|
||||
* Python 2.7
|
||||
* Python 3.5
|
||||
* Python 3.6
|
||||
* Python 3.7
|
||||
@ -61,7 +61,7 @@ Requirements
|
||||
------------
|
||||
- `cryptography <https://github.com/pyca/cryptography#cryptography>`_
|
||||
- `requests <http://docs.python-requests.org/en/latest/>`_
|
||||
- `future <http://python-future.org/>`_
|
||||
- `PyNBT <https://github.com/TkTech/PyNBT>`_
|
||||
|
||||
The requirements are also stored in ``setup.py``
|
||||
|
||||
|
@ -4,7 +4,7 @@ with a MineCraft server.
|
||||
"""
|
||||
|
||||
# The version number of the most recent pyCraft release.
|
||||
__version__ = "0.6.0"
|
||||
__version__ = "0.7.0"
|
||||
|
||||
# A dict mapping the ID string of each Minecraft version supported by pyCraft
|
||||
# to the corresponding protocol version number. The ID string of a version is
|
||||
@ -225,20 +225,84 @@ SUPPORTED_MINECRAFT_VERSIONS = {
|
||||
'1.15.2-pre1': 576,
|
||||
'1.15.2-pre2': 577,
|
||||
'1.15.2': 578,
|
||||
'20w06a': 701,
|
||||
'20w07a': 702,
|
||||
'20w08a': 703,
|
||||
'20w09a': 704,
|
||||
'20w10a': 705,
|
||||
'20w11a': 706,
|
||||
'20w12a': 707,
|
||||
'20w13a': 708,
|
||||
'20w13b': 709,
|
||||
'20w14a': 710,
|
||||
'20w15a': 711,
|
||||
'20w16a': 712,
|
||||
'20w17a': 713,
|
||||
'20w18a': 714,
|
||||
'20w19a': 715,
|
||||
'20w20a': 716,
|
||||
'20w20b': 717,
|
||||
'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,
|
||||
}
|
||||
|
||||
# Those Minecraft versions supported by pyCraft which are "release" versions,
|
||||
# i.e. not development snapshots or pre-release versions.
|
||||
RELEASE_MINECRAFT_VERSIONS = {
|
||||
vid: protocol for (vid, protocol) in SUPPORTED_MINECRAFT_VERSIONS.items()
|
||||
if __import__('re').match(r'\d+(\.\d+)+$', vid)}
|
||||
RELEASE_MINECRAFT_VERSIONS = {}
|
||||
|
||||
# The protocol versions of SUPPORTED_MINECRAFT_VERSIONS, without duplicates,
|
||||
# in ascending numerical (and hence chronological) order.
|
||||
SUPPORTED_PROTOCOL_VERSIONS = \
|
||||
sorted(set(SUPPORTED_MINECRAFT_VERSIONS.values()))
|
||||
SUPPORTED_PROTOCOL_VERSIONS = []
|
||||
|
||||
# The protocol versions of RELEASE_MINECRAFT_VERSIONS, without duplicates,
|
||||
# in ascending numerical (and hence chronological) order.
|
||||
RELEASE_PROTOCOL_VERSIONS = \
|
||||
sorted(set(RELEASE_MINECRAFT_VERSIONS.values()))
|
||||
RELEASE_PROTOCOL_VERSIONS = []
|
||||
|
||||
|
||||
def initglobals():
|
||||
'''Init the globals from the SUPPORTED_MINECRAFT_VERSIONS dict
|
||||
|
||||
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.
|
||||
'''
|
||||
global RELEASE_MINECRAFT_VERSIONS, SUPPORTED_PROTOCOL_VERSIONS
|
||||
global RELEASE_PROTOCOL_VERSIONS
|
||||
|
||||
import re
|
||||
|
||||
for (vid, protocol) in SUPPORTED_MINECRAFT_VERSIONS.items():
|
||||
if re.match(r"\d+(\.\d+)+$", vid):
|
||||
RELEASE_MINECRAFT_VERSIONS[vid] = protocol
|
||||
if protocol not in RELEASE_PROTOCOL_VERSIONS:
|
||||
RELEASE_PROTOCOL_VERSIONS.append(protocol)
|
||||
if protocol not in SUPPORTED_PROTOCOL_VERSIONS:
|
||||
SUPPORTED_PROTOCOL_VERSIONS.append(protocol)
|
||||
|
||||
SUPPORTED_PROTOCOL_VERSIONS.sort()
|
||||
RELEASE_PROTOCOL_VERSIONS.sort()
|
||||
|
||||
|
||||
initglobals()
|
||||
|
@ -1,24 +0,0 @@
|
||||
"""
|
||||
This module stores code used for making pyCraft compatible with
|
||||
both Python2 and Python3 while using the same codebase.
|
||||
"""
|
||||
|
||||
# Raw input -> input shenangians
|
||||
# example
|
||||
# > from minecraft.compat import input
|
||||
# > input("asd")
|
||||
|
||||
# Hi, I'm pylint, and sometimes I act silly, at which point my programmer
|
||||
# overlords need to correct me.
|
||||
|
||||
# pylint: disable=undefined-variable,redefined-builtin,invalid-name
|
||||
try:
|
||||
input = raw_input
|
||||
except NameError:
|
||||
input = input
|
||||
|
||||
try:
|
||||
unicode = unicode
|
||||
except NameError:
|
||||
unicode = str
|
||||
# pylint: enable=undefined-variable,redefined-builtin,invalid-name
|
@ -1,5 +1,3 @@
|
||||
from __future__ import print_function
|
||||
|
||||
from collections import deque
|
||||
from threading import RLock
|
||||
import zlib
|
||||
@ -11,8 +9,6 @@ import sys
|
||||
import json
|
||||
import re
|
||||
|
||||
from future.utils import raise_
|
||||
|
||||
from .types import VarInt
|
||||
from .packets import clientbound, serverbound
|
||||
from . import packets
|
||||
@ -495,7 +491,8 @@ class Connection(object):
|
||||
|
||||
# If allowed by the final exception handler, re-raise the exception.
|
||||
if self.handle_exception is None and not caught:
|
||||
raise_(*exc_info)
|
||||
exc_value, exc_tb = exc_info[1:]
|
||||
raise exc_value.with_traceback(exc_tb)
|
||||
|
||||
def _version_mismatch(self, server_protocol=None, server_version=None):
|
||||
if server_protocol is None:
|
||||
@ -510,7 +507,10 @@ class Connection(object):
|
||||
ss = 'supported, but not allowed for this connection' \
|
||||
if server_protocol in SUPPORTED_PROTOCOL_VERSIONS \
|
||||
else 'not supported'
|
||||
raise VersionMismatch("Server's %s is %s." % (vs, ss))
|
||||
err = VersionMismatch("Server's %s is %s." % (vs, ss))
|
||||
err.server_protocol = server_protocol
|
||||
err.server_version = server_version
|
||||
raise err
|
||||
|
||||
def _handle_exit(self):
|
||||
if not self.connected and self.handle_exit is not None:
|
||||
@ -593,7 +593,8 @@ class NetworkingThread(threading.Thread):
|
||||
exc_info = None
|
||||
|
||||
if exc_info is not None:
|
||||
raise_(*exc_info)
|
||||
exc_value, exc_tb = exc_info[1:]
|
||||
raise exc_value.with_traceback(exc_tb)
|
||||
|
||||
|
||||
class PacketReactor(object):
|
||||
@ -644,14 +645,16 @@ class PacketReactor(object):
|
||||
packet_id = VarInt.read(packet_data)
|
||||
|
||||
# If we know the structure of the packet, attempt to parse it
|
||||
# otherwise just skip it
|
||||
# otherwise, just return an instance of the base Packet class.
|
||||
if packet_id in self.clientbound_packets:
|
||||
packet = self.clientbound_packets[packet_id]()
|
||||
packet.context = self.connection.context
|
||||
packet.read(packet_data)
|
||||
return packet
|
||||
else:
|
||||
return packets.Packet(context=self.connection.context)
|
||||
packet = packets.Packet()
|
||||
packet.context = self.connection.context
|
||||
packet.id = packet_id
|
||||
return packet
|
||||
else:
|
||||
return None
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
from minecraft.networking.packets import Packet
|
||||
|
||||
from minecraft.networking.types import (
|
||||
VarInt, String, VarIntPrefixedByteArray, TrailingByteArray
|
||||
VarInt, String, VarIntPrefixedByteArray, TrailingByteArray, UUID,
|
||||
)
|
||||
|
||||
|
||||
@ -54,9 +54,10 @@ class LoginSuccessPacket(Packet):
|
||||
0x02
|
||||
|
||||
packet_name = "login success"
|
||||
definition = [
|
||||
{'UUID': String},
|
||||
{'Username': String}]
|
||||
get_definition = staticmethod(lambda context: [
|
||||
{'UUID': UUID if context.protocol_version >= 707 else String},
|
||||
{'Username': String}
|
||||
])
|
||||
|
||||
|
||||
class SetCompressionPacket(Packet):
|
||||
|
@ -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.
|
||||
@ -33,6 +33,8 @@ def get_packets(context):
|
||||
DisconnectPacket,
|
||||
SpawnPlayerPacket,
|
||||
EntityVelocityPacket,
|
||||
EntityPositionDeltaPacket,
|
||||
TimeUpdatePacket,
|
||||
UpdateHealthPacket,
|
||||
CombatEventPacket,
|
||||
ExplosionPacket,
|
||||
@ -62,7 +64,9 @@ def get_packets(context):
|
||||
class KeepAlivePacket(AbstractKeepAlivePacket):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x21 if context.protocol_version >= 550 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 \
|
||||
0x20 if context.protocol_version >= 345 else \
|
||||
@ -72,41 +76,11 @@ class KeepAlivePacket(AbstractKeepAlivePacket):
|
||||
0x00
|
||||
|
||||
|
||||
class JoinGamePacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 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},
|
||||
{'game_mode': UnsignedByte},
|
||||
{'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},
|
||||
{'render_distance': VarInt} if context.protocol_version >= 468 else {},
|
||||
{'reduced_debug_info': Boolean},
|
||||
{'respawn_screen': Boolean} if context.protocol_version >= 571 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 0x0E if context.protocol_version >= 550 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 \
|
||||
0x0D if context.protocol_version >= 70 else \
|
||||
@ -125,7 +99,8 @@ class ServerDifficultyPacket(Packet):
|
||||
class ChatMessagePacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x0F if context.protocol_version >= 550 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 \
|
||||
0x10 if context.protocol_version >= 317 else \
|
||||
@ -133,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.
|
||||
@ -146,7 +123,9 @@ class ChatMessagePacket(Packet):
|
||||
class DisconnectPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x1B if context.protocol_version >= 550 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 \
|
||||
0x1A if context.protocol_version >= 332 else \
|
||||
@ -171,7 +150,8 @@ class SetCompressionPacket(Packet):
|
||||
class SpawnPlayerPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x05 if context.protocol_version >= 67 else \
|
||||
return 0x04 if context.protocol_version >= 721 else \
|
||||
0x05 if context.protocol_version >= 67 else \
|
||||
0x0C
|
||||
|
||||
packet_name = 'spawn player'
|
||||
@ -206,7 +186,9 @@ class SpawnPlayerPacket(Packet):
|
||||
class EntityVelocityPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x46 if context.protocol_version >= 550 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 \
|
||||
0x41 if context.protocol_version >= 461 else \
|
||||
0x42 if context.protocol_version >= 451 else \
|
||||
@ -229,10 +211,59 @@ class EntityVelocityPacket(Packet):
|
||||
])
|
||||
|
||||
|
||||
class EntityPositionDeltaPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
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: [
|
||||
{'entity_id': VarInt},
|
||||
{'delta_x': Short},
|
||||
{'delta_y': Short},
|
||||
{'delta_z': Short},
|
||||
{'on_ground': Boolean}
|
||||
])
|
||||
|
||||
|
||||
class TimeUpdatePacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
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: [
|
||||
{'world_age': Long},
|
||||
{'time_of_day': Long},
|
||||
])
|
||||
|
||||
|
||||
class UpdateHealthPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x49 if context.protocol_version >= 550 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 \
|
||||
0x44 if context.protocol_version >= 461 else \
|
||||
0x45 if context.protocol_version >= 451 else \
|
||||
@ -254,41 +285,12 @@ class UpdateHealthPacket(Packet):
|
||||
])
|
||||
|
||||
|
||||
class RespawnPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 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},
|
||||
])
|
||||
|
||||
# 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 0x19 if context.protocol_version >= 550 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 \
|
||||
0x18 if context.protocol_version >= 332 else \
|
||||
@ -300,7 +302,8 @@ class PluginMessagePacket(AbstractPluginMessagePacket):
|
||||
class PlayerListHeaderAndFooterPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x54 if context.protocol_version >= 550 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 \
|
||||
0x50 if context.protocol_version >= 451 else \
|
||||
@ -321,7 +324,9 @@ class PlayerListHeaderAndFooterPacket(Packet):
|
||||
class EntityLookPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x2B if context.protocol_version >= 550 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 \
|
||||
0x28 if context.protocol_version >= 318 else \
|
||||
|
@ -1,14 +1,16 @@
|
||||
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 0x0C if context.protocol_version >= 550 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 \
|
||||
0x0B if context.protocol_version >= 67 else \
|
||||
@ -46,7 +48,9 @@ class BlockChangePacket(Packet):
|
||||
class MultiBlockChangePacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x10 if context.protocol_version >= 550 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 \
|
||||
0x11 if context.protocol_version >= 318 else \
|
||||
@ -55,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):
|
||||
@ -91,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,7 +8,9 @@ from minecraft.networking.types import (
|
||||
class CombatEventPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x33 if context.protocol_version >= 550 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 \
|
||||
0x2F if context.protocol_version >= 389 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,7 +7,9 @@ from minecraft.networking.packets import Packet
|
||||
class ExplosionPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x1D if context.protocol_version >= 550 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 \
|
||||
0x1D if context.protocol_version >= 345 else \
|
||||
@ -19,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')
|
||||
@ -28,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,7 +8,9 @@ from minecraft.networking.packets import Packet
|
||||
class FacePlayerPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x35 if context.protocol_version >= 550 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 \
|
||||
0x31 if context.protocol_version >= 389 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,7 +8,9 @@ from minecraft.networking.types import (
|
||||
class MapPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x27 if context.protocol_version >= 550 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 \
|
||||
0x24 if context.protocol_version >= 334 else \
|
||||
|
@ -9,7 +9,9 @@ from minecraft.networking.types import (
|
||||
class PlayerListItemPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x34 if context.protocol_version >= 550 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 \
|
||||
0x30 if context.protocol_version >= 389 else \
|
||||
|
@ -9,7 +9,9 @@ from minecraft.networking.types import (
|
||||
class PlayerPositionAndLookPacket(Packet, BitFieldEnum):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x36 if context.protocol_version >= 550 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 \
|
||||
0x32 if context.protocol_version >= 389 else \
|
||||
|
@ -9,11 +9,11 @@ __all__ = 'SoundEffectPacket',
|
||||
class SoundEffectPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x52 if context.protocol_version >= 550 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 \
|
||||
0x4E if context.protocol_version >= 451 else \
|
||||
0x4E if context.protocol_version >= 451 else \
|
||||
0x4D if context.protocol_version >= 389 else \
|
||||
0x4C if context.protocol_version >= 352 else \
|
||||
0x4B if context.protocol_version >= 345 else \
|
||||
|
@ -49,6 +49,7 @@ class SpawnObjectPacket(Packet):
|
||||
return getattr(cls, name)
|
||||
|
||||
class EntityType(Enum):
|
||||
# XXX This has not been updated for >= v1.15
|
||||
ACTIVATED_TNT = 50 if pv < 458 else 55 # PrimedTnt
|
||||
AREA_EFFECT_CLOUD = 3 if pv < 458 else 0
|
||||
ARMORSTAND = 78 if pv < 458 else 1
|
||||
|
@ -1,23 +1,28 @@
|
||||
from .packet_buffer import PacketBuffer
|
||||
from zlib import compress
|
||||
|
||||
from .packet_buffer import PacketBuffer
|
||||
from minecraft.networking.types import (
|
||||
VarInt, Enum
|
||||
VarInt, Enum, overridable_property,
|
||||
)
|
||||
|
||||
|
||||
class Packet(object):
|
||||
packet_name = "base"
|
||||
id = None
|
||||
definition = None
|
||||
|
||||
# To define the packet ID, either:
|
||||
# 1. Define the attribute `id', of type int, in a subclass; or
|
||||
# 2. Override `get_id' in a subclass and return the correct packet ID
|
||||
# for the given ConnectionContext. This is necessary if the packet ID
|
||||
# has changed across protocol versions, for example.
|
||||
# has changed across protocol versions, for example; or
|
||||
# 3. Define the attribute `id' in an instance of a class without either
|
||||
# of the above.
|
||||
@classmethod
|
||||
def get_id(cls, context):
|
||||
return cls.id
|
||||
def get_id(cls, _context):
|
||||
return getattr(cls, 'id')
|
||||
|
||||
@overridable_property
|
||||
def id(self):
|
||||
return None if self.context is None else self.get_id(self.context)
|
||||
|
||||
# To define the network data layout of a packet, either:
|
||||
# 1. Define the attribute `definition', a list of fields, each of which
|
||||
@ -29,37 +34,37 @@ class Packet(object):
|
||||
# This may be necessary if the packet layout cannot be described as a
|
||||
# simple list of fields.
|
||||
@classmethod
|
||||
def get_definition(cls, context):
|
||||
return cls.definition
|
||||
def get_definition(cls, _context):
|
||||
return getattr(cls, 'definition')
|
||||
|
||||
@overridable_property
|
||||
def definition(self):
|
||||
return None if self.context is None else \
|
||||
self.get_definition(self.context)
|
||||
|
||||
# In general, a packet instance must have its 'context' attribute set to an
|
||||
# instance of 'ConnectionContext', for example to decide on version-
|
||||
# dependent behaviour. This can either be given as an argument to this
|
||||
# constructor (e.g. 'p = P(context=c)') or set later
|
||||
# (e.g. 'p.context = c').
|
||||
#
|
||||
# While a packet has no 'context' set, all attributes should *writable*
|
||||
# without errors, but some attributes may not be *readable*.
|
||||
#
|
||||
# When sending or receiving packets via 'Connection', it is generally not
|
||||
# necessary to set the 'context', as this will be done automatically by
|
||||
# 'Connection'.
|
||||
def __init__(self, context=None, **kwargs):
|
||||
self.context = context
|
||||
self.set_values(**kwargs)
|
||||
|
||||
@property
|
||||
def context(self):
|
||||
return self._context
|
||||
|
||||
@context.setter
|
||||
def context(self, _context):
|
||||
self._context = _context
|
||||
self._context_changed()
|
||||
|
||||
def _context_changed(self):
|
||||
if self._context is not None:
|
||||
self.id = self.get_id(self._context)
|
||||
self.definition = self.get_definition(self._context)
|
||||
else:
|
||||
self.id = None
|
||||
self.definition = None
|
||||
|
||||
def set_values(self, **kwargs):
|
||||
for key, value in kwargs.items():
|
||||
setattr(self, key, value)
|
||||
return self
|
||||
|
||||
def read(self, file_object):
|
||||
for field in self.definition:
|
||||
for field in self.definition: # pylint: disable=not-an-iterable
|
||||
for var_name, data_type in field.items():
|
||||
value = data_type.read_with_context(file_object, self.context)
|
||||
setattr(self, var_name, value)
|
||||
@ -101,7 +106,7 @@ class Packet(object):
|
||||
def write_fields(self, packet_buffer):
|
||||
# Write the fields comprising the body of the packet (excluding the
|
||||
# length, packet ID, compression and encryption) into a PacketBuffer.
|
||||
for field in self.definition:
|
||||
for field in self.definition: # pylint: disable=not-an-iterable
|
||||
for var_name, data_type in field.items():
|
||||
data = getattr(self, var_name)
|
||||
data_type.send_with_context(data, packet_buffer, self.context)
|
||||
@ -122,6 +127,7 @@ class Packet(object):
|
||||
""" An iterable of the names of the packet's fields, or None. """
|
||||
if self.definition is None:
|
||||
return None
|
||||
# pylint: disable=not-an-iterable
|
||||
return (field for defn in self.definition for field in defn)
|
||||
|
||||
def field_string(self, field):
|
||||
|
@ -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
|
||||
multi_attribute_alias,
|
||||
)
|
||||
|
||||
from .client_settings_packet import ClientSettingsPacket
|
||||
@ -37,7 +37,8 @@ def get_packets(context):
|
||||
class KeepAlivePacket(AbstractKeepAlivePacket):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x0F if context.protocol_version >= 471 else \
|
||||
return 0x10 if context.protocol_version >= 712 else \
|
||||
0x0F if context.protocol_version >= 471 else \
|
||||
0x10 if context.protocol_version >= 464 else \
|
||||
0x0E if context.protocol_version >= 389 else \
|
||||
0x0C if context.protocol_version >= 386 else \
|
||||
@ -78,7 +79,8 @@ class ChatPacket(Packet):
|
||||
class PositionAndLookPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x12 if context.protocol_version >= 471 else \
|
||||
return 0x13 if context.protocol_version >= 712 else \
|
||||
0x12 if context.protocol_version >= 471 else \
|
||||
0x13 if context.protocol_version >= 464 else \
|
||||
0x11 if context.protocol_version >= 389 else \
|
||||
0x0F if context.protocol_version >= 386 else \
|
||||
@ -124,7 +126,9 @@ class TeleportConfirmPacket(Packet):
|
||||
class AnimationPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x2A if context.protocol_version >= 468 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 \
|
||||
0x25 if context.protocol_version >= 386 else \
|
||||
@ -197,7 +201,9 @@ class PlayerBlockPlacementPacket(Packet):
|
||||
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x2C if context.protocol_version >= 468 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 \
|
||||
0x27 if context.protocol_version >= 386 else \
|
||||
@ -234,7 +240,9 @@ class PlayerBlockPlacementPacket(Packet):
|
||||
class UseItemPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x2D if context.protocol_version >= 468 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 \
|
||||
0x28 if context.protocol_version >= 386 else \
|
||||
|
@ -2,32 +2,35 @@
|
||||
Each type has a method which is used to read and write it.
|
||||
These definitions and methods are used by the packet definitions
|
||||
"""
|
||||
from __future__ import division
|
||||
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):
|
||||
@ -131,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)
|
||||
@ -149,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
|
||||
|
||||
@ -172,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,
|
||||
@ -324,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,7 +1,7 @@
|
||||
"""Minecraft data types that are used by packets, but don't have a specific
|
||||
network representation.
|
||||
"""
|
||||
from __future__ import division
|
||||
import types
|
||||
|
||||
from collections import namedtuple
|
||||
from itertools import chain
|
||||
@ -9,7 +9,8 @@ from itertools import chain
|
||||
|
||||
__all__ = (
|
||||
'Vector', 'MutableRecord', 'Direction', 'PositionAndLook', 'descriptor',
|
||||
'attribute_alias', 'multi_attribute_alias',
|
||||
'overridable_descriptor', 'overridable_property', 'attribute_alias',
|
||||
'multi_attribute_alias',
|
||||
)
|
||||
|
||||
|
||||
@ -144,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
|
||||
@ -169,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")
|
||||
@ -181,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)
|
||||
|
||||
@ -191,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 +1,3 @@
|
||||
# Package dependencies are stored in setup.py.
|
||||
# For more information, see <https://github.com/ammaraskar/pyCraft/pull/156>.
|
||||
-e .
|
||||
|
2
setup.py
2
setup.py
@ -24,7 +24,7 @@ setup(name="pyCraft",
|
||||
author=", ".join(MAIN_AUTHORS),
|
||||
install_requires=["cryptography>=1.5",
|
||||
"requests",
|
||||
"future",
|
||||
"pynbt",
|
||||
],
|
||||
packages=["minecraft",
|
||||
"minecraft.networking",
|
||||
|
16
start.py
16
start.py
@ -1,7 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import getpass
|
||||
import sys
|
||||
import re
|
||||
@ -11,7 +9,6 @@ from minecraft import authentication
|
||||
from minecraft.exceptions import YggdrasilError
|
||||
from minecraft.networking.connection import Connection
|
||||
from minecraft.networking.packets import Packet, clientbound, serverbound
|
||||
from minecraft.compat import input
|
||||
|
||||
|
||||
def get_options():
|
||||
@ -35,6 +32,10 @@ def get_options():
|
||||
action="store_true",
|
||||
help="print sent and received packets to standard error")
|
||||
|
||||
parser.add_option("-v", "--dump-unknown-packets", dest="dump_unknown",
|
||||
action="store_true",
|
||||
help="include unknown packets in --dump-packets output")
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
if not options.username:
|
||||
@ -81,9 +82,12 @@ def main():
|
||||
def print_incoming(packet):
|
||||
if type(packet) is Packet:
|
||||
# This is a direct instance of the base Packet type, meaning
|
||||
# that it is a packet of unknown type, so we do not print it.
|
||||
return
|
||||
print('--> %s' % packet, file=sys.stderr)
|
||||
# that it is a packet of unknown type, so we do not print it
|
||||
# unless explicitly requested by the user.
|
||||
if options.dump_unknown:
|
||||
print('--> [unknown packet] %s' % packet, file=sys.stderr)
|
||||
else:
|
||||
print('--> %s' % packet, file=sys.stderr)
|
||||
|
||||
def print_outgoing(packet):
|
||||
print('<-- %s' % packet, file=sys.stderr)
|
||||
|
@ -1,7 +0,0 @@
|
||||
import platform
|
||||
from distutils.version import StrictVersion
|
||||
|
||||
if StrictVersion(platform.python_version()) < StrictVersion("3.3.0"):
|
||||
import mock # noqa
|
||||
else:
|
||||
from unittest import mock # noqa
|
@ -1,4 +1,4 @@
|
||||
from __future__ import print_function
|
||||
import pynbt
|
||||
|
||||
from minecraft import SUPPORTED_MINECRAFT_VERSIONS
|
||||
from minecraft.networking import connection
|
||||
@ -11,7 +11,6 @@ from minecraft.networking.encryption import (
|
||||
)
|
||||
|
||||
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
|
||||
from future.utils import raise_
|
||||
|
||||
from numbers import Integral
|
||||
import unittest
|
||||
@ -101,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.
|
||||
@ -181,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()
|
||||
@ -576,7 +612,8 @@ class _FakeServerTest(unittest.TestCase):
|
||||
logging.error(**error)
|
||||
self.fail('Multiple errors: see logging output.')
|
||||
elif errors and 'exc_info' in errors[0]:
|
||||
raise_(*errors[0]['exc_info'])
|
||||
exc_value, exc_tb = errors[0]['exc_info'][1:]
|
||||
raise exc_value.with_traceback(exc_tb)
|
||||
elif errors:
|
||||
self.fail(errors[0]['msg'])
|
||||
|
||||
|
@ -4,11 +4,11 @@ from minecraft.authentication import _make_request
|
||||
from minecraft.authentication import _raise_from_response
|
||||
from minecraft.exceptions import YggdrasilError
|
||||
|
||||
from unittest import mock
|
||||
import unittest
|
||||
import requests
|
||||
import json
|
||||
import unittest
|
||||
import os
|
||||
from .compat import mock
|
||||
|
||||
FAKE_DATA = {
|
||||
"id_": "85e2c12b9eab4a7dabf61babc11354c2",
|
||||
|
@ -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
|
||||
|
@ -5,7 +5,6 @@ from minecraft.networking.connection import Connection
|
||||
from minecraft.exceptions import (
|
||||
VersionMismatch, LoginDisconnect, InvalidState, IgnorePacket
|
||||
)
|
||||
from minecraft.compat import unicode
|
||||
|
||||
from . import fake_server
|
||||
|
||||
@ -92,7 +91,7 @@ class DefaultStatusTest(ConnectTest):
|
||||
def setUp(self):
|
||||
class FakeStdOut(io.BytesIO):
|
||||
def write(self, data):
|
||||
if isinstance(data, unicode):
|
||||
if isinstance(data, str):
|
||||
data = data.encode('utf8')
|
||||
super(FakeStdOut, self).write(data)
|
||||
sys.stdout, self.old_stdout = FakeStdOut(), sys.stdout
|
||||
|
@ -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:
|
||||
|
7
tox.ini
7
tox.ini
@ -4,7 +4,7 @@
|
||||
# and then run "tox" from this directory.
|
||||
|
||||
[tox]
|
||||
envlist = py27, py35, py36, py37, py38, pypy, flake8, pylint-errors, pylint-full, verify-manifest
|
||||
envlist = py35, py36, py37, py38, pypy, flake8, pylint-errors, pylint-full, verify-manifest
|
||||
|
||||
[testenv]
|
||||
commands = nosetests --with-timer
|
||||
@ -27,11 +27,6 @@ commands =
|
||||
deps =
|
||||
coveralls
|
||||
|
||||
[testenv:py27]
|
||||
deps =
|
||||
{[testenv]deps}
|
||||
mock
|
||||
|
||||
[testenv:py38]
|
||||
setenv =
|
||||
PYCRAFT_RUN_INTERNET_TESTS=1
|
||||
|
Loading…
Reference in New Issue
Block a user