mirror of
https://github.com/ammaraskar/pyCraft.git
synced 2024-11-21 17:56:30 +01:00
Add support for Minecraft 1.18 and 1.18.1.
This commit is contained in:
parent
1ae9a2b48a
commit
bf49006553
16
.travis.yml
16
.travis.yml
@ -1,11 +1,13 @@
|
||||
language: python
|
||||
dist: focal
|
||||
python: 3.8
|
||||
python: 3.9
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- python: pypy3
|
||||
env: TOX_ENV=pypy
|
||||
before_install:
|
||||
- python -m pip install --upgrade virtualenv
|
||||
- python: 3.5
|
||||
env: TOX_ENV=py35
|
||||
- python: 3.6
|
||||
@ -14,13 +16,15 @@ matrix:
|
||||
env: TOX_ENV=py37
|
||||
- python: 3.8
|
||||
env: TOX_ENV=py38
|
||||
- python: 3.8
|
||||
- python: 3.9
|
||||
env: TOX_ENV=py39
|
||||
- python: 3.9
|
||||
env: TOX_ENV=flake8
|
||||
- python: 3.8
|
||||
- python: 3.9
|
||||
env: TOX_ENV=pylint-errors
|
||||
- python: 3.8
|
||||
- python: 3.9
|
||||
env: TOX_ENV=pylint-full
|
||||
- python: 3.8
|
||||
- python: 3.9
|
||||
env: TOX_ENV=verify-manifest
|
||||
|
||||
before_install:
|
||||
@ -32,6 +36,6 @@ install:
|
||||
script:
|
||||
- tox -e $TOX_ENV
|
||||
after_script:
|
||||
- if [ "$TOX_ENV" = "py38" ]; then tox -e coveralls; fi
|
||||
- if [ "$TOX_ENV" = "py39" ]; then tox -e coveralls; fi
|
||||
notifications:
|
||||
email: false
|
||||
|
@ -1,7 +1,7 @@
|
||||
pyCraft
|
||||
=======
|
||||
.. image:: https://travis-ci.org/ammaraskar/pyCraft.svg?branch=master
|
||||
:target: https://travis-ci.org/ammaraskar/pyCraft
|
||||
.. image:: https://app.travis-ci.com/ammaraskar/pyCraft.svg?branch=master
|
||||
:target: https://app.travis-ci.com/github/ammaraskar/pyCraft
|
||||
.. image:: https://readthedocs.org/projects/pycraft/badge/?version=latest
|
||||
:target: https://pycraft.readthedocs.org/en/latest
|
||||
.. image:: https://coveralls.io/repos/ammaraskar/pyCraft/badge.svg?branch=master
|
||||
@ -31,7 +31,9 @@ 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, 1.16.3, 1.16.4
|
||||
* 1.16, 1.16.1, 1.16.2, 1.16.3, 1.16.4, 1.16.5
|
||||
* 1.17, 1.17.1
|
||||
* 1.18, 1.18.1
|
||||
|
||||
In addition, some development snapshots and pre-release versions are supported:
|
||||
`<minecraft/__init__.py>`_ contains a full list of supported Minecraft versions
|
||||
@ -55,6 +57,7 @@ pyCraft is compatible with (at least) the following Python implementations:
|
||||
* Python 3.6
|
||||
* Python 3.7
|
||||
* Python 3.8
|
||||
* Python 3.9
|
||||
* PyPy
|
||||
|
||||
Requirements
|
||||
|
@ -460,8 +460,21 @@ KNOWN_MINECRAFT_VERSION_RECORDS = [
|
||||
Version('20w45a', PRE | 5, True),
|
||||
Version('20w46a', PRE | 6, True),
|
||||
Version('20w48a', PRE | 7, True),
|
||||
Version('20w49a', PRE | 8, False),
|
||||
Version('20w51a', PRE | 9, False),
|
||||
Version('1.16.5', 754, False),
|
||||
Version('21w03a', PRE | 11, False),
|
||||
Version('21w05a', PRE | 12, False),
|
||||
Version('21w05b', PRE | 13, False),
|
||||
Version('21w06a', PRE | 14, False),
|
||||
Version('21w07a', PRE | 15, False),
|
||||
Version('1.17-rc2', PRE | 35, False),
|
||||
Version('1.17', 755, True),
|
||||
Version('1.17.1', 756, True),
|
||||
Version('1.17.1', 756, True),
|
||||
Version('21w44a', PRE | 48, False),
|
||||
Version('1.18-rc4', PRE | 60, False),
|
||||
Version('1.18', 757, True),
|
||||
Version('1.18.1', 757, True),
|
||||
]
|
||||
|
||||
# An OrderedDict mapping the id string of each known Minecraft version to its
|
||||
|
@ -53,6 +53,13 @@ class ConnectionContext(object):
|
||||
later than, or is equal to, 'other_pv', or else False."""
|
||||
return utility.protocol_earlier_eq(other_pv, self.protocol_version)
|
||||
|
||||
def protocol_in_range(self, start_pv, end_pv):
|
||||
"""Returns True if the protocol version of this context was published
|
||||
later than, or is equal to, 'start_pv' and was published earlier
|
||||
than 'end_pv' (analogously to Python's 'range' function)."""
|
||||
return (utility.protocol_earlier(self.protocol_version, end_pv) and
|
||||
utility.protocol_earlier_eq(start_pv, self.protocol_version))
|
||||
|
||||
|
||||
class _ConnectionOptions(object):
|
||||
def __init__(self, address=None, port=None, compression_threshold=-1,
|
||||
|
@ -1,3 +1,4 @@
|
||||
from minecraft import PRE
|
||||
from minecraft.networking.packets import (
|
||||
Packet, AbstractKeepAlivePacket, AbstractPluginMessagePacket
|
||||
)
|
||||
@ -8,7 +9,10 @@ from minecraft.networking.types import (
|
||||
PositionAndLook, multi_attribute_alias, attribute_transform,
|
||||
)
|
||||
|
||||
from .combat_event_packet import CombatEventPacket
|
||||
from .combat_event_packet import (
|
||||
CombatEventPacket, EnterCombatEventPacket, EndCombatEventPacket,
|
||||
DeathCombatEventPacket,
|
||||
)
|
||||
from .map_packet import MapPacket
|
||||
from .player_list_item_packet import PlayerListItemPacket
|
||||
from .player_position_and_look_packet import PlayerPositionAndLookPacket
|
||||
@ -36,7 +40,6 @@ def get_packets(context):
|
||||
EntityPositionDeltaPacket,
|
||||
TimeUpdatePacket,
|
||||
UpdateHealthPacket,
|
||||
CombatEventPacket,
|
||||
ExplosionPacket,
|
||||
SpawnObjectPacket,
|
||||
BlockChangePacket,
|
||||
@ -47,18 +50,33 @@ def get_packets(context):
|
||||
EntityLookPacket,
|
||||
ResourcePackSendPacket
|
||||
}
|
||||
|
||||
if context.protocol_earlier_eq(47):
|
||||
packets |= {
|
||||
SetCompressionPacket,
|
||||
}
|
||||
|
||||
if context.protocol_earlier(PRE | 15):
|
||||
packets |= {
|
||||
CombatEventPacket,
|
||||
}
|
||||
else:
|
||||
packets |= {
|
||||
EnterCombatEventPacket,
|
||||
EndCombatEventPacket,
|
||||
DeathCombatEventPacket,
|
||||
}
|
||||
|
||||
if context.protocol_later_eq(94):
|
||||
packets |= {
|
||||
SoundEffectPacket,
|
||||
}
|
||||
|
||||
if context.protocol_later_eq(352):
|
||||
packets |= {
|
||||
FacePlayerPacket
|
||||
}
|
||||
|
||||
return packets
|
||||
|
||||
|
||||
@ -264,7 +282,8 @@ class EntityPositionDeltaPacket(Packet):
|
||||
class TimeUpdatePacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x58 if context.protocol_later_eq(755) else \
|
||||
return 0x59 if context.protocol_later_eq(PRE | 48) else \
|
||||
0x58 if context.protocol_later_eq(755) else \
|
||||
0x4E if context.protocol_later_eq(721) else \
|
||||
0x4F if context.protocol_later_eq(550) else \
|
||||
0x4E if context.protocol_later_eq(471) else \
|
||||
@ -332,7 +351,8 @@ class PluginMessagePacket(AbstractPluginMessagePacket):
|
||||
class PlayerListHeaderAndFooterPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x5E if context.protocol_later_eq(755) else \
|
||||
return 0x5F if context.protocol_later_eq(PRE | 48) else \
|
||||
0x5E if context.protocol_later_eq(755) else \
|
||||
0x53 if context.protocol_later_eq(721) else \
|
||||
0x54 if context.protocol_later_eq(550) else \
|
||||
0x53 if context.protocol_later_eq(471) else \
|
||||
@ -374,16 +394,35 @@ class EntityLookPacket(Packet):
|
||||
{'on_ground': Boolean}
|
||||
]
|
||||
|
||||
|
||||
class ResourcePackSendPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x3C if context.protocol_later_eq(755) else \
|
||||
0x38
|
||||
return 0x3C if context.protocol_later_eq(PRE | 15) else \
|
||||
0x39 if context.protocol_later_eq(PRE | 8) else \
|
||||
0x38 if context.protocol_later_eq(741) else \
|
||||
0x39 if context.protocol_later_eq(721) else \
|
||||
0x3A if context.protocol_later_eq(550) else \
|
||||
0x39 if context.protocol_later_eq(471) else \
|
||||
0x37 if context.protocol_later_eq(461) else \
|
||||
0x38 if context.protocol_later_eq(451) else \
|
||||
0x37 if context.protocol_later_eq(389) else \
|
||||
0x36 if context.protocol_later_eq(352) else \
|
||||
0x35 if context.protocol_later_eq(345) else \
|
||||
0x34 if context.protocol_later_eq(336) else \
|
||||
0x33 if context.protocol_later_eq(332) else \
|
||||
0x34 if context.protocol_later_eq(318) else \
|
||||
0x32 if context.protocol_later_eq(70) else \
|
||||
0x48
|
||||
|
||||
packet_name = "resource pack send"
|
||||
definition = [
|
||||
{"url": String},
|
||||
{"hash": String},
|
||||
{"forced": Boolean},
|
||||
{"forced_message": String}
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def get_definition(context):
|
||||
return [
|
||||
{"url": String},
|
||||
{"hash": String},
|
||||
{"forced": Boolean} if context.protocol_later_eq(PRE | 5) else {},
|
||||
{"forced_message": String}
|
||||
if context.protocol_later_eq(PRE | 15) else {},
|
||||
]
|
||||
|
@ -1,3 +1,6 @@
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
from minecraft import PRE
|
||||
from minecraft.networking.packets import Packet
|
||||
|
||||
from minecraft.networking.types import (
|
||||
@ -5,10 +8,16 @@ from minecraft.networking.types import (
|
||||
)
|
||||
|
||||
|
||||
# Note: this packet was removed in Minecraft 21w07a (protocol PRE|15)
|
||||
# and replaced with the separate EnterCombatEvent, EndCombatEvent, and
|
||||
# DeathCombatEvent packets. These are subclasses of CombatEventPacket, so
|
||||
# that code written to listen for CombatEventPacket instances should in most
|
||||
# cases continue to work without modification.
|
||||
class CombatEventPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x31 if context.protocol_later_eq(741) else \
|
||||
@classmethod
|
||||
def get_id(cls, context):
|
||||
return cls.deprecated() if context.protocol_later_eq(PRE | 15) else \
|
||||
0x31 if context.protocol_later_eq(741) else \
|
||||
0x32 if context.protocol_later_eq(721) else \
|
||||
0x33 if context.protocol_later_eq(550) else \
|
||||
0x32 if context.protocol_later_eq(471) else \
|
||||
@ -28,19 +37,19 @@ class CombatEventPacket(Packet):
|
||||
fields = 'event',
|
||||
|
||||
# The abstract type of the 'event' field of this packet.
|
||||
class EventType(MutableRecord):
|
||||
class EventType(MutableRecord, metaclass=ABCMeta):
|
||||
__slots__ = ()
|
||||
type_from_id_dict = {}
|
||||
|
||||
# Read the fields of the event (not including the ID) from the file.
|
||||
@abstractmethod
|
||||
def read(self, file_object):
|
||||
raise NotImplementedError(
|
||||
'This abstract method must be overridden in a subclass.')
|
||||
pass
|
||||
|
||||
# Write the fields of the event (not including the ID) to the buffer.
|
||||
@abstractmethod
|
||||
def write(self, packet_buffer):
|
||||
raise NotImplementedError(
|
||||
'This abstract method must be overridden in a subclass.')
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def type_from_id(cls, event_id):
|
||||
@ -89,10 +98,72 @@ class CombatEventPacket(Packet):
|
||||
EventType.type_from_id_dict[EntityDeadEvent.id] = EntityDeadEvent
|
||||
|
||||
def read(self, file_object):
|
||||
if self.context and self.context.protocol_later_eq(PRE | 15):
|
||||
self.deprecated()
|
||||
event_id = VarInt.read(file_object)
|
||||
self.event = CombatEventPacket.EventType.type_from_id(event_id)()
|
||||
self.event.read(file_object)
|
||||
|
||||
def write_fields(self, packet_buffer):
|
||||
if self.context and self.context.protocol_later_eq(PRE | 15):
|
||||
self.deprecated()
|
||||
VarInt.send(self.event.id, packet_buffer)
|
||||
self.event.write(packet_buffer)
|
||||
|
||||
@staticmethod
|
||||
def deprecated():
|
||||
raise NotImplementedError(
|
||||
'`CombatEventPacket` was removed in Minecraft snapshot 21w07a '
|
||||
'(protocol version 2**30 + 15). In this and later versions, one '
|
||||
'of the subclasses '
|
||||
+ repr(SpecialisedCombatEventPacket.__subclasses__()) + ' must be '
|
||||
'used directly for usage like that which generates this message.')
|
||||
|
||||
|
||||
# Contains the behaviour common to all concrete CombatEventPacket subclasses
|
||||
class SpecialisedCombatEventPacket(CombatEventPacket):
|
||||
def __init__(self, *args, **kwds):
|
||||
super(SpecialisedCombatEventPacket, self).__init__(*args, **kwds)
|
||||
|
||||
# Prior to Minecraft 21w07a, instances of CombatEventPacket had a
|
||||
# single 'event' field giving a 'MutableRecord' of one of three types
|
||||
# corresponding to the type of combat event represented. For backward
|
||||
# compatibility, we here present a similar interface, giving the packet
|
||||
# object itself as the 'event', which should work identically in most
|
||||
# use cases, since it is a virtual subclass of, and has attributes of
|
||||
# the same names and contents as those of, the previous event records.
|
||||
self.event = self
|
||||
|
||||
# The 'get_id', 'fields', 'read', and 'write_fields' attributes of the
|
||||
# 'Packet' base class are all overridden in 'CombatEventPacket'. We desire
|
||||
# the default behaviour of these attributes, so we restore them here:
|
||||
get_id = Packet.__dict__['get_id']
|
||||
fields = Packet.__dict__['fields']
|
||||
read = Packet.__dict__['read']
|
||||
write_fields = Packet.__dict__['write_fields']
|
||||
|
||||
|
||||
@CombatEventPacket.EnterCombatEvent.register # virtual subclass
|
||||
class EnterCombatEventPacket(SpecialisedCombatEventPacket):
|
||||
packet_name = 'enter combat event'
|
||||
id = 0x34
|
||||
definition = []
|
||||
|
||||
|
||||
@CombatEventPacket.EndCombatEvent.register # virtual subclass
|
||||
class EndCombatEventPacket(SpecialisedCombatEventPacket):
|
||||
packet_name = 'end combat event'
|
||||
id = 0x33
|
||||
definition = [
|
||||
{'duration': VarInt},
|
||||
{'entity_id': Integer}]
|
||||
|
||||
|
||||
@CombatEventPacket.EntityDeadEvent.register # virtual subclass
|
||||
class DeathCombatEventPacket(SpecialisedCombatEventPacket):
|
||||
packet_name = 'death combat event'
|
||||
id = 0x35
|
||||
definition = [
|
||||
{'player_id': VarInt},
|
||||
{'entity_id': Integer},
|
||||
{'message': String}]
|
||||
|
@ -1,5 +1,6 @@
|
||||
from minecraft.networking.types import (
|
||||
Vector, Float, Byte, Integer, PrefixedArray, multi_attribute_alias, Type,
|
||||
VarInt,
|
||||
)
|
||||
from minecraft.networking.packets import Packet
|
||||
|
||||
@ -34,15 +35,22 @@ class ExplosionPacket(Packet):
|
||||
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}]
|
||||
@staticmethod
|
||||
def get_definition(context):
|
||||
return [
|
||||
{'x': Float},
|
||||
{'y': Float},
|
||||
{'z': Float},
|
||||
{'radius': Float},
|
||||
|
||||
{'records': PrefixedArray(VarInt, ExplosionPacket.Record)}
|
||||
if context.protocol_later_eq(755) else
|
||||
{'records': PrefixedArray(Integer, ExplosionPacket.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')
|
||||
|
@ -92,6 +92,8 @@ class JoinGamePacket(AbstractDimensionPacket):
|
||||
VarInt if context.protocol_later_eq(749) else UnsignedByte},
|
||||
{'level_type': String} if context.protocol_earlier(716) else {},
|
||||
{'render_distance': VarInt} if context.protocol_later_eq(468) else {},
|
||||
{'simulation_distance': VarInt}
|
||||
if context.protocol_later_eq(757) else {},
|
||||
{'reduced_debug_info': Boolean},
|
||||
{'respawn_screen': Boolean} if context.protocol_later_eq(571) else {},
|
||||
{'is_debug': Boolean} if context.protocol_later_eq(716) else {},
|
||||
|
@ -1,3 +1,4 @@
|
||||
from minecraft import PRE
|
||||
from minecraft.networking.packets import Packet
|
||||
from minecraft.networking.types import (
|
||||
VarInt, Byte, Boolean, UnsignedByte, VarIntPrefixedByteArray, String,
|
||||
@ -72,9 +73,9 @@ class MapPacket(Packet):
|
||||
self.map_id = VarInt.read(file_object)
|
||||
self.scale = Byte.read(file_object)
|
||||
|
||||
if self.context.protocol_later_eq(107):
|
||||
if self.context.protocol_in_range(107, PRE | 6):
|
||||
self.is_tracking_position = Boolean.read(file_object)
|
||||
else:
|
||||
elif self.context.protocol_earlier(107):
|
||||
self.is_tracking_position = True
|
||||
|
||||
if self.context.protocol_later_eq(452):
|
||||
@ -82,6 +83,9 @@ class MapPacket(Packet):
|
||||
else:
|
||||
self.is_locked = False
|
||||
|
||||
if self.context.protocol_later_eq(PRE | 6):
|
||||
self.is_tracking_position = Boolean.read(file_object)
|
||||
|
||||
icon_count = VarInt.read(file_object)
|
||||
self.icons = []
|
||||
for i in range(icon_count):
|
||||
@ -101,10 +105,7 @@ class MapPacket(Packet):
|
||||
icon = MapPacket.MapIcon(type, direction, (x, z), display_name)
|
||||
self.icons.append(icon)
|
||||
|
||||
try:
|
||||
self.width = UnsignedByte.read(file_object)
|
||||
except:
|
||||
self.width = None
|
||||
self.width = UnsignedByte.read(file_object)
|
||||
if self.width:
|
||||
self.height = UnsignedByte.read(file_object)
|
||||
x = Byte.read(file_object)
|
||||
|
@ -1,3 +1,4 @@
|
||||
from minecraft import PRE
|
||||
from minecraft.networking.packets import Packet
|
||||
from minecraft.networking.types import (
|
||||
VarInt, String, Float, Byte, Type, Integer, Vector, Enum,
|
||||
@ -9,7 +10,8 @@ __all__ = 'SoundEffectPacket',
|
||||
class SoundEffectPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x5C if context.protocol_later_eq(755) else \
|
||||
return 0x5D if context.protocol_later_eq(PRE | 48) else \
|
||||
0x5C if context.protocol_later_eq(755) else \
|
||||
0x51 if context.protocol_later_eq(721) else \
|
||||
0x52 if context.protocol_later_eq(550) else \
|
||||
0x51 if context.protocol_later_eq(471) else \
|
||||
|
@ -268,11 +268,12 @@ class UseItemPacket(Packet):
|
||||
|
||||
Hand = RelativeHand
|
||||
|
||||
|
||||
class ResourcePackStatusPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x21
|
||||
packet_name = "resource pask status"
|
||||
packet_name = "resource pack status"
|
||||
definition = [
|
||||
{"result": VarInt}
|
||||
]
|
@ -1,8 +1,11 @@
|
||||
import operator
|
||||
|
||||
from minecraft.networking.packets import Packet
|
||||
from minecraft.networking.types import (
|
||||
String, Byte, VarInt, Boolean, UnsignedByte, Enum, BitFieldEnum,
|
||||
AbsoluteHand
|
||||
)
|
||||
from minecraft.utility import attribute_transform
|
||||
|
||||
|
||||
class ClientSettingsPacket(Packet):
|
||||
@ -18,13 +21,40 @@ class ClientSettingsPacket(Packet):
|
||||
|
||||
packet_name = 'client settings'
|
||||
|
||||
get_definition = staticmethod(lambda context: [
|
||||
{'locale': String},
|
||||
{'view_distance': Byte},
|
||||
{'chat_mode': VarInt if context.protocol_later(47) else Byte},
|
||||
{'chat_colors': Boolean},
|
||||
{'displayed_skin_parts': UnsignedByte},
|
||||
{'main_hand': VarInt} if context.protocol_later(49) else {}])
|
||||
@staticmethod
|
||||
def get_definition(context):
|
||||
return [
|
||||
{'locale': String},
|
||||
{'view_distance': Byte},
|
||||
{'chat_mode': VarInt if context.protocol_later(47) else Byte},
|
||||
{'chat_colors': Boolean},
|
||||
{'displayed_skin_parts': UnsignedByte},
|
||||
{'main_hand': VarInt} if context.protocol_later(49) else {},
|
||||
|
||||
{'enable_text_filtering': Boolean}
|
||||
if context.protocol_later_eq(757) else
|
||||
{'disable_text_filtering': Boolean}
|
||||
if context.protocol_later_eq(755) else {},
|
||||
|
||||
{'allow_server_listings': Boolean}
|
||||
if context.protocol_later_eq(755) else {},
|
||||
]
|
||||
|
||||
# Set a default value for 'enable_text_filtering', because most clients
|
||||
# will probably want this value, and to avoid breaking old code.
|
||||
enable_text_filtering = False
|
||||
|
||||
# To support the possibility of both 'enable_text_filtering' and
|
||||
# 'disable_text_filtering' fields existing, make 'disable_text_filtering'
|
||||
# (which is the less likely to be used of the two) into a property that
|
||||
# stores the negation of its value in the ordinary attribute
|
||||
# 'enable_text_filtering'.
|
||||
disable_text_filtering = attribute_transform(
|
||||
'enable_text_filtering', operator.not_, operator.not_)
|
||||
|
||||
# Set a default value for 'allow_server_listings', because most clients
|
||||
# will probably want this value, and to avoid breaking old code.
|
||||
allow_server_listings = False
|
||||
|
||||
field_enum = classmethod(
|
||||
lambda cls, field, context: {
|
||||
|
@ -33,7 +33,20 @@ def attribute_alias(name):
|
||||
"""An attribute descriptor that redirects access to a different attribute
|
||||
with a given name.
|
||||
"""
|
||||
return attribute_transform(name, lambda x: x, lambda x: x)
|
||||
return property(
|
||||
fget=(lambda self: getattr(self, name)),
|
||||
fset=(lambda self, value: setattr(self, name, value)),
|
||||
fdel=(lambda self: delattr(self, name)))
|
||||
|
||||
|
||||
def partial_attribute_alias(name, part):
|
||||
"""An attribute descriptor that redirects access to a particular named
|
||||
attribute, 'part', on a different attribute with a given name.
|
||||
"""
|
||||
return property(
|
||||
fget=(lambda self: getattr(getattr(self, name), part)),
|
||||
fset=(lambda self, value: setattr(getattr(self, name), part, value)),
|
||||
fdel=(lambda self: delattr(getattr(self, name), part)))
|
||||
|
||||
|
||||
def multi_attribute_alias(container, *arg_names, **kwd_names):
|
||||
|
@ -115,7 +115,8 @@ class FakeClientHandler(object):
|
||||
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)
|
||||
simulation_distance=9, respawn_screen=False, is_debug=False,
|
||||
is_flat=False)
|
||||
|
||||
if self.server.context.protocol_later_eq(748):
|
||||
packet.dimension = pynbt.TAG_Compound({
|
||||
|
@ -6,7 +6,10 @@ import struct
|
||||
from zlib import decompress
|
||||
from random import choice
|
||||
|
||||
from minecraft import SUPPORTED_PROTOCOL_VERSIONS, RELEASE_PROTOCOL_VERSIONS
|
||||
from minecraft.utility import protocol_earlier
|
||||
from minecraft import (
|
||||
PRE, SUPPORTED_PROTOCOL_VERSIONS, RELEASE_PROTOCOL_VERSIONS,
|
||||
)
|
||||
from minecraft.networking.connection import ConnectionContext
|
||||
from minecraft.networking.types import (
|
||||
VarInt, Enum, Vector, PositionAndLook, OriginPoint,
|
||||
@ -157,19 +160,21 @@ class TestReadWritePackets(unittest.TestCase):
|
||||
maxDiff = None
|
||||
|
||||
def test_explosion_packet(self):
|
||||
context = ConnectionContext(protocol_version=TEST_VERSIONS[-1])
|
||||
Record = clientbound.play.ExplosionPacket.Record
|
||||
packet = clientbound.play.ExplosionPacket(
|
||||
position=Vector(787, -37, 0), radius=15,
|
||||
records=[Record(-14, -116, -5), Record(-77, 34, -36),
|
||||
Record(-35, -127, 95), Record(11, 113, -8)],
|
||||
player_motion=Vector(4, 5, 0))
|
||||
player_motion=Vector(4, 5, 0), context=context)
|
||||
|
||||
self.assertEqual(
|
||||
str(packet),
|
||||
'ExplosionPacket(x=787, y=-37, z=0, radius=15, records=['
|
||||
'0x%02X ExplosionPacket(x=787, y=-37, z=0, radius=15, records=['
|
||||
'Record(-14, -116, -5), Record(-77, 34, -36), '
|
||||
'Record(-35, -127, 95), Record(11, 113, -8)], '
|
||||
'player_motion_x=4, player_motion_y=5, player_motion_z=0)'
|
||||
% packet.id
|
||||
)
|
||||
|
||||
self._test_read_write_packet(packet)
|
||||
@ -181,7 +186,16 @@ class TestReadWritePackets(unittest.TestCase):
|
||||
str(packet),
|
||||
'CombatEventPacket(event=EnterCombatEvent())'
|
||||
)
|
||||
self._test_read_write_packet(packet)
|
||||
self._test_read_write_packet(packet, vmax=PRE | 14)
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self._test_read_write_packet(packet, vmin=PRE | 15)
|
||||
|
||||
specialised_packet = clientbound.play.EnterCombatEventPacket()
|
||||
self.assertIsInstance(specialised_packet.event, type(packet.event))
|
||||
for field in specialised_packet.fields:
|
||||
value = getattr(packet.event, field)
|
||||
setattr(specialised_packet, field, value)
|
||||
self.assertEqual(getattr(specialised_packet.event, field), value)
|
||||
|
||||
packet = clientbound.play.CombatEventPacket(
|
||||
event=clientbound.play.CombatEventPacket.EndCombatEvent(
|
||||
@ -189,7 +203,16 @@ class TestReadWritePackets(unittest.TestCase):
|
||||
self.assertEqual(str(packet),
|
||||
'CombatEventPacket(event=EndCombatEvent('
|
||||
'duration=415, entity_id=91063502))')
|
||||
self._test_read_write_packet(packet)
|
||||
self._test_read_write_packet(packet, vmax=PRE | 14)
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self._test_read_write_packet(packet, vmin=PRE | 15)
|
||||
|
||||
specialised_packet = clientbound.play.EndCombatEventPacket()
|
||||
self.assertIsInstance(specialised_packet.event, type(packet.event))
|
||||
for field in specialised_packet.fields:
|
||||
value = getattr(packet.event, field)
|
||||
setattr(specialised_packet, field, value)
|
||||
self.assertEqual(getattr(specialised_packet.event, field), value)
|
||||
|
||||
packet = clientbound.play.CombatEventPacket(
|
||||
event=clientbound.play.CombatEventPacket.EntityDeadEvent(
|
||||
@ -199,7 +222,16 @@ class TestReadWritePackets(unittest.TestCase):
|
||||
"CombatEventPacket(event=EntityDeadEvent("
|
||||
"player_id=178, entity_id=36, message='RIP'))"
|
||||
)
|
||||
self._test_read_write_packet(packet)
|
||||
self._test_read_write_packet(packet, vmax=PRE | 14)
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self._test_read_write_packet(packet, vmin=PRE | 15)
|
||||
|
||||
specialised_packet = clientbound.play.DeathCombatEventPacket()
|
||||
self.assertIsInstance(specialised_packet.event, type(packet.event))
|
||||
for field in specialised_packet.fields:
|
||||
value = getattr(packet.event, field)
|
||||
setattr(specialised_packet, field, value)
|
||||
self.assertEqual(getattr(specialised_packet.event, field), value)
|
||||
|
||||
def test_multi_block_change_packet(self):
|
||||
Record = clientbound.play.MultiBlockChangePacket.Record
|
||||
@ -348,16 +380,22 @@ class TestReadWritePackets(unittest.TestCase):
|
||||
)
|
||||
self._test_read_write_packet(packet, context)
|
||||
|
||||
def _test_read_write_packet(self, packet_in, context=None, **kwargs):
|
||||
def _test_read_write_packet(
|
||||
self, packet_in, context=None, vmin=None, vmax=None, **kwargs
|
||||
):
|
||||
"""
|
||||
If kwargs are specified, the key will be tested against the
|
||||
respective delta value. Useful for testing FixedPointNumbers
|
||||
where there is precision lost in the resulting value.
|
||||
"""
|
||||
if context is None:
|
||||
for protocol_version in TEST_VERSIONS:
|
||||
logging.debug('protocol_version = %r' % protocol_version)
|
||||
context = ConnectionContext(protocol_version=protocol_version)
|
||||
for pv in TEST_VERSIONS:
|
||||
if vmin is not None and protocol_earlier(pv, vmin):
|
||||
continue
|
||||
if vmax is not None and protocol_earlier(vmax, pv):
|
||||
continue
|
||||
logging.debug('protocol_version = %r' % pv)
|
||||
context = ConnectionContext(protocol_version=pv)
|
||||
self._test_read_write_packet(packet_in, context)
|
||||
else:
|
||||
packet_in.context = context
|
||||
|
@ -4,6 +4,7 @@ from minecraft.networking.packets import PacketBuffer
|
||||
from minecraft.networking.packets.clientbound.play import (
|
||||
PlayerPositionAndLookPacket, PlayerListItemPacket, MapPacket
|
||||
)
|
||||
from minecraft.networking.packets import serverbound
|
||||
from minecraft.networking.connection import ConnectionContext
|
||||
|
||||
from tests.test_packets import TEST_VERSIONS
|
||||
@ -306,3 +307,16 @@ class PlayerListItemTest(unittest.TestCase):
|
||||
packet.write_fields(packet_buffer)
|
||||
self.read_and_apply(context, packet_buffer, player_list)
|
||||
self.assertNotIn(fake_uuid, player_list.players_by_uuid)
|
||||
|
||||
|
||||
class ClientSettingsTest(unittest.TestCase):
|
||||
def test_enable_disable_text_filtering(self):
|
||||
packet = serverbound.play.ClientSettingsPacket()
|
||||
self.assertEqual(packet.enable_text_filtering, False)
|
||||
self.assertEqual(packet.disable_text_filtering, True)
|
||||
packet.enable_text_filtering = True
|
||||
self.assertEqual(packet.enable_text_filtering, True)
|
||||
self.assertEqual(packet.disable_text_filtering, False)
|
||||
packet.disable_text_filtering = True
|
||||
self.assertEqual(packet.enable_text_filtering, False)
|
||||
self.assertEqual(packet.disable_text_filtering, True)
|
||||
|
14
tox.ini
14
tox.ini
@ -4,7 +4,7 @@
|
||||
# and then run "tox" from this directory.
|
||||
|
||||
[tox]
|
||||
envlist = py35, py36, py37, py38, pypy, flake8, pylint-errors, pylint-full, verify-manifest
|
||||
envlist = py35, py36, py37, py38, py39, pypy, flake8, pylint-errors, pylint-full, verify-manifest
|
||||
|
||||
[testenv]
|
||||
commands = nosetests --with-timer
|
||||
@ -27,7 +27,7 @@ commands =
|
||||
deps =
|
||||
coveralls
|
||||
|
||||
[testenv:py38]
|
||||
[testenv:py39]
|
||||
setenv =
|
||||
PYCRAFT_RUN_INTERNET_TESTS=1
|
||||
commands =
|
||||
@ -41,7 +41,7 @@ deps =
|
||||
mock
|
||||
|
||||
[testenv:flake8]
|
||||
basepython = python3.8
|
||||
basepython = python3.9
|
||||
commands =
|
||||
flake8 minecraft tests setup.py start.py bin/generate_travis_yml.py
|
||||
deps =
|
||||
@ -54,14 +54,14 @@ per-file-ignores =
|
||||
minecraft/networking/packets/__init__.py:F401
|
||||
|
||||
[testenv:pylint-errors]
|
||||
basepython = python3.8
|
||||
basepython = python3.9
|
||||
deps =
|
||||
{[testenv]deps}
|
||||
pylint
|
||||
commands = pylint minecraft -E
|
||||
|
||||
[testenv:pylint-full]
|
||||
basepython = python3.8
|
||||
basepython = python3.9
|
||||
deps =
|
||||
{[testenv]deps}
|
||||
pylint
|
||||
@ -69,7 +69,7 @@ commands =
|
||||
- pylint minecraft --disable=E
|
||||
|
||||
[testenv:docs]
|
||||
basepython = python3.8
|
||||
basepython = python3.9
|
||||
deps =
|
||||
{[testenv:cover]deps}
|
||||
sphinx
|
||||
@ -78,7 +78,7 @@ commands =
|
||||
{toxinidir}/bin/build_docs
|
||||
|
||||
[testenv:verify-manifest]
|
||||
basepython = python3.8
|
||||
basepython = python3.9
|
||||
deps =
|
||||
check-manifest
|
||||
commands =
|
||||
|
Loading…
Reference in New Issue
Block a user