mirror of
https://github.com/ammaraskar/pyCraft.git
synced 2024-11-25 11:46:54 +01:00
Merge branch 'master' into patch-1
This commit is contained in:
commit
d627423949
@ -28,7 +28,8 @@ pyCraft is compatible with the following Minecraft releases:
|
||||
* 1.10, 1.10.1, 1.10.2
|
||||
* 1.11, 1.11.1, 1.11.2
|
||||
* 1.12, 1.12.1, 1.12.2
|
||||
* 1.13
|
||||
* 1.13, 1.13.1, 1.13.2
|
||||
* 1.14
|
||||
|
||||
In addition, some development snapshots and pre-release versions are supported:
|
||||
`<minecraft/__init__.py>`_ contains a full list of supported Minecraft versions
|
||||
|
@ -28,10 +28,10 @@ is provided or the web request failed.
|
||||
:members:
|
||||
|
||||
|
||||
Arbitary Requests
|
||||
Arbitrary Requests
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You may make any arbitary request to the Yggdrasil service with the _make_request
|
||||
You may make any arbitrary request to the Yggdrasil service with the _make_request
|
||||
method passing in the AUTH_SERVER as the server parameter.
|
||||
|
||||
.. automodule:: minecraft.authentication
|
||||
@ -43,7 +43,7 @@ method passing in the AUTH_SERVER as the server parameter.
|
||||
---------------
|
||||
Example Usage
|
||||
---------------
|
||||
An example of making an arbitary request can be seen here::
|
||||
An example of making an arbitrary request can be seen here::
|
||||
|
||||
payload = {'username': username,
|
||||
'password': password}
|
||||
|
@ -34,7 +34,6 @@ extensions = [
|
||||
'sphinx.ext.intersphinx',
|
||||
'sphinx.ext.todo',
|
||||
'sphinx.ext.coverage',
|
||||
'sphinx.ext.pngmath',
|
||||
'sphinx.ext.viewcode',
|
||||
]
|
||||
|
||||
|
@ -6,121 +6,170 @@ with a MineCraft server.
|
||||
__version__ = "0.5.0"
|
||||
|
||||
SUPPORTED_MINECRAFT_VERSIONS = {
|
||||
'1.8': 47,
|
||||
'1.8.1': 47,
|
||||
'1.8.2': 47,
|
||||
'1.8.3': 47,
|
||||
'1.8.4': 47,
|
||||
'1.8.5': 47,
|
||||
'1.8.6': 47,
|
||||
'1.8.7': 47,
|
||||
'1.8.8': 47,
|
||||
'1.8.9': 47,
|
||||
'1.9': 107,
|
||||
'1.9.1': 108,
|
||||
'1.9.2': 109,
|
||||
'1.9.3': 110,
|
||||
'1.9.4': 110,
|
||||
'1.10': 210,
|
||||
'1.10.1': 210,
|
||||
'1.10.2': 210,
|
||||
'16w32a': 301,
|
||||
'16w32b': 302,
|
||||
'16w33a': 303,
|
||||
'16w35a': 304,
|
||||
'16w36a': 305,
|
||||
'16w38a': 306,
|
||||
'16w39a': 307,
|
||||
'16w39b': 308,
|
||||
'16w39c': 309,
|
||||
'16w40a': 310,
|
||||
'16w41a': 311,
|
||||
'16w42a': 312,
|
||||
'16w43a': 313,
|
||||
'16w44a': 313,
|
||||
'1.11-pre1': 314,
|
||||
'1.11': 315,
|
||||
'16w50a': 316,
|
||||
'1.11.1': 316,
|
||||
'1.11.2': 316,
|
||||
'17w06a': 317,
|
||||
'17w13a': 318,
|
||||
'17w13b': 319,
|
||||
'17w14a': 320,
|
||||
'17w15a': 321,
|
||||
'17w16a': 322,
|
||||
'17w16b': 323,
|
||||
'17w17a': 324,
|
||||
'17w17b': 325,
|
||||
'17w18a': 326,
|
||||
'17w18b': 327,
|
||||
'1.12-pre1': 328,
|
||||
'1.12-pre2': 329,
|
||||
'1.12-pre3': 330,
|
||||
'1.12-pre4': 331,
|
||||
'1.12-pre5': 332,
|
||||
'1.12-pre6': 333,
|
||||
'1.12-pre7': 334,
|
||||
'1.12': 335,
|
||||
'17w31a': 336,
|
||||
'1.12.1-pre1': 337,
|
||||
'1.12.1': 338,
|
||||
'1.12.2-pre1': 339,
|
||||
'1.12.2-pre2': 339,
|
||||
'1.12.2': 340,
|
||||
'17w43a': 341,
|
||||
'17w43b': 342,
|
||||
'17w45a': 343,
|
||||
'17w45b': 344,
|
||||
'17w46a': 345,
|
||||
'17w47a': 346,
|
||||
'17w47b': 347,
|
||||
'17w48a': 348,
|
||||
'17w49a': 349,
|
||||
'17w49b': 350,
|
||||
'17w50a': 351,
|
||||
'18w01a': 352,
|
||||
'18w02a': 353,
|
||||
'18w03a': 354,
|
||||
'18w03b': 355,
|
||||
'18w05a': 356,
|
||||
'18w06a': 357,
|
||||
'18w07a': 358,
|
||||
'18w07b': 359,
|
||||
'18w07c': 360,
|
||||
'18w08a': 361,
|
||||
'18w08b': 362,
|
||||
'18w09a': 363,
|
||||
'18w10a': 364,
|
||||
'18w10b': 365,
|
||||
'18w10c': 366,
|
||||
'18w10d': 367,
|
||||
'18w11a': 368,
|
||||
'18w14a': 369,
|
||||
'18w14b': 370,
|
||||
'18w15a': 371,
|
||||
'18w16a': 372,
|
||||
'18w19a': 373,
|
||||
'18w19b': 374,
|
||||
'18w20a': 375,
|
||||
'18w20b': 376,
|
||||
'18w20c': 377,
|
||||
'18w21a': 378,
|
||||
'18w21b': 379,
|
||||
'18w22a': 380,
|
||||
'18w22b': 381,
|
||||
'18w22c': 382,
|
||||
'1.13-pre1': 383,
|
||||
'1.13-pre2': 384,
|
||||
'1.13-pre3': 385,
|
||||
'1.13-pre4': 386,
|
||||
'1.13-pre5': 387,
|
||||
'1.13-pre6': 388,
|
||||
'1.13-pre7': 389,
|
||||
'1.13-pre8': 390,
|
||||
'1.13-pre9': 391,
|
||||
'1.13-pre10': 392,
|
||||
'1.13': 393,
|
||||
'1.8': 47,
|
||||
'1.8.1': 47,
|
||||
'1.8.2': 47,
|
||||
'1.8.3': 47,
|
||||
'1.8.4': 47,
|
||||
'1.8.5': 47,
|
||||
'1.8.6': 47,
|
||||
'1.8.7': 47,
|
||||
'1.8.8': 47,
|
||||
'1.8.9': 47,
|
||||
'1.9': 107,
|
||||
'1.9.1': 108,
|
||||
'1.9.2': 109,
|
||||
'1.9.3': 110,
|
||||
'1.9.4': 110,
|
||||
'1.10': 210,
|
||||
'1.10.1': 210,
|
||||
'1.10.2': 210,
|
||||
'16w32a': 301,
|
||||
'16w32b': 302,
|
||||
'16w33a': 303,
|
||||
'16w35a': 304,
|
||||
'16w36a': 305,
|
||||
'16w38a': 306,
|
||||
'16w39a': 307,
|
||||
'16w39b': 308,
|
||||
'16w39c': 309,
|
||||
'16w40a': 310,
|
||||
'16w41a': 311,
|
||||
'16w42a': 312,
|
||||
'16w43a': 313,
|
||||
'16w44a': 313,
|
||||
'1.11-pre1': 314,
|
||||
'1.11': 315,
|
||||
'16w50a': 316,
|
||||
'1.11.1': 316,
|
||||
'1.11.2': 316,
|
||||
'17w06a': 317,
|
||||
'17w13a': 318,
|
||||
'17w13b': 319,
|
||||
'17w14a': 320,
|
||||
'17w15a': 321,
|
||||
'17w16a': 322,
|
||||
'17w16b': 323,
|
||||
'17w17a': 324,
|
||||
'17w17b': 325,
|
||||
'17w18a': 326,
|
||||
'17w18b': 327,
|
||||
'1.12-pre1': 328,
|
||||
'1.12-pre2': 329,
|
||||
'1.12-pre3': 330,
|
||||
'1.12-pre4': 331,
|
||||
'1.12-pre5': 332,
|
||||
'1.12-pre6': 333,
|
||||
'1.12-pre7': 334,
|
||||
'1.12': 335,
|
||||
'17w31a': 336,
|
||||
'1.12.1-pre1': 337,
|
||||
'1.12.1': 338,
|
||||
'1.12.2-pre1': 339,
|
||||
'1.12.2-pre2': 339,
|
||||
'1.12.2': 340,
|
||||
'17w43a': 341,
|
||||
'17w43b': 342,
|
||||
'17w45a': 343,
|
||||
'17w45b': 344,
|
||||
'17w46a': 345,
|
||||
'17w47a': 346,
|
||||
'17w47b': 347,
|
||||
'17w48a': 348,
|
||||
'17w49a': 349,
|
||||
'17w49b': 350,
|
||||
'17w50a': 351,
|
||||
'18w01a': 352,
|
||||
'18w02a': 353,
|
||||
'18w03a': 354,
|
||||
'18w03b': 355,
|
||||
'18w05a': 356,
|
||||
'18w06a': 357,
|
||||
'18w07a': 358,
|
||||
'18w07b': 359,
|
||||
'18w07c': 360,
|
||||
'18w08a': 361,
|
||||
'18w08b': 362,
|
||||
'18w09a': 363,
|
||||
'18w10a': 364,
|
||||
'18w10b': 365,
|
||||
'18w10c': 366,
|
||||
'18w10d': 367,
|
||||
'18w11a': 368,
|
||||
'18w14a': 369,
|
||||
'18w14b': 370,
|
||||
'18w15a': 371,
|
||||
'18w16a': 372,
|
||||
'18w19a': 373,
|
||||
'18w19b': 374,
|
||||
'18w20a': 375,
|
||||
'18w20b': 376,
|
||||
'18w20c': 377,
|
||||
'18w21a': 378,
|
||||
'18w21b': 379,
|
||||
'18w22a': 380,
|
||||
'18w22b': 381,
|
||||
'18w22c': 382,
|
||||
'1.13-pre1': 383,
|
||||
'1.13-pre2': 384,
|
||||
'1.13-pre3': 385,
|
||||
'1.13-pre4': 386,
|
||||
'1.13-pre5': 387,
|
||||
'1.13-pre6': 388,
|
||||
'1.13-pre7': 389,
|
||||
'1.13-pre8': 390,
|
||||
'1.13-pre9': 391,
|
||||
'1.13-pre10': 392,
|
||||
'1.13': 393,
|
||||
'18w30a': 394,
|
||||
'18w30b': 395,
|
||||
'18w31a': 396,
|
||||
'18w32a': 397,
|
||||
'18w33a': 398,
|
||||
'1.13.1-pre1': 399,
|
||||
'1.13.1-pre2': 400,
|
||||
'1.13.1': 401,
|
||||
'1.13.2-pre1': 402,
|
||||
'1.13.2-pre2': 403,
|
||||
'1.13.2': 404,
|
||||
'18w43a': 441,
|
||||
'18w43b': 441,
|
||||
'18w43c': 442,
|
||||
'18w44a': 443,
|
||||
'18w45a': 444,
|
||||
'18w46a': 445,
|
||||
'18w47a': 446,
|
||||
'18w47b': 447,
|
||||
'18w48a': 448,
|
||||
'18w48b': 449,
|
||||
'18w49a': 450,
|
||||
'18w50a': 451,
|
||||
'19w02a': 452,
|
||||
'19w03a': 453,
|
||||
'19w03b': 454,
|
||||
'19w03c': 455,
|
||||
'19w04a': 456,
|
||||
'19w04b': 457,
|
||||
'19w05a': 458,
|
||||
'19w06a': 459,
|
||||
'19w07a': 460,
|
||||
'19w08a': 461,
|
||||
'19w08b': 462,
|
||||
'19w09a': 463,
|
||||
'19w11a': 464,
|
||||
'19w11b': 465,
|
||||
'19w12a': 466,
|
||||
'19w12b': 467,
|
||||
'19w13a': 468,
|
||||
'19w13b': 469,
|
||||
'19w14a': 470,
|
||||
'19w14b': 471,
|
||||
'1.14 Pre-Release 1': 472,
|
||||
'1.14 Pre-Release 2': 473,
|
||||
'1.14 Pre-Release 3': 474,
|
||||
'1.14 Pre-Release 4': 475,
|
||||
'1.14 Pre-Release 5': 476,
|
||||
'1.14': 477,
|
||||
}
|
||||
|
||||
SUPPORTED_PROTOCOL_VERSIONS = \
|
||||
|
@ -1,5 +1,6 @@
|
||||
import requests
|
||||
import json
|
||||
import uuid
|
||||
from .exceptions import YggdrasilError
|
||||
|
||||
#: The base url for Ygdrassil requests
|
||||
@ -84,7 +85,7 @@ class AuthenticationToken(object):
|
||||
|
||||
return True
|
||||
|
||||
def authenticate(self, username, password):
|
||||
def authenticate(self, username, password, invalidate_previous=False):
|
||||
"""
|
||||
Authenticates the user against https://authserver.mojang.com using
|
||||
`username` and `password` parameters.
|
||||
@ -93,6 +94,8 @@ class AuthenticationToken(object):
|
||||
username - An `str` object with the username (unmigrated accounts)
|
||||
or email address for a Mojang account.
|
||||
password - An `str` object with the password.
|
||||
invalidate_previous - A `bool`. When `True`, invalidate
|
||||
all previously acquired `access_token`s across all clients.
|
||||
|
||||
Returns:
|
||||
Returns `True` if successful.
|
||||
@ -110,6 +113,12 @@ class AuthenticationToken(object):
|
||||
"password": password
|
||||
}
|
||||
|
||||
if not invalidate_previous:
|
||||
# Include a `client_token` in the payload to prevent existing
|
||||
# `access_token`s from being invalidated. If `self.client_token`
|
||||
# is `None` generate a `client_token` using uuid4
|
||||
payload["clientToken"] = self.client_token or uuid.uuid4().hex
|
||||
|
||||
res = _make_request(AUTH_SERVER, "authenticate", payload)
|
||||
|
||||
_raise_from_response(res)
|
||||
@ -269,7 +278,7 @@ def _make_request(server, endpoint, data):
|
||||
A `requests.Request` object.
|
||||
"""
|
||||
res = requests.post(server + "/" + endpoint, data=json.dumps(data),
|
||||
headers=HEADERS)
|
||||
headers=HEADERS, timeout=15)
|
||||
return res
|
||||
|
||||
|
||||
|
@ -352,6 +352,8 @@ class Connection(object):
|
||||
self.socket = socket.socket(ai_faml, ai_type, ai_prot)
|
||||
self.socket.connect(ai_addr)
|
||||
self.file_object = self.socket.makefile("rb", 0)
|
||||
self.options.compression_enabled = False
|
||||
self.options.compression_threshold = -1
|
||||
self.connected = True
|
||||
|
||||
def disconnect(self, immediate=False):
|
||||
@ -500,7 +502,7 @@ class NetworkingThread(threading.Thread):
|
||||
|
||||
# Ignore the earlier exception if a disconnect packet is
|
||||
# received, as it may have been caused by trying to write to
|
||||
# thw closed socket, which does not represent a program error.
|
||||
# the closed socket, which does not represent a program error.
|
||||
if exc_info is not None and packet.packet_name == "disconnect":
|
||||
exc_info = None
|
||||
|
||||
|
@ -37,6 +37,7 @@ def get_packets(context):
|
||||
MultiBlockChangePacket,
|
||||
RespawnPacket,
|
||||
PluginMessagePacket,
|
||||
PlayerListHeaderAndFooterPacket,
|
||||
}
|
||||
if context.protocol_version <= 47:
|
||||
packets |= {
|
||||
@ -48,7 +49,8 @@ def get_packets(context):
|
||||
class KeepAlivePacket(AbstractKeepAlivePacket):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x21 if context.protocol_version >= 389 else \
|
||||
return 0x20 if context.protocol_version >= 471 else \
|
||||
0x21 if context.protocol_version >= 389 else \
|
||||
0x20 if context.protocol_version >= 345 else \
|
||||
0x1F if context.protocol_version >= 332 else \
|
||||
0x20 if context.protocol_version >= 318 else \
|
||||
@ -71,10 +73,11 @@ class JoinGamePacket(Packet):
|
||||
{'entity_id': Integer},
|
||||
{'game_mode': UnsignedByte},
|
||||
{'dimension': Integer if context.protocol_version >= 108 else Byte},
|
||||
{'difficulty': UnsignedByte},
|
||||
{'difficulty': UnsignedByte} if context.protocol_version < 464 else {},
|
||||
{'max_players': UnsignedByte},
|
||||
{'level_type': String},
|
||||
{'reduced_debug_info': Boolean}
|
||||
{'render_distance': VarInt} if context.protocol_version >= 468 else {},
|
||||
{'reduced_debug_info': Boolean},
|
||||
])
|
||||
|
||||
# JoinGamePacket.Difficulty is an alias for Difficulty
|
||||
@ -127,7 +130,8 @@ class ChatMessagePacket(Packet):
|
||||
class DisconnectPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x1B if context.protocol_version >= 345 else \
|
||||
return 0x1A if context.protocol_version >= 471 else \
|
||||
0x1B if context.protocol_version >= 345 else \
|
||||
0x1A if context.protocol_version >= 332 else \
|
||||
0x1B if context.protocol_version >= 318 else \
|
||||
0x1A if context.protocol_version >= 107 else \
|
||||
@ -173,7 +177,10 @@ class SpawnPlayerPacket(Packet):
|
||||
class EntityVelocityPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x41 if context.protocol_version >= 389 else \
|
||||
return 0x45 if context.protocol_version >= 471 else \
|
||||
0x41 if context.protocol_version >= 461 else \
|
||||
0x42 if context.protocol_version >= 451 else \
|
||||
0x41 if context.protocol_version >= 389 else \
|
||||
0x40 if context.protocol_version >= 352 else \
|
||||
0x3F if context.protocol_version >= 345 else \
|
||||
0x3E if context.protocol_version >= 336 else \
|
||||
@ -195,7 +202,10 @@ class EntityVelocityPacket(Packet):
|
||||
class UpdateHealthPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x44 if context.protocol_version >= 389 else \
|
||||
return 0x48 if context.protocol_version >= 471 else \
|
||||
0x44 if context.protocol_version >= 461 else \
|
||||
0x45 if context.protocol_version >= 451 else \
|
||||
0x44 if context.protocol_version >= 389 else \
|
||||
0x43 if context.protocol_version >= 352 else \
|
||||
0x42 if context.protocol_version >= 345 else \
|
||||
0x41 if context.protocol_version >= 336 else \
|
||||
@ -246,8 +256,29 @@ class RespawnPacket(Packet):
|
||||
class PluginMessagePacket(AbstractPluginMessagePacket):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x19 if context.protocol_version >= 345 else \
|
||||
return 0x18 if context.protocol_version >= 471 else \
|
||||
0x19 if context.protocol_version >= 345 else \
|
||||
0x18 if context.protocol_version >= 332 else \
|
||||
0x19 if context.protocol_version >= 318 else \
|
||||
0x18 if context.protocol_version >= 70 else \
|
||||
0x3F
|
||||
|
||||
|
||||
class PlayerListHeaderAndFooterPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x53 if context.protocol_version >= 471 else \
|
||||
0x5F if context.protocol_version >= 461 else \
|
||||
0x50 if context.protocol_version >= 451 else \
|
||||
0x4F if context.protocol_version >= 441 else \
|
||||
0x4E if context.protocol_version >= 393 else \
|
||||
0x4A if context.protocol_version >= 338 else \
|
||||
0x49 if context.protocol_version >= 335 else \
|
||||
0x47 if context.protocol_version >= 110 else \
|
||||
0x48 if context.protocol_version >= 107 else \
|
||||
0x47
|
||||
|
||||
packet_name = 'player list header and footer'
|
||||
definition = [
|
||||
{'header': String},
|
||||
{'footer': String}]
|
||||
|
@ -8,7 +8,9 @@ from minecraft.networking.types import (
|
||||
class CombatEventPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x2F if context.protocol_version >= 389 else \
|
||||
return 0x32 if context.protocol_version >= 471 else \
|
||||
0x30 if context.protocol_version >= 451 else \
|
||||
0x2F if context.protocol_version >= 389 else \
|
||||
0x2E if context.protocol_version >= 345 else \
|
||||
0x2D if context.protocol_version >= 336 else \
|
||||
0x2C if context.protocol_version >= 332 else \
|
||||
|
@ -5,7 +5,8 @@ from minecraft.networking.packets import Packet
|
||||
class ExplosionPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x1E if context.protocol_version >= 389 else \
|
||||
return 0x1C if context.protocol_version >= 471 else \
|
||||
0x1E if context.protocol_version >= 389 else \
|
||||
0x1D if context.protocol_version >= 345 else \
|
||||
0x1C if context.protocol_version >= 332 else \
|
||||
0x1D if context.protocol_version >= 318 else \
|
||||
|
@ -28,7 +28,7 @@ class MapPacket(Packet):
|
||||
|
||||
class Map(MutableRecord):
|
||||
__slots__ = ('id', 'scale', 'icons', 'pixels', 'width', 'height',
|
||||
'is_tracking_position')
|
||||
'is_tracking_position', 'is_locked')
|
||||
|
||||
def __init__(self, id=None, scale=None, width=128, height=128):
|
||||
self.id = id
|
||||
@ -38,6 +38,7 @@ class MapPacket(Packet):
|
||||
self.height = height
|
||||
self.pixels = bytearray(0 for i in range(width*height))
|
||||
self.is_tracking_position = True
|
||||
self.is_locked = False
|
||||
|
||||
class MapSet(object):
|
||||
__slots__ = 'maps_by_id'
|
||||
@ -58,6 +59,11 @@ class MapPacket(Packet):
|
||||
else:
|
||||
self.is_tracking_position = True
|
||||
|
||||
if self.context.protocol_version >= 452:
|
||||
self.is_locked = Boolean.read(file_object)
|
||||
else:
|
||||
self.is_locked = False
|
||||
|
||||
icon_count = VarInt.read(file_object)
|
||||
self.icons = []
|
||||
for i in range(icon_count):
|
||||
@ -99,6 +105,7 @@ class MapPacket(Packet):
|
||||
z = self.offset[1] + i // self.width
|
||||
map.pixels[x + map.width * z] = self.pixels[i]
|
||||
map.is_tracking_position = self.is_tracking_position
|
||||
map.is_locked = self.is_locked
|
||||
|
||||
def apply_to_map_set(self, map_set):
|
||||
map = map_set.maps_by_id.get(self.map_id)
|
||||
|
@ -8,7 +8,9 @@ from minecraft.networking.types import (
|
||||
class PlayerListItemPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x30 if context.protocol_version >= 389 else \
|
||||
return 0x33 if context.protocol_version >= 471 else \
|
||||
0x31 if context.protocol_version >= 451 else \
|
||||
0x30 if context.protocol_version >= 389 else \
|
||||
0x2F if context.protocol_version >= 345 else \
|
||||
0x2E if context.protocol_version >= 336 else \
|
||||
0x2D if context.protocol_version >= 332 else \
|
||||
|
@ -8,7 +8,9 @@ from minecraft.networking.types import (
|
||||
class PlayerPositionAndLookPacket(Packet, BitFieldEnum):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x32 if context.protocol_version >= 389 else \
|
||||
return 0x35 if context.protocol_version >= 471 else \
|
||||
0x33 if context.protocol_version >= 451 else \
|
||||
0x32 if context.protocol_version >= 389 else \
|
||||
0x31 if context.protocol_version >= 352 else \
|
||||
0x30 if context.protocol_version >= 345 else \
|
||||
0x2F if context.protocol_version >= 336 else \
|
||||
@ -29,7 +31,7 @@ class PlayerPositionAndLookPacket(Packet, BitFieldEnum):
|
||||
])
|
||||
|
||||
field_enum = classmethod(
|
||||
lambda cls, field: cls if field == 'flags' else None)
|
||||
lambda cls, field, context: cls if field == 'flags' else None)
|
||||
|
||||
FLAG_REL_X = 0x01
|
||||
FLAG_REL_Y = 0x02
|
||||
|
@ -1,4 +1,5 @@
|
||||
from minecraft.networking.packets import Packet
|
||||
from minecraft.networking.types.utility import descriptor
|
||||
|
||||
from minecraft.networking.types import (
|
||||
VarInt, UUID, Byte, Double, Integer, UnsignedByte, Short, Enum, Vector,
|
||||
@ -14,40 +15,89 @@ class SpawnObjectPacket(Packet):
|
||||
|
||||
packet_name = 'spawn object'
|
||||
|
||||
class EntityType(Enum):
|
||||
BOAT = 1
|
||||
ITEM_STACK = 2
|
||||
AREA_EFFECT_CLOUD = 3
|
||||
MINECART = 10
|
||||
ACTIVATED_TNT = 50
|
||||
ENDERCRYSTAL = 51
|
||||
ARROW = 60
|
||||
SNOWBALL = 61
|
||||
EGG = 62
|
||||
FIREBALL = 63
|
||||
FIRECHARGE = 64
|
||||
ENDERPERL = 65
|
||||
WITHER_SKULL = 66
|
||||
SHULKER_BULLET = 67
|
||||
LLAMA_SPIT = 68
|
||||
FALLING_OBJECT = 70
|
||||
ITEM_FRAMES = 71
|
||||
EYE_OF_ENDER = 72
|
||||
POTION = 73
|
||||
EXP_BOTTLE = 75
|
||||
FIREWORK_ROCKET = 76
|
||||
LEASH_KNOT = 77
|
||||
ARMORSTAND = 78
|
||||
EVOCATION_FANGS = 79
|
||||
FISHING_HOOK = 90
|
||||
SPECTRAL_ARROW = 91
|
||||
DRAGON_FIREBALL = 93
|
||||
fields = ('entity_id', 'object_uuid', 'type_id',
|
||||
'x', 'y', 'z', 'pitch', 'yaw')
|
||||
|
||||
@descriptor
|
||||
def EntityType(desc, self, cls): # pylint: disable=no-self-argument
|
||||
if self is None:
|
||||
# EntityType is being accessed as a class attribute.
|
||||
raise AttributeError(
|
||||
'This interface is deprecated:\n\n'
|
||||
'As of pyCraft\'s support for Minecraft 1.14, the nested '
|
||||
'class "SpawnObjectPacket.EntityType" cannot be accessed as a '
|
||||
'class attribute, because it depends on the protocol version. '
|
||||
'There are two ways to access the correct version of the '
|
||||
'class:\n\n'
|
||||
'1. Access the "EntityType" attribute of a '
|
||||
'"SpawnObjectPacket" instance with its "context" property '
|
||||
'set.\n\n'
|
||||
'2. Call "SpawnObjectPacket.field_enum(\'type_id\', '
|
||||
'context)".')
|
||||
else:
|
||||
# EntityType is being accessed as an instance attribute.
|
||||
return self.field_enum('type_id', self.context)
|
||||
|
||||
@classmethod
|
||||
def field_enum(cls, field, context):
|
||||
if field != 'type_id' or context is None:
|
||||
return
|
||||
|
||||
pv = context.protocol_version
|
||||
name = 'EntityType_%d' % pv
|
||||
if hasattr(cls, name):
|
||||
return getattr(cls, name)
|
||||
|
||||
class EntityType(Enum):
|
||||
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
|
||||
ARROW = 60 if pv < 458 else 2
|
||||
BOAT = 1 if pv < 458 else 5
|
||||
DRAGON_FIREBALL = 93 if pv < 458 else 13
|
||||
EGG = 62 if pv < 458 else 74 # ThrownEgg
|
||||
ENDERCRYSTAL = 51 if pv < 458 else 16
|
||||
ENDERPEARL = 65 if pv < 458 else 75 # ThrownEnderpearl
|
||||
EVOCATION_FANGS = 79 if pv < 458 else 20
|
||||
EXP_BOTTLE = 75 if pv < 458 else 76 # ThrownExpBottle
|
||||
EYE_OF_ENDER = 72 if pv < 458 else 23 # EyeOfEnderSignal
|
||||
FALLING_OBJECT = 70 if pv < 458 else 24 # FallingSand
|
||||
FIREBALL = 63 if pv < 458 else 34 # Fireball (ghast)
|
||||
FIRECHARGE = 64 if pv < 458 else 65 # SmallFireball (blaze)
|
||||
FIREWORK_ROCKET = 76 if pv < 458 else 25 # FireworksRocketEntity
|
||||
FISHING_HOOK = 90 if pv < 458 else 93 # Fishing bobber
|
||||
ITEM_FRAMES = 71 if pv < 458 else 33 # ItemFrame
|
||||
ITEM_STACK = 2 if pv < 458 else 32 # Item
|
||||
LEASH_KNOT = 77 if pv < 458 else 35
|
||||
LLAMA_SPIT = 68 if pv < 458 else 37
|
||||
MINECART = 10 if pv < 458 else 39 # MinecartRideable
|
||||
POTION = 73 if pv < 458 else 77 # ThrownPotion
|
||||
SHULKER_BULLET = 67 if pv < 458 else 60
|
||||
SNOWBALL = 61 if pv < 458 else 67
|
||||
SPECTRAL_ARROW = 91 if pv < 458 else 68
|
||||
WITHER_SKULL = 66 if pv < 458 else 85
|
||||
if pv >= 393:
|
||||
TRIDENT = 94
|
||||
if pv >= 458:
|
||||
MINECART_CHEST = 40
|
||||
MINECART_COMMAND_BLOCK = 41
|
||||
MINECART_FURNACE = 42
|
||||
MINECART_HOPPER = 43
|
||||
MINECART_SPAWNER = 44
|
||||
MINECART_TNT = 45
|
||||
|
||||
setattr(cls, name, EntityType)
|
||||
return EntityType
|
||||
|
||||
def read(self, file_object):
|
||||
self.entity_id = VarInt.read(file_object)
|
||||
if self.context.protocol_version >= 49:
|
||||
self.object_uuid = UUID.read(file_object)
|
||||
self.type_id = Byte.read(file_object)
|
||||
|
||||
if self.context.protocol_version >= 458:
|
||||
self.type_id = VarInt.read(file_object)
|
||||
else:
|
||||
self.type_id = Byte.read(file_object)
|
||||
|
||||
xyz_type = Double if self.context.protocol_version >= 100 else Integer
|
||||
for attr in 'x', 'y', 'z':
|
||||
@ -64,7 +114,11 @@ class SpawnObjectPacket(Packet):
|
||||
VarInt.send(self.entity_id, packet_buffer)
|
||||
if self.context.protocol_version >= 49:
|
||||
UUID.send(self.object_uuid, packet_buffer)
|
||||
Byte.send(self.type_id, packet_buffer)
|
||||
|
||||
if self.context.protocol_version >= 458:
|
||||
VarInt.send(self.type_id, packet_buffer)
|
||||
else:
|
||||
Byte.send(self.type_id, packet_buffer)
|
||||
|
||||
xyz_type = Double if self.context.protocol_version >= 100 else Integer
|
||||
for coord in self.x, self.y, self.z:
|
||||
@ -78,9 +132,20 @@ class SpawnObjectPacket(Packet):
|
||||
Short.send(coord, packet_buffer)
|
||||
|
||||
# Access the entity type as a string, according to the EntityType enum.
|
||||
@property
|
||||
def type(self):
|
||||
if self.context is None:
|
||||
raise ValueError('This packet must have a non-None "context" '
|
||||
'in order to read the "type" property.')
|
||||
# pylint: disable=no-member
|
||||
return self.EntityType.name_from_value(self.type_id)
|
||||
|
||||
@type.setter
|
||||
def type(self, type_name):
|
||||
if self.context is None:
|
||||
raise ValueError('This packet must have a non-None "context" '
|
||||
'in order to set the "type" property.')
|
||||
self.type_id = getattr(self.EntityType, type_name)
|
||||
type = property(lambda p: p.EntityType.name_from_value(p.type_id), type)
|
||||
|
||||
# Access the fields 'x', 'y', 'z' as a Vector.
|
||||
def position(self, position):
|
||||
|
@ -61,7 +61,7 @@ class Packet(object):
|
||||
def read(self, file_object):
|
||||
for field in self.definition:
|
||||
for var_name, data_type in field.items():
|
||||
value = data_type.read(file_object)
|
||||
value = data_type.read_with_context(file_object, self.context)
|
||||
setattr(self, var_name, value)
|
||||
|
||||
# Writes a packet buffer to the socket with the appropriate headers
|
||||
@ -104,7 +104,7 @@ class Packet(object):
|
||||
for field in self.definition:
|
||||
for var_name, data_type in field.items():
|
||||
data = getattr(self, var_name)
|
||||
data_type.send(data, packet_buffer)
|
||||
data_type.send_with_context(data, packet_buffer, self.context)
|
||||
|
||||
def __repr__(self):
|
||||
str = type(self).__name__
|
||||
@ -119,6 +119,8 @@ class Packet(object):
|
||||
@property
|
||||
def fields(self):
|
||||
""" An iterable of the names of the packet's fields, or None. """
|
||||
if self.definition is None:
|
||||
return None
|
||||
return (field for defn in self.definition for field in defn)
|
||||
|
||||
def field_string(self, field):
|
||||
@ -127,7 +129,7 @@ class Packet(object):
|
||||
"""
|
||||
value = getattr(self, field, None)
|
||||
|
||||
enum_class = self.field_enum(field)
|
||||
enum_class = self.field_enum(field, self.context)
|
||||
if enum_class is not None:
|
||||
name = enum_class.name_from_value(value)
|
||||
if name is not None:
|
||||
@ -136,7 +138,7 @@ class Packet(object):
|
||||
return repr(value)
|
||||
|
||||
@classmethod
|
||||
def field_enum(cls, field):
|
||||
def field_enum(cls, field, context=None):
|
||||
""" The subclass of 'minecraft.networking.types.Enum' associated with
|
||||
this field, or None if there is no such class.
|
||||
"""
|
||||
|
@ -32,7 +32,9 @@ def get_packets(context):
|
||||
class KeepAlivePacket(AbstractKeepAlivePacket):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x0E if context.protocol_version >= 389 else \
|
||||
return 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 \
|
||||
0x0B if context.protocol_version >= 345 else \
|
||||
0x0A if context.protocol_version >= 343 else \
|
||||
@ -45,7 +47,8 @@ class KeepAlivePacket(AbstractKeepAlivePacket):
|
||||
class ChatPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x02 if context.protocol_version >= 389 else \
|
||||
return 0x03 if context.protocol_version >= 464 else \
|
||||
0x02 if context.protocol_version >= 389 else \
|
||||
0x01 if context.protocol_version >= 343 else \
|
||||
0x02 if context.protocol_version >= 336 else \
|
||||
0x03 if context.protocol_version >= 318 else \
|
||||
@ -70,7 +73,9 @@ class ChatPacket(Packet):
|
||||
class PositionAndLookPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x11 if context.protocol_version >= 389 else \
|
||||
return 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 \
|
||||
0x0E if context.protocol_version >= 345 else \
|
||||
0x0D if context.protocol_version >= 343 else \
|
||||
@ -101,7 +106,9 @@ class TeleportConfirmPacket(Packet):
|
||||
class AnimationPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x27 if context.protocol_version >= 389 else \
|
||||
return 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 \
|
||||
0x1D if context.protocol_version >= 345 else \
|
||||
0x1C if context.protocol_version >= 343 else \
|
||||
@ -121,7 +128,8 @@ class AnimationPacket(Packet):
|
||||
class ClientStatusPacket(Packet, Enum):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x03 if context.protocol_version >= 389 else \
|
||||
return 0x04 if context.protocol_version >= 464 else \
|
||||
0x03 if context.protocol_version >= 389 else \
|
||||
0x02 if context.protocol_version >= 343 else \
|
||||
0x03 if context.protocol_version >= 336 else \
|
||||
0x04 if context.protocol_version >= 318 else \
|
||||
@ -134,7 +142,7 @@ class ClientStatusPacket(Packet, Enum):
|
||||
get_definition = staticmethod(lambda context: [
|
||||
{'action_id': VarInt}])
|
||||
field_enum = classmethod(
|
||||
lambda cls, field: cls if field == 'action_id' else None)
|
||||
lambda cls, field, context: cls if field == 'action_id' else None)
|
||||
|
||||
RESPAWN = 0
|
||||
REQUEST_STATS = 1
|
||||
@ -145,7 +153,8 @@ class ClientStatusPacket(Packet, Enum):
|
||||
class PluginMessagePacket(AbstractPluginMessagePacket):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x0A if context.protocol_version >= 389 else \
|
||||
return 0x0B if context.protocol_version >= 464 else \
|
||||
0x0A if context.protocol_version >= 389 else \
|
||||
0x09 if context.protocol_version >= 345 else \
|
||||
0x08 if context.protocol_version >= 343 else \
|
||||
0x09 if context.protocol_version >= 336 else \
|
||||
@ -170,7 +179,9 @@ class PlayerBlockPlacementPacket(Packet):
|
||||
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x29 if context.protocol_version >= 389 else \
|
||||
return 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 \
|
||||
0x1F if context.protocol_version >= 345 else \
|
||||
0x1E if context.protocol_version >= 343 else \
|
||||
@ -184,12 +195,15 @@ class PlayerBlockPlacementPacket(Packet):
|
||||
@staticmethod
|
||||
def get_definition(context):
|
||||
return [
|
||||
{'hand': VarInt} if context.protocol_version >= 453 else {},
|
||||
{'location': Position},
|
||||
{'face': VarInt if context.protocol_version >= 69 else Byte},
|
||||
{'hand': VarInt},
|
||||
{'hand': VarInt} if context.protocol_version < 453 else {},
|
||||
{'x': Float if context.protocol_version >= 309 else Byte},
|
||||
{'y': Float if context.protocol_version >= 309 else Byte},
|
||||
{'z': Float if context.protocol_version >= 309 else Byte},
|
||||
({'inside_block': Boolean}
|
||||
if context.protocol_version >= 453 else {}),
|
||||
]
|
||||
|
||||
# PlayerBlockPlacementPacket.Hand is an alias for RelativeHand.
|
||||
|
@ -8,7 +8,8 @@ from minecraft.networking.types import (
|
||||
class ClientSettingsPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x04 if context.protocol_version >= 389 else \
|
||||
return 0x05 if context.protocol_version >= 464 else \
|
||||
0x04 if context.protocol_version >= 389 else \
|
||||
0x03 if context.protocol_version >= 343 else \
|
||||
0x04 if context.protocol_version >= 336 else \
|
||||
0x05 if context.protocol_version >= 318 else \
|
||||
@ -26,7 +27,7 @@ class ClientSettingsPacket(Packet):
|
||||
{'main_hand': VarInt} if context.protocol_version > 49 else {}])
|
||||
|
||||
field_enum = classmethod(
|
||||
lambda cls, field: {
|
||||
lambda cls, field, context: {
|
||||
'chat_mode': cls.ChatMode,
|
||||
'displayed_skin_parts': cls.SkinParts,
|
||||
'main_hand': AbsoluteHand,
|
||||
|
@ -20,13 +20,31 @@ __all__ = (
|
||||
class Type(object):
|
||||
__slots__ = ()
|
||||
|
||||
@staticmethod
|
||||
def read(file_object):
|
||||
raise NotImplementedError("Base data type not serializable")
|
||||
@classmethod
|
||||
def read_with_context(cls, file_object, _context):
|
||||
return cls.read(file_object)
|
||||
|
||||
@staticmethod
|
||||
def send(value, socket):
|
||||
raise NotImplementedError("Base data type not serializable")
|
||||
@classmethod
|
||||
def send_with_context(cls, value, socket, _context):
|
||||
return cls.send(value, socket)
|
||||
|
||||
@classmethod
|
||||
def read(cls, file_object):
|
||||
if cls.read_with_context == Type.read_with_context:
|
||||
raise NotImplementedError('One of "read" or "read_with_context" '
|
||||
'must be overridden in a subclass.')
|
||||
else:
|
||||
raise TypeError('This type requires a ConnectionContext: '
|
||||
'call "read_with_context" instead of "read".')
|
||||
|
||||
@classmethod
|
||||
def send(cls, value, socket):
|
||||
if cls.send_with_context == Type.send_with_context:
|
||||
raise NotImplementedError('One of "send" or "send_with_context" '
|
||||
'must be overridden in a subclass.')
|
||||
else:
|
||||
raise TypeError('This type requires a ConnectionContext: '
|
||||
'call "send_with_context" instead of "send".')
|
||||
|
||||
|
||||
class Boolean(Type):
|
||||
@ -263,11 +281,16 @@ class Position(Type, Vector):
|
||||
__slots__ = ()
|
||||
|
||||
@staticmethod
|
||||
def read(file_object):
|
||||
def read_with_context(file_object, context):
|
||||
location = UnsignedLong.read(file_object)
|
||||
x = int(location >> 38)
|
||||
y = int((location >> 26) & 0xFFF)
|
||||
z = int(location & 0x3FFFFFF)
|
||||
x = int(location >> 38) # 26 most significant bits
|
||||
|
||||
if context.protocol_version >= 443:
|
||||
z = int((location >> 12) & 0x3FFFFFF) # 26 intermediate bits
|
||||
y = int(location & 0xFFF) # 12 least signficant bits
|
||||
else:
|
||||
y = int((location >> 26) & 0xFFF) # 12 intermediate bits
|
||||
z = int(location & 0x3FFFFFF) # 26 least significant bits
|
||||
|
||||
if x >= pow(2, 25):
|
||||
x -= pow(2, 26)
|
||||
@ -281,8 +304,10 @@ class Position(Type, Vector):
|
||||
return Position(x=x, y=y, z=z)
|
||||
|
||||
@staticmethod
|
||||
def send(position, socket):
|
||||
def send_with_context(position, socket, context):
|
||||
# 'position' can be either a tuple or Position object.
|
||||
x, y, z = position
|
||||
value = ((x & 0x3FFFFFF) << 38) | ((y & 0xFFF) << 26) | (z & 0x3FFFFFF)
|
||||
value = ((x & 0x3FFFFFF) << 38 | (z & 0x3FFFFFF) << 12 | (y & 0xFFF)
|
||||
if context.protocol_version >= 443 else
|
||||
(x & 0x3FFFFFF) << 38 | (y & 0xFFF) << 26 | (z & 0x3FFFFFF))
|
||||
UnsignedLong.send(value, socket)
|
||||
|
@ -90,3 +90,50 @@ class PositionAndLook(MutableRecord):
|
||||
def look(self, look):
|
||||
self.yaw, self.pitch = look
|
||||
look = property(lambda self: (self.yaw, self.pitch), look)
|
||||
|
||||
|
||||
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.
|
||||
"""
|
||||
__slots__ = '_fget', '_fset', '_fdel'
|
||||
|
||||
def __init__(self, fget=None, fset=None, fdel=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
|
||||
|
||||
def setter(self, fset):
|
||||
self._fset = fset
|
||||
return self
|
||||
|
||||
def deleter(self, fdel):
|
||||
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")
|
||||
|
||||
@staticmethod
|
||||
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)
|
||||
|
||||
def __delete__(self, instance):
|
||||
return self._fdel(self, instance)
|
||||
|
@ -103,7 +103,7 @@ class FakeClientHandler(object):
|
||||
# Called upon entering the play state.
|
||||
self.write_packet(clientbound.play.JoinGamePacket(
|
||||
entity_id=0, game_mode=0, dimension=0, difficulty=2, max_players=1,
|
||||
level_type='default', reduced_debug_info=False))
|
||||
level_type='default', reduced_debug_info=False, render_distance=9))
|
||||
|
||||
def handle_play_packet(self, packet):
|
||||
# Called upon each packet received after handle_play_start() returns.
|
||||
@ -275,7 +275,7 @@ class FakeClientHandler(object):
|
||||
return
|
||||
assert isinstance(packet, serverbound.status.PingPacket)
|
||||
self.handle_ping(packet)
|
||||
except FakeServerDisconnect as e:
|
||||
except FakeServerDisconnect:
|
||||
pass
|
||||
|
||||
def _read_packet_buffer(self):
|
||||
@ -326,14 +326,14 @@ class FakeServer(object):
|
||||
"""
|
||||
|
||||
__slots__ = 'listen_socket', 'compression_threshold', 'context', \
|
||||
'minecraft_version', 'client_handler_type', \
|
||||
'minecraft_version', 'client_handler_type', 'server_type', \
|
||||
'packets_handshake', 'packets_login', 'packets_playing', \
|
||||
'packets_status', 'lock', 'stopping', 'private_key', \
|
||||
'public_key_bytes',
|
||||
'public_key_bytes', 'test_case',
|
||||
|
||||
def __init__(self, minecraft_version=None, compression_threshold=None,
|
||||
client_handler_type=FakeClientHandler, private_key=None,
|
||||
public_key_bytes=None):
|
||||
public_key_bytes=None, test_case=None):
|
||||
if minecraft_version is None:
|
||||
minecraft_version = VERSIONS[-1][0]
|
||||
|
||||
@ -352,6 +352,7 @@ class FakeServer(object):
|
||||
self.client_handler_type = client_handler_type
|
||||
self.private_key = private_key
|
||||
self.public_key_bytes = public_key_bytes
|
||||
self.test_case = test_case
|
||||
|
||||
self.packets_handshake = {
|
||||
p.get_id(self.context): p for p in
|
||||
@ -427,6 +428,9 @@ class _FakeServerTest(unittest.TestCase):
|
||||
# The set of Minecraft version names or protocol version numbers that the
|
||||
# client will support. If None, the client supports all possible versions.
|
||||
|
||||
server_type = FakeServer
|
||||
# A subclass of FakeServer to be used in tests.
|
||||
|
||||
client_handler_type = FakeClientHandler
|
||||
# A subclass of FakeClientHandler to be used in tests.
|
||||
|
||||
@ -464,13 +468,16 @@ class _FakeServerTest(unittest.TestCase):
|
||||
client.connect()
|
||||
|
||||
def _test_connect(self, client_versions=None, server_version=None,
|
||||
client_handler_type=None, connection_type=None,
|
||||
compression_threshold=None, private_key=None,
|
||||
public_key_bytes=None, ignore_extra_exceptions=None):
|
||||
server_type=None, client_handler_type=None,
|
||||
connection_type=None, compression_threshold=None,
|
||||
private_key=None, public_key_bytes=None,
|
||||
ignore_extra_exceptions=None):
|
||||
if client_versions is None:
|
||||
client_versions = self.client_versions
|
||||
if server_version is None:
|
||||
server_version = self.server_version
|
||||
if server_type is None:
|
||||
server_type = self.server_type
|
||||
if client_handler_type is None:
|
||||
client_handler_type = self.client_handler_type
|
||||
if connection_type is None:
|
||||
@ -484,11 +491,12 @@ class _FakeServerTest(unittest.TestCase):
|
||||
if ignore_extra_exceptions is None:
|
||||
ignore_extra_exceptions = self.ignore_extra_exceptions
|
||||
|
||||
server = FakeServer(minecraft_version=server_version,
|
||||
compression_threshold=compression_threshold,
|
||||
client_handler_type=client_handler_type,
|
||||
private_key=private_key,
|
||||
public_key_bytes=public_key_bytes)
|
||||
server = server_type(minecraft_version=server_version,
|
||||
compression_threshold=compression_threshold,
|
||||
client_handler_type=client_handler_type,
|
||||
private_key=private_key,
|
||||
public_key_bytes=public_key_bytes,
|
||||
test_case=self)
|
||||
addr = "localhost"
|
||||
port = server.listen_socket.getsockname()[1]
|
||||
|
||||
|
@ -300,6 +300,10 @@ class NormalConnectionProcedure(unittest.TestCase):
|
||||
|
||||
def mocked_make_request(server, endpoint, data):
|
||||
if endpoint == "authenticate":
|
||||
if "accessToken" in data:
|
||||
response = successful_res.copy()
|
||||
response.json["accessToken"] = data["accessToken"]
|
||||
return response
|
||||
return successful_res
|
||||
if endpoint == "refresh" and data["accessToken"] == "token":
|
||||
return successful_res
|
||||
@ -323,6 +327,9 @@ class NormalConnectionProcedure(unittest.TestCase):
|
||||
self.assertFalse(a.authenticated)
|
||||
self.assertTrue(a.authenticate("username", "password"))
|
||||
|
||||
self.assertEqual(_make_request_mock.call_count, 1)
|
||||
self.assertIn("clientToken", _make_request_mock.call_args[0][2])
|
||||
|
||||
self.assertTrue(a.authenticated)
|
||||
|
||||
self.assertTrue(a.refresh())
|
||||
@ -337,6 +344,35 @@ class NormalConnectionProcedure(unittest.TestCase):
|
||||
|
||||
self.assertEqual(_make_request_mock.call_count, 6)
|
||||
|
||||
# Test that we send a provided clientToken if the authenticationToken
|
||||
# is initialized with one
|
||||
with mock.patch("minecraft.authentication._make_request",
|
||||
side_effect=mocked_make_request) as _make_request_mock:
|
||||
a = AuthenticationToken(client_token="existing_token")
|
||||
|
||||
self.assertTrue(a.authenticate("username", "password",
|
||||
invalidate_previous=False))
|
||||
|
||||
self.assertEqual(_make_request_mock.call_count, 1)
|
||||
self.assertEqual(
|
||||
"existing_token",
|
||||
_make_request_mock.call_args[0][2]["clientToken"]
|
||||
)
|
||||
|
||||
# Test that we invalidate previous tokens properly
|
||||
with mock.patch("minecraft.authentication._make_request",
|
||||
side_effect=mocked_make_request) as _make_request_mock:
|
||||
a = AuthenticationToken()
|
||||
|
||||
self.assertFalse(a.authenticated)
|
||||
self.assertTrue(a.authenticate("username", "password",
|
||||
invalidate_previous=True))
|
||||
|
||||
self.assertTrue(a.authenticated)
|
||||
self.assertEqual(a.access_token, "token")
|
||||
self.assertEqual(_make_request_mock.call_count, 1)
|
||||
self.assertNotIn("clientToken", _make_request_mock.call_args[0][2])
|
||||
|
||||
a = AuthenticationToken(username="username",
|
||||
access_token="token",
|
||||
client_token="token")
|
||||
|
@ -32,6 +32,46 @@ class ConnectTest(fake_server._FakeServerTest):
|
||||
raise fake_server.FakeServerDisconnect
|
||||
|
||||
|
||||
class ReconnectTest(ConnectTest):
|
||||
phase = 0
|
||||
|
||||
def _start_client(self, client):
|
||||
def handle_login_disconnect(packet):
|
||||
if 'Please reconnect' in packet.json_data:
|
||||
# Override the default behaviour of raising a fatal exception.
|
||||
client.disconnect()
|
||||
client.connect()
|
||||
raise IgnorePacket
|
||||
client.register_packet_listener(
|
||||
handle_login_disconnect, clientbound.login.DisconnectPacket,
|
||||
early=True)
|
||||
|
||||
def handle_play_disconnect(packet):
|
||||
if 'Please reconnect' in packet.json_data:
|
||||
client.connect()
|
||||
elif 'Test successful' in packet.json_data:
|
||||
raise fake_server.FakeServerTestSuccess
|
||||
client.register_packet_listener(
|
||||
handle_play_disconnect, clientbound.play.DisconnectPacket)
|
||||
|
||||
client.connect()
|
||||
|
||||
class client_handler_type(fake_server.FakeClientHandler):
|
||||
def handle_login(self, packet):
|
||||
if self.server.test_case.phase == 0:
|
||||
self.server.test_case.phase = 1
|
||||
raise fake_server.FakeServerDisconnect('Please reconnect (0).')
|
||||
super(ReconnectTest.client_handler_type, self).handle_login(packet)
|
||||
|
||||
def handle_play_start(self):
|
||||
if self.server.test_case.phase == 1:
|
||||
self.server.test_case.phase = 2
|
||||
raise fake_server.FakeServerDisconnect('Please reconnect (1).')
|
||||
else:
|
||||
assert self.server.test_case.phase == 2
|
||||
raise fake_server.FakeServerDisconnect('Test successful (2).')
|
||||
|
||||
|
||||
class PingTest(ConnectTest):
|
||||
def _start_client(self, client):
|
||||
def handle_ping(latency_ms):
|
||||
@ -369,7 +409,7 @@ class VersionNegotiationEdgeCases(fake_server._FakeServerTest):
|
||||
status_response = '{"description": {"text": "FakeServer"}}'
|
||||
|
||||
class ClientHandler(fake_server.FakeClientHandler):
|
||||
def _run_status(self):
|
||||
def handle_status(self, request_packet):
|
||||
packet = clientbound.status.ResponsePacket()
|
||||
packet.json_response = status_response
|
||||
self.write_packet(packet)
|
||||
|
@ -131,6 +131,12 @@ class EncryptedCompressedConnection(EncryptedConnection,
|
||||
pass
|
||||
|
||||
|
||||
# Regression test for <https://github.com/ammaraskar/pyCraft/issues/109>.
|
||||
class EncryptedCompressedReconnect(test_connection.ReconnectTest,
|
||||
EncryptedCompressedConnection):
|
||||
pass
|
||||
|
||||
|
||||
class MockSocket(object):
|
||||
|
||||
def __init__(self, encryptor, decryptor):
|
||||
|
@ -187,7 +187,8 @@ class TestReadWritePackets(unittest.TestCase):
|
||||
self._test_read_write_packet(packet)
|
||||
|
||||
def test_spawn_object_packet(self):
|
||||
EntityType = clientbound.play.SpawnObjectPacket.EntityType
|
||||
EntityType = clientbound.play.SpawnObjectPacket.field_enum(
|
||||
'type_id', self.context)
|
||||
|
||||
object_uuid = 'd9568851-85bc-4a10-8d6a-261d130626fa'
|
||||
pos_look = PositionAndLook(x=68.0, y=38.0, z=76.0, yaw=16, pitch=23)
|
||||
@ -195,6 +196,7 @@ class TestReadWritePackets(unittest.TestCase):
|
||||
entity_id, type_name, type_id = 49846, 'EGG', EntityType.EGG
|
||||
|
||||
packet = clientbound.play.SpawnObjectPacket(
|
||||
context=self.context,
|
||||
x=pos_look.x, y=pos_look.y, z=pos_look.z,
|
||||
yaw=pos_look.yaw, pitch=pos_look.pitch,
|
||||
velocity_x=velocity.x, velocity_y=velocity.y,
|
||||
@ -207,9 +209,9 @@ class TestReadWritePackets(unittest.TestCase):
|
||||
self.assertEqual(packet.type, type_name)
|
||||
|
||||
packet2 = clientbound.play.SpawnObjectPacket(
|
||||
position_and_look=pos_look, velocity=velocity,
|
||||
type=type_name, object_uuid=object_uuid,
|
||||
entity_id=entity_id, data=1)
|
||||
context=self.context, position_and_look=pos_look,
|
||||
velocity=velocity, type=type_name,
|
||||
object_uuid=object_uuid, entity_id=entity_id, data=1)
|
||||
self.assertEqual(packet.__dict__, packet2.__dict__)
|
||||
|
||||
packet2.position = pos_look.position
|
||||
|
@ -51,6 +51,7 @@ class MapPacketTest(unittest.TestCase):
|
||||
packet.map_id = 1
|
||||
packet.scale = 42
|
||||
packet.is_tracking_position = True
|
||||
packet.is_locked = False
|
||||
packet.icons = []
|
||||
d_name = u'Marshmallow' if context.protocol_version >= 364 else None
|
||||
packet.icons.append(MapPacket.MapIcon(
|
||||
|
@ -8,6 +8,8 @@ from minecraft.networking.types import (
|
||||
String as StringType, Position, TrailingByteArray, UnsignedLong,
|
||||
)
|
||||
from minecraft.networking.packets import PacketBuffer
|
||||
from minecraft.networking.connection import ConnectionContext
|
||||
from minecraft import SUPPORTED_PROTOCOL_VERSIONS
|
||||
|
||||
|
||||
TEST_DATA = {
|
||||
@ -33,34 +35,50 @@ TEST_DATA = {
|
||||
|
||||
|
||||
class SerializationTest(unittest.TestCase):
|
||||
|
||||
def test_serialization(self):
|
||||
for data_type in Type.__subclasses__():
|
||||
if data_type in TEST_DATA:
|
||||
test_cases = TEST_DATA[data_type]
|
||||
for protocol_version in SUPPORTED_PROTOCOL_VERSIONS:
|
||||
context = ConnectionContext(protocol_version=protocol_version)
|
||||
|
||||
for test_data in test_cases:
|
||||
packet_buffer = PacketBuffer()
|
||||
data_type.send(test_data, packet_buffer)
|
||||
packet_buffer.reset_cursor()
|
||||
for data_type in Type.__subclasses__():
|
||||
if data_type in TEST_DATA:
|
||||
test_cases = TEST_DATA[data_type]
|
||||
|
||||
deserialized = data_type.read(packet_buffer)
|
||||
if data_type is FixedPointInteger:
|
||||
self.assertAlmostEqual(
|
||||
test_data, deserialized, delta=1.0/32.0)
|
||||
elif data_type is Float or data_type is Double:
|
||||
self.assertAlmostEquals(test_data, deserialized, 3)
|
||||
else:
|
||||
self.assertEqual(test_data, deserialized)
|
||||
for test_data in test_cases:
|
||||
packet_buffer = PacketBuffer()
|
||||
data_type.send_with_context(
|
||||
test_data, packet_buffer, context)
|
||||
packet_buffer.reset_cursor()
|
||||
|
||||
deserialized = data_type.read_with_context(
|
||||
packet_buffer, context)
|
||||
if data_type is FixedPointInteger:
|
||||
self.assertAlmostEqual(
|
||||
test_data, deserialized, delta=1.0/32.0)
|
||||
elif data_type is Float or data_type is Double:
|
||||
self.assertAlmostEquals(test_data, deserialized, 3)
|
||||
else:
|
||||
self.assertEqual(test_data, deserialized)
|
||||
|
||||
def test_exceptions(self):
|
||||
base_type = Type()
|
||||
with self.assertRaises(NotImplementedError):
|
||||
base_type.read(None)
|
||||
|
||||
with self.assertRaises(NotImplementedError):
|
||||
base_type.read_with_context(None, None)
|
||||
|
||||
with self.assertRaises(NotImplementedError):
|
||||
base_type.send(None, None)
|
||||
|
||||
with self.assertRaises(NotImplementedError):
|
||||
base_type.send_with_context(None, None, None)
|
||||
|
||||
with self.assertRaises(TypeError):
|
||||
Position.read(None)
|
||||
|
||||
with self.assertRaises(TypeError):
|
||||
Position.send(None, None)
|
||||
|
||||
empty_socket = PacketBuffer()
|
||||
with self.assertRaises(Exception):
|
||||
VarInt.read(empty_socket)
|
||||
|
Loading…
Reference in New Issue
Block a user