2021-12-16 19:39:13 +01:00
|
|
|
from abc import ABCMeta, abstractmethod
|
|
|
|
|
|
|
|
from minecraft import PRE
|
2017-08-08 15:07:33 +02:00
|
|
|
from minecraft.networking.packets import Packet
|
|
|
|
|
|
|
|
from minecraft.networking.types import (
|
2018-05-27 16:36:13 +02:00
|
|
|
VarInt, Integer, String, MutableRecord
|
2017-08-08 15:07:33 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
2021-12-16 19:39:13 +01:00
|
|
|
# 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.
|
2017-08-08 15:07:33 +02:00
|
|
|
class CombatEventPacket(Packet):
|
2021-12-16 19:39:13 +01:00
|
|
|
@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 \
|
Fix: non-monotonic protocol versions are not correctly handled
After 1.16.3, Mojang started publishing snapshot, pre-release and release
candidate versions of Minecraft with protocol version numbers of the form
`(1 << 30) | n' where 'n' is a small non-negative integer increasing with each
such version; the release versions continued to use the old format. For
example, these are the last 8 published Minecraft versions as of this commit:
release 1.16.3 uses protocol version 753
pre-release 1.16.4-pre1 uses protocol version 1073741825 == (1 << 30) | 1
pre-release 1.16.4-pre2 uses protocol version 1073741826 == (1 << 30) | 2
release candidate 1.16.4-rc1 uses protocol version 1073741827 == (1 << 30) | 3
release 1.16.4 uses protocol version 754
snapshot 20w45a uses protocol version 1073741829 == (1 << 30) | 5
snapshot 20w46a uses protocol version 1073741830 == (1 << 30) | 6
snapshot 20w48a uses protocol version 1073741831 == (1 << 30) | 7
This means that protocol versions no longer increase monotonically with respect
to publication history, a property that was assumed to hold in much of
pyCraft's code relating to support of multiple protocol versions. This commit
rectifies the issue by replacing any comparison of protocol versions by their
numerical value with a comparison based on their publication time.
Newly defined is the dictionary `minecraft.PROTOCOL_VERSION_INDICES', which
maps each known protocol version to its index in the protocol chronology. As
such, the bound method `minecraft.PROTOCOL_VERSION_INDICES.get` can be used as
a key function for the built-in `sorted`, `min` and `max` functions to collate
protocol versions chronologically.
Two utility functions are provided for direct comparison of protocol versions:
`minecraft.utility.protocol_earlier` and
`minecraft.utility.protocol_earlier_eq`.
Additionally, four methods are added to the `ConnectionContext` type to ease
the most common cases where the protocol of a given context must be compared to
a given version number:
`minecraft.connection.ConnectionContext.protocol_earlier`,
`minecraft.connection.ConnectionContext.protocol_earlier_eq`,
`minecraft.connection.ConnectionContext.protocol_later` and
`minecraft.connection.ConnectionContext.protocol_later_eq`.
2020-12-02 14:30:40 +01:00
|
|
|
0x32 if context.protocol_later_eq(721) else \
|
|
|
|
0x33 if context.protocol_later_eq(550) else \
|
|
|
|
0x32 if context.protocol_later_eq(471) else \
|
|
|
|
0x30 if context.protocol_later_eq(451) else \
|
|
|
|
0x2F if context.protocol_later_eq(389) else \
|
|
|
|
0x2E if context.protocol_later_eq(345) else \
|
|
|
|
0x2D if context.protocol_later_eq(336) else \
|
|
|
|
0x2C if context.protocol_later_eq(332) else \
|
|
|
|
0x2D if context.protocol_later_eq(318) else \
|
|
|
|
0x2C if context.protocol_later_eq(86) else \
|
|
|
|
0x2D if context.protocol_later_eq(80) else \
|
|
|
|
0x2C if context.protocol_later_eq(67) else \
|
2017-08-08 15:07:33 +02:00
|
|
|
0x42
|
|
|
|
|
|
|
|
packet_name = 'combat event'
|
|
|
|
|
2019-06-08 15:38:20 +02:00
|
|
|
fields = 'event',
|
|
|
|
|
2018-05-27 14:28:01 +02:00
|
|
|
# The abstract type of the 'event' field of this packet.
|
2021-12-16 19:39:13 +01:00
|
|
|
class EventType(MutableRecord, metaclass=ABCMeta):
|
2018-05-27 14:28:01 +02:00
|
|
|
__slots__ = ()
|
|
|
|
type_from_id_dict = {}
|
|
|
|
|
|
|
|
# Read the fields of the event (not including the ID) from the file.
|
2021-12-16 19:39:13 +01:00
|
|
|
@abstractmethod
|
2017-08-08 15:07:33 +02:00
|
|
|
def read(self, file_object):
|
2021-12-16 19:39:13 +01:00
|
|
|
pass
|
2017-08-08 15:07:33 +02:00
|
|
|
|
2018-05-27 14:28:01 +02:00
|
|
|
# Write the fields of the event (not including the ID) to the buffer.
|
2021-12-16 19:39:13 +01:00
|
|
|
@abstractmethod
|
2018-05-27 14:28:01 +02:00
|
|
|
def write(self, packet_buffer):
|
2021-12-16 19:39:13 +01:00
|
|
|
pass
|
2017-08-08 15:07:33 +02:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def type_from_id(cls, event_id):
|
2018-05-27 14:28:01 +02:00
|
|
|
subcls = cls.type_from_id_dict.get(event_id)
|
2017-08-08 15:07:33 +02:00
|
|
|
if subcls is None:
|
2018-05-27 14:28:01 +02:00
|
|
|
raise ValueError('Unknown combat event ID: %s.' % event_id)
|
2017-08-08 15:07:33 +02:00
|
|
|
return subcls
|
|
|
|
|
|
|
|
class EnterCombatEvent(EventType):
|
2018-05-27 14:28:01 +02:00
|
|
|
__slots__ = ()
|
|
|
|
id = 0
|
|
|
|
|
|
|
|
def read(self, file_object):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def write(self, packet_buffer):
|
2017-08-08 15:07:33 +02:00
|
|
|
pass
|
2018-05-27 14:28:01 +02:00
|
|
|
EventType.type_from_id_dict[EnterCombatEvent.id] = EnterCombatEvent
|
2017-08-08 15:07:33 +02:00
|
|
|
|
|
|
|
class EndCombatEvent(EventType):
|
|
|
|
__slots__ = 'duration', 'entity_id'
|
2018-05-27 14:28:01 +02:00
|
|
|
id = 1
|
2017-08-08 15:07:33 +02:00
|
|
|
|
2018-05-27 14:28:01 +02:00
|
|
|
def read(self, file_object):
|
2017-08-08 15:07:33 +02:00
|
|
|
self.duration = VarInt.read(file_object)
|
|
|
|
self.entity_id = Integer.read(file_object)
|
|
|
|
|
2018-05-27 14:28:01 +02:00
|
|
|
def write(self, packet_buffer):
|
|
|
|
VarInt.send(self.duration, packet_buffer)
|
|
|
|
Integer.send(self.entity_id, packet_buffer)
|
|
|
|
EventType.type_from_id_dict[EndCombatEvent.id] = EndCombatEvent
|
|
|
|
|
2017-08-08 15:07:33 +02:00
|
|
|
class EntityDeadEvent(EventType):
|
|
|
|
__slots__ = 'player_id', 'entity_id', 'message'
|
2018-05-27 14:28:01 +02:00
|
|
|
id = 2
|
2017-08-08 15:07:33 +02:00
|
|
|
|
2018-05-27 14:28:01 +02:00
|
|
|
def read(self, file_object):
|
2017-08-08 15:07:33 +02:00
|
|
|
self.player_id = VarInt.read(file_object)
|
|
|
|
self.entity_id = Integer.read(file_object)
|
|
|
|
self.message = String.read(file_object)
|
|
|
|
|
2018-05-27 14:28:01 +02:00
|
|
|
def write(self, packet_buffer):
|
|
|
|
VarInt.send(self.player_id, packet_buffer)
|
|
|
|
Integer.send(self.entity_id, packet_buffer)
|
|
|
|
String.send(self.message, packet_buffer)
|
|
|
|
EventType.type_from_id_dict[EntityDeadEvent.id] = EntityDeadEvent
|
|
|
|
|
2017-08-08 15:07:33 +02:00
|
|
|
def read(self, file_object):
|
2021-12-16 19:39:13 +01:00
|
|
|
if self.context and self.context.protocol_later_eq(PRE | 15):
|
|
|
|
self.deprecated()
|
2017-08-08 15:07:33 +02:00
|
|
|
event_id = VarInt.read(file_object)
|
2018-05-27 14:28:01 +02:00
|
|
|
self.event = CombatEventPacket.EventType.type_from_id(event_id)()
|
|
|
|
self.event.read(file_object)
|
2017-08-08 15:07:33 +02:00
|
|
|
|
2018-05-27 14:28:01 +02:00
|
|
|
def write_fields(self, packet_buffer):
|
2021-12-16 19:39:13 +01:00
|
|
|
if self.context and self.context.protocol_later_eq(PRE | 15):
|
|
|
|
self.deprecated()
|
2018-05-27 14:28:01 +02:00
|
|
|
VarInt.send(self.event.id, packet_buffer)
|
|
|
|
self.event.write(packet_buffer)
|
2021-12-16 19:39:13 +01:00
|
|
|
|
|
|
|
@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}]
|