easyprotolib 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- easyprotolib/__init__.py +86 -0
- easyprotolib/advanced_datatypes.py +459 -0
- easyprotolib/basic_datatypes.py +683 -0
- easyprotolib/biome.py +17 -0
- easyprotolib/block.py +129 -0
- easyprotolib/chunk.py +55 -0
- easyprotolib/command.py +91 -0
- easyprotolib/data_packet.py +587 -0
- easyprotolib/err.py +17 -0
- easyprotolib/nbt.py +293 -0
- easyprotolib-0.1.0.dist-info/METADATA +297 -0
- easyprotolib-0.1.0.dist-info/RECORD +15 -0
- easyprotolib-0.1.0.dist-info/WHEEL +5 -0
- easyprotolib-0.1.0.dist-info/licenses/LICENSE +21 -0
- easyprotolib-0.1.0.dist-info/top_level.txt +1 -0
easyprotolib/__init__.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
debug = False
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from .basic_datatypes import MCObject
|
|
5
|
+
|
|
6
|
+
from .basic_datatypes import MCBoolean
|
|
7
|
+
from .basic_datatypes import MCByte, MCUnsignedByte
|
|
8
|
+
from .basic_datatypes import MCShort, MCUnsignedShort
|
|
9
|
+
from .basic_datatypes import MCInt, MCLong
|
|
10
|
+
from .basic_datatypes import MCFloat, MCDouble
|
|
11
|
+
from .basic_datatypes import MCVarInt, MCVarLong
|
|
12
|
+
from .basic_datatypes import MCAngle, MCUUID, MCLpVec3, MCPosition
|
|
13
|
+
from .basic_datatypes import MCString, MCIdentifier, MCJSONTextComponent
|
|
14
|
+
|
|
15
|
+
from .basic_datatypes import MCObjectArray
|
|
16
|
+
from .basic_datatypes import MCByteArray, MCUnsignedByteArray
|
|
17
|
+
from .basic_datatypes import MCLongArray
|
|
18
|
+
from .basic_datatypes import MCVarIntArray
|
|
19
|
+
from .basic_datatypes import MCAngleArray
|
|
20
|
+
from .basic_datatypes import MCIdentifierArray
|
|
21
|
+
from .basic_datatypes import MCUnsignedByteArrayArray
|
|
22
|
+
|
|
23
|
+
from .basic_datatypes import MCBaseBytearray, MCVarBaseBytearray
|
|
24
|
+
from .basic_datatypes import MCDependentObject, MCStruct
|
|
25
|
+
|
|
26
|
+
from .basic_datatypes import MCObjectSetter, MCObjectDuplicator
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
from .nbt import MCNBT, TAGArray
|
|
30
|
+
|
|
31
|
+
from .nbt import TAGEnd
|
|
32
|
+
from .nbt import TAGByte, TAGShort, TAGInt, TAGLong, TAGFloat, TAGDouble
|
|
33
|
+
from .nbt import TAGByteArray
|
|
34
|
+
from .nbt import TAGString
|
|
35
|
+
from .nbt import TAGList
|
|
36
|
+
from .nbt import TAGCompound
|
|
37
|
+
from .nbt import TAGIntArray, TAGLongArray
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
from .advanced_datatypes import MCBitSet
|
|
41
|
+
from .advanced_datatypes import MCPaletteContainer, MCBiomePaletteContainer, MCBlockPaletteContainer
|
|
42
|
+
from .advanced_datatypes import MCProtocolChunkSection
|
|
43
|
+
from .advanced_datatypes import MCChunkData, MCLightData, MCHeightMap
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
from .data_packet import MCDataPackets
|
|
47
|
+
from .data_packet import MCConfig
|
|
48
|
+
from .data_packet import S2C, C2S
|
|
49
|
+
from .data_packet import SIDE_CLIENT, SIDE_SERVER
|
|
50
|
+
from .data_packet import STATE_HANDSHAKE, STATE_STATE, STATE_LOGIN, STATE_PLAY, STATE_CONFIGURATION
|
|
51
|
+
|
|
52
|
+
from .data_packet import MCDataPacket
|
|
53
|
+
from .data_packet import MCCDataPacket, MCSDataPacket
|
|
54
|
+
from .data_packet import MCCStateDataPacket, MCCLoginDataPacket, MCCPlayDataPacket, MCCConfigurationDataPacket
|
|
55
|
+
from .data_packet import MCSHandshakeDataPacket, MCSStateDataPacket, MCSLoginDataPacket, MCSPlayDataPacket, MCSConfigurationDataPacket
|
|
56
|
+
|
|
57
|
+
from .data_packet import MCSHandshake, MCSLegacyServerListPing # State 0
|
|
58
|
+
from .data_packet import MCSRequestStatus, MCSPingStatus # State 1
|
|
59
|
+
from .data_packet import MCSLoginStart, MCSEncryptionResponse, MCSLoginPluginResponse # State 2
|
|
60
|
+
from .data_packet import (MCSTeleportConfirm, MCSSetDifficulty, MCSChatMessage, MCSClientStatus, MCSClientSettings,
|
|
61
|
+
MCSPluginMessage, MCSKeepAlive, MCSPlayerPosition, MCSPlayerPositionAndRotation, MCSPongPlay) # State 3
|
|
62
|
+
|
|
63
|
+
from .data_packet import MCCResponseStatus, MCCPongStatus # State 1
|
|
64
|
+
from .data_packet import MCCDisconnectLogin, MCCEncryptionRequest, MCCLoginSuccess, MCCSetCompression, MCCLoginPluginRequest # State 2
|
|
65
|
+
from .data_packet import (MCCServerDifficulty, MCCChatMessage, MCCDeclareCommands, MCCPluginMessage, MCCDisconnectPlay,
|
|
66
|
+
MCCUnloadChunk, MCCKeepAlive, MCCChunkDataAndUpdateLight, MCCUpdateLight, MCCJoinGame,
|
|
67
|
+
MCCOpenBook, MCCPingPlay, MCCHeldItemChange, MCCTimeUpdate) # State 3
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
from .block import MCBlock
|
|
71
|
+
from .block import has_direction, can_contain_water, full_block, air, liquid
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
from .biome import MCBiome
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
from .chunk import MCChunk, MCChunkSection
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
from .command import MCCommandGraph
|
|
81
|
+
|
|
82
|
+
from .command import (MCCommandLiteralNode, MCCommandArgumentNode, MCCommandIsExecutable,
|
|
83
|
+
MCCommandHasRedirect, MCCommandHasSuggestionsType)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
from .err import MinecraftException, MCProtocolIdNotFound
|
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from .basic_datatypes import MCObject, MCObjectArray, MCVarInt, MCLongArray, MCUnsignedByteArrayArray, MCUnsignedByte, MCShort, MCVarIntArray, MCInt, MCLong
|
|
5
|
+
from .nbt import TAGLongArray, TAGCompound, MCNBT
|
|
6
|
+
from .block import MCBlock, MCBlockEntitiesMap
|
|
7
|
+
import math
|
|
8
|
+
import time
|
|
9
|
+
import array
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _pack(l: list[int], bpe: int) -> list[int]:
|
|
13
|
+
if bpe < 1 or bpe > 64:
|
|
14
|
+
raise ValueError(f"BPE({bpe}) must be between 1 and 64")
|
|
15
|
+
n = len(l)
|
|
16
|
+
if bpe == 4:
|
|
17
|
+
num_longs = (n + 15) >> 4
|
|
18
|
+
result = [0] * num_longs
|
|
19
|
+
for i in range(n):
|
|
20
|
+
long_idx = i >> 4
|
|
21
|
+
bit_idx = (i & 15) << 2
|
|
22
|
+
# 清空目标位
|
|
23
|
+
result[long_idx] &= ~(15 << bit_idx)
|
|
24
|
+
# 写入新值
|
|
25
|
+
result[long_idx] |= l[i] << bit_idx
|
|
26
|
+
else:
|
|
27
|
+
entries_per_long = 64 // bpe
|
|
28
|
+
num_longs = (n + entries_per_long - 1) // entries_per_long
|
|
29
|
+
result = [0] * num_longs
|
|
30
|
+
mask = (1 << bpe) - 1
|
|
31
|
+
for i in range(n):
|
|
32
|
+
long_idx = i // entries_per_long
|
|
33
|
+
bit_idx = (i % entries_per_long) * bpe
|
|
34
|
+
# 清空目标位
|
|
35
|
+
result[long_idx] &= ~(mask << bit_idx)
|
|
36
|
+
# 写入新值
|
|
37
|
+
result[long_idx] |= l[i] << bit_idx
|
|
38
|
+
threshold = 1 << 63
|
|
39
|
+
mask = 1 << 64
|
|
40
|
+
# 返回的列表中整数的取值范围为0 ~ (1 << 64) - 1
|
|
41
|
+
return [x - mask if x >= threshold else x for x in result] # 最后一步: 80~100μs
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _pack_to_bytes_fast(l: list[int], bpe: int) -> bytearray | bytes:
|
|
45
|
+
if bpe < 1 or bpe > 64:
|
|
46
|
+
raise ValueError(f"BPE({bpe}) must be between 1 and 64")
|
|
47
|
+
n = len(l)
|
|
48
|
+
assert n & 7 == 0
|
|
49
|
+
if bpe == 1:
|
|
50
|
+
num = 0
|
|
51
|
+
for bit in l:
|
|
52
|
+
num = (num << 1) | bit
|
|
53
|
+
result = num.to_bytes(n >> 3, 'big')
|
|
54
|
+
# 可以保证n >> 3 << 3 == n
|
|
55
|
+
return MCVarInt(((n >> 3) + 7) >> 3) + result + b'\x00' * ((8 - ((n >> 3) & 7)) & 7)
|
|
56
|
+
elif bpe == 4:
|
|
57
|
+
num_longs = (n + 15) >> 4 << 3
|
|
58
|
+
result = bytearray(num_longs)
|
|
59
|
+
for i in range(0, n, 2):
|
|
60
|
+
result[i >> 1] = (l[i] << 4) | l[i + 1]
|
|
61
|
+
# 可以保证不存在奇数尾巴
|
|
62
|
+
return MCVarInt(num_longs >> 3) + result
|
|
63
|
+
elif bpe == 8:
|
|
64
|
+
return MCVarInt(n >> 3) + bytes(l)
|
|
65
|
+
else:
|
|
66
|
+
entries_per_long = 64 // bpe
|
|
67
|
+
num_longs = (n + entries_per_long - 1) // entries_per_long
|
|
68
|
+
result = [0] * num_longs
|
|
69
|
+
for i in range(n):
|
|
70
|
+
long_idx = i // entries_per_long
|
|
71
|
+
bit_idx = (i % entries_per_long) * bpe
|
|
72
|
+
# 写入新值
|
|
73
|
+
result[long_idx] |= l[i] << bit_idx
|
|
74
|
+
result = array.array("Q", result)
|
|
75
|
+
if sys.byteorder != "big":
|
|
76
|
+
result.byteswap()
|
|
77
|
+
return MCVarInt(num_longs) + result.tobytes()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _unpack(l: list[int], bpe: int, length: int) -> list[int]:
|
|
81
|
+
if bpe < 1 or bpe > 64:
|
|
82
|
+
raise ValueError(f"BPE({bpe}) must be between 1 and 64")
|
|
83
|
+
entries_per_long = 64 // bpe
|
|
84
|
+
mask = (1 << bpe) - 1
|
|
85
|
+
result = [0] * length
|
|
86
|
+
for i in range(length):
|
|
87
|
+
long_idx = i // entries_per_long
|
|
88
|
+
bit_idx = (i % entries_per_long) * bpe
|
|
89
|
+
result[i] = (l[long_idx] >> bit_idx) & mask
|
|
90
|
+
return result
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class MCBitSet(MCLongArray):
|
|
94
|
+
def __init__(self, data: list[bool] | tuple[bool]):
|
|
95
|
+
n = len(data)
|
|
96
|
+
if n == 0:
|
|
97
|
+
super().__init__([])
|
|
98
|
+
return
|
|
99
|
+
num_longs = (n + 0x3f) >> 6
|
|
100
|
+
result = [0] * num_longs
|
|
101
|
+
for i in range(num_longs):
|
|
102
|
+
start = i << 6
|
|
103
|
+
end = min(start + 0x40, n)
|
|
104
|
+
val = 0
|
|
105
|
+
for j in range(start, end):
|
|
106
|
+
if data[j]:
|
|
107
|
+
val |= (1 << (j - start))
|
|
108
|
+
result[i] = val
|
|
109
|
+
super().__init__(result)
|
|
110
|
+
|
|
111
|
+
@classmethod
|
|
112
|
+
def _obj_deserialization(cls, data: bytearray, bit_count=-1) -> tuple[list[bool], int]:
|
|
113
|
+
length, offset = MCVarInt._obj_deserialization(data)
|
|
114
|
+
longs = []
|
|
115
|
+
for i in range(length):
|
|
116
|
+
longs.append(MCLong._obj_deserialization(data[offset:offset + 8])[0])
|
|
117
|
+
offset += 8
|
|
118
|
+
bits = [False] * bit_count
|
|
119
|
+
for i in range(bit_count):
|
|
120
|
+
long_idx = i >> 6
|
|
121
|
+
bit_offset = i & 0x3f
|
|
122
|
+
if long_idx < len(longs):
|
|
123
|
+
bit = (longs[long_idx] >> bit_offset) & 1
|
|
124
|
+
bits[i] = bit == 1
|
|
125
|
+
return bits, offset
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# noinspection DuplicatedCode
|
|
129
|
+
class MCLightData(MCObject):
|
|
130
|
+
|
|
131
|
+
def __init__(self, sky_light: list[int], block_light: list[int], section_count: int=24, fast_mode=False):
|
|
132
|
+
super().__init__((sky_light, block_light, section_count))
|
|
133
|
+
self.fast_mode = fast_mode
|
|
134
|
+
|
|
135
|
+
def _obj_serialization(self) -> bytearray:
|
|
136
|
+
sky_light_, block_light_, section_count = self.data
|
|
137
|
+
|
|
138
|
+
total_sections = section_count + 2
|
|
139
|
+
# 初始化掩码
|
|
140
|
+
sky_mask = [True] * total_sections
|
|
141
|
+
block_mask = [True] * total_sections
|
|
142
|
+
|
|
143
|
+
assert len(sky_light_) == len(block_light_)
|
|
144
|
+
|
|
145
|
+
if self.fast_mode:
|
|
146
|
+
empty_sky_mask = [False] * total_sections
|
|
147
|
+
empty_block_mask = [False] * total_sections
|
|
148
|
+
sky_arrays = [[(sky_light_[j + 1] << 4) | sky_light_[j] for j in range(i << 1, (i << 1) + 4096, 2)] for i in range(0, total_sections << 11, 2048)]
|
|
149
|
+
block_arrays = [[(block_light_[j + 1] << 4) | block_light_[j] for j in range(i << 1, (i << 1) + 4096, 2)] for i in range(0, total_sections << 11, 2048)]
|
|
150
|
+
# 假设光照数据确实严格在0~15范围内
|
|
151
|
+
# -5~256 在 CPython 中是永生对象, 不会重复创建和销毁
|
|
152
|
+
else:
|
|
153
|
+
empty_sky_mask = [True] * total_sections
|
|
154
|
+
empty_block_mask = [True] * total_sections
|
|
155
|
+
l = 0
|
|
156
|
+
sky_arrays = []
|
|
157
|
+
block_arrays = []
|
|
158
|
+
for i in range(0, len(sky_light_), 2):
|
|
159
|
+
a = l >> 11
|
|
160
|
+
byte_val = ((sky_light_[i + 1] & 0x0F) << 4) | (sky_light_[i] & 0x0F) # 高4位放第二个,低4位放第一个
|
|
161
|
+
if byte_val != 0:
|
|
162
|
+
empty_sky_mask[a] = False
|
|
163
|
+
try:
|
|
164
|
+
sky_arrays[a].append(byte_val)
|
|
165
|
+
except IndexError:
|
|
166
|
+
sky_arrays.append([byte_val])
|
|
167
|
+
|
|
168
|
+
byte_val = ((block_light_[i + 1] & 0x0F) << 4) | (block_light_[i] & 0x0F) # 高4位放第二个,低4位放第一个
|
|
169
|
+
if byte_val != 0:
|
|
170
|
+
empty_block_mask[a] = False
|
|
171
|
+
try:
|
|
172
|
+
block_arrays[a].append(byte_val)
|
|
173
|
+
except IndexError:
|
|
174
|
+
block_arrays.append([byte_val])
|
|
175
|
+
l += 1
|
|
176
|
+
|
|
177
|
+
result = MCBitSet(sky_mask).serialization()
|
|
178
|
+
result += MCBitSet(block_mask)
|
|
179
|
+
result += MCBitSet(empty_sky_mask)
|
|
180
|
+
result += MCBitSet(empty_block_mask)
|
|
181
|
+
try:
|
|
182
|
+
result += MCUnsignedByteArrayArray(sky_arrays)
|
|
183
|
+
result += MCUnsignedByteArrayArray(block_arrays)
|
|
184
|
+
except OverflowError as e:
|
|
185
|
+
if self.fast_mode:
|
|
186
|
+
print("部分光照强度可能不在0~15之内, 快速模式关闭! 这是预期中的吗? ")
|
|
187
|
+
self.fast_mode = False
|
|
188
|
+
return self._obj_serialization()
|
|
189
|
+
raise e
|
|
190
|
+
# result 的组装耗时: 100~130ms -> 1~2ms
|
|
191
|
+
return result # 方法总耗时: 240~350ms -> 200~250ms -> 130~160ms -> 30~50ms -> 15~22ms(FAST MODE) -> 13~15ms(FAST MODE)
|
|
192
|
+
|
|
193
|
+
@classmethod
|
|
194
|
+
def _obj_deserialization(cls, data: bytearray, section_count: int=24) -> tuple[tuple[list[int], list[int]], int]:
|
|
195
|
+
sky_mask, offset = MCBitSet._obj_deserialization(data, section_count + 2)
|
|
196
|
+
block_mask, l = MCBitSet._obj_deserialization(data[offset:], section_count + 2)
|
|
197
|
+
offset += l
|
|
198
|
+
empty_sky_mask, l = MCBitSet._obj_deserialization(data[offset:], section_count + 2)
|
|
199
|
+
offset += l
|
|
200
|
+
empty_block_mask, l = MCBitSet._obj_deserialization(data[offset:], section_count + 2)
|
|
201
|
+
offset += l
|
|
202
|
+
sky_arrays, l = MCUnsignedByteArrayArray._obj_deserialization(data[offset:])
|
|
203
|
+
offset += l
|
|
204
|
+
block_arrays, l = MCUnsignedByteArrayArray._obj_deserialization(data[offset:])
|
|
205
|
+
offset += l
|
|
206
|
+
|
|
207
|
+
# 验证长度一致
|
|
208
|
+
if not (len(block_mask) == len(empty_sky_mask) == len(empty_block_mask) == len(sky_mask)):
|
|
209
|
+
raise ValueError("所有掩码的长度必须相同")
|
|
210
|
+
|
|
211
|
+
sky_light = []
|
|
212
|
+
block_light = []
|
|
213
|
+
|
|
214
|
+
sky_idx = 0
|
|
215
|
+
block_idx = 0
|
|
216
|
+
|
|
217
|
+
for i in range(len(sky_mask)):
|
|
218
|
+
if sky_mask[i]:
|
|
219
|
+
if sky_idx >= len(sky_arrays):
|
|
220
|
+
raise IndexError(f"sky_arrays 不足:期望第 {sky_idx} 段,但只有 {len(sky_arrays)} 段")
|
|
221
|
+
compressed_sky = sky_arrays[sky_idx]
|
|
222
|
+
sky_idx += 1
|
|
223
|
+
if empty_sky_mask[i]:
|
|
224
|
+
sky_light.extend([0] * 4096)
|
|
225
|
+
else:
|
|
226
|
+
sky_light.extend(cls.decompress_light_section(compressed_sky))
|
|
227
|
+
else:
|
|
228
|
+
sky_light.extend([0] * 4096)
|
|
229
|
+
if block_mask[i]:
|
|
230
|
+
if block_idx >= len(block_arrays):
|
|
231
|
+
raise IndexError(f"block_arrays 不足:期望第 {block_idx} 段,但只有 {len(block_arrays)} 段")
|
|
232
|
+
compressed_block = block_arrays[block_idx]
|
|
233
|
+
block_idx += 1
|
|
234
|
+
|
|
235
|
+
if empty_block_mask[i]:
|
|
236
|
+
block_light.extend([0] * 4096)
|
|
237
|
+
else:
|
|
238
|
+
block_light.extend(cls.decompress_light_section(compressed_block))
|
|
239
|
+
else:
|
|
240
|
+
block_light.extend([0] * 4096)
|
|
241
|
+
if sky_idx != len(sky_arrays):
|
|
242
|
+
raise ValueError(f"sky_arrays 未完全消耗:期望 {len(sky_arrays)} 段,实际消耗 {sky_idx} 段")
|
|
243
|
+
if block_idx != len(block_arrays):
|
|
244
|
+
raise ValueError(f"block_arrays 未完全消耗:期望 {len(block_arrays)} 段,实际消耗 {block_idx} 段")
|
|
245
|
+
|
|
246
|
+
return (sky_light, block_light), offset
|
|
247
|
+
|
|
248
|
+
@staticmethod
|
|
249
|
+
def decompress_light_section(compressed: list[int]) -> list[int]:
|
|
250
|
+
if len(compressed) != 2048:
|
|
251
|
+
raise ValueError("每个子区块的压缩数据必须为 2048 字节")
|
|
252
|
+
|
|
253
|
+
result = [0] * 4096
|
|
254
|
+
for i, byte_val in enumerate(compressed):
|
|
255
|
+
result[2 * i] = byte_val & 0x0F # 低4位
|
|
256
|
+
result[2 * i + 1] = (byte_val >> 4) & 0x0F # 高4位
|
|
257
|
+
return result
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
class MCHeightMap(MCObject):
|
|
261
|
+
world_height = -1
|
|
262
|
+
|
|
263
|
+
def __init__(self, heightmap: dict[str, list[int]]):
|
|
264
|
+
"""heightmap 期望的y坐标是已经减去世界最低坐标的偏移值"""
|
|
265
|
+
# 高度图在高版本不再是NBT了, 但在1.18.2中, 高度图仍然是NBT
|
|
266
|
+
if self.world_height == -1:
|
|
267
|
+
raise ValueError("请设置正确的 world_height 值")
|
|
268
|
+
super().__init__(heightmap)
|
|
269
|
+
|
|
270
|
+
def _obj_serialization(self) -> bytearray:
|
|
271
|
+
heightmap_ = self.data
|
|
272
|
+
world_height = self.world_height
|
|
273
|
+
bits_per_entry = math.ceil(math.log2(world_height + 1))
|
|
274
|
+
result = []
|
|
275
|
+
for name in heightmap_:
|
|
276
|
+
heightmap = heightmap_[name]
|
|
277
|
+
if len(heightmap) != 256:
|
|
278
|
+
raise ValueError("高度图必须恰好包含256个条目(16*16)")
|
|
279
|
+
result.append(TAGLongArray(name, _pack(heightmap, bits_per_entry)))
|
|
280
|
+
return TAGCompound("", result).serialization() # 0~1ms
|
|
281
|
+
|
|
282
|
+
@classmethod
|
|
283
|
+
def _obj_deserialization(cls, data: bytearray) -> tuple[dict[str, list[int]], int]:
|
|
284
|
+
bits_per_entry = math.ceil(math.log2(cls.world_height + 1))
|
|
285
|
+
result = MCNBT._obj_deserialization(data)
|
|
286
|
+
result2 = {result[0][2][0].name: _unpack(result[0][2][0].data, bits_per_entry, 256), result[0][2][1].name: _unpack(
|
|
287
|
+
result[0][2][1].data, bits_per_entry, 256)}
|
|
288
|
+
return result2, result[1]
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
class MCPaletteContainer(MCObject):
|
|
292
|
+
min_bpe = -1 # 间接模式最小BPE
|
|
293
|
+
max_bpe = -1 # 间接模式最大BPE
|
|
294
|
+
bpe = -1 # 直接模式最小BPE
|
|
295
|
+
length = -1
|
|
296
|
+
def __init__(self, data: list[int]):
|
|
297
|
+
# data中存放全局id
|
|
298
|
+
super().__init__(data)
|
|
299
|
+
|
|
300
|
+
def _obj_serialization(self) -> bytearray:
|
|
301
|
+
result = bytearray(b'')
|
|
302
|
+
i = list(set(self.data))
|
|
303
|
+
if len(i) == 1:
|
|
304
|
+
bpe = 0
|
|
305
|
+
else:
|
|
306
|
+
bpe = max(self.min_bpe, math.ceil(math.log2(len(i))))
|
|
307
|
+
if bpe > self.max_bpe:
|
|
308
|
+
# 直接模式
|
|
309
|
+
bpe = max(self.bpe, bpe)
|
|
310
|
+
result += MCUnsignedByte(bpe)
|
|
311
|
+
result += _pack_to_bytes_fast(self.data, bpe) # 在1.21.5+, 这个数组不带长度前缀, 不过我们实现的协议版本是1.18.2
|
|
312
|
+
elif bpe > 0:
|
|
313
|
+
# 间接模式
|
|
314
|
+
result += MCUnsignedByte(bpe)
|
|
315
|
+
result += MCVarIntArray(i)
|
|
316
|
+
if len(i) > 20:
|
|
317
|
+
i = {k: j for j, k in enumerate(i)}
|
|
318
|
+
data = [i[j] for j in self.data]
|
|
319
|
+
else:
|
|
320
|
+
data = [i.index(j) for j in self.data] # 190~220μs
|
|
321
|
+
result += _pack_to_bytes_fast(data, bpe) # 1.8~2.1ms -> 1.6~1.8ms -> 330~500μs
|
|
322
|
+
else:
|
|
323
|
+
# 单值模式
|
|
324
|
+
result += MCUnsignedByte(0)
|
|
325
|
+
result += MCVarInt(i[0])
|
|
326
|
+
result += MCInt(0) # 我不知道为什么要加上这个MCInt(0), 这也不是协议里的标准内容, 但是加上这个之后, 程序就能跑了!(什么鬼...)
|
|
327
|
+
return result
|
|
328
|
+
|
|
329
|
+
@classmethod
|
|
330
|
+
def _obj_deserialization(cls, data: bytearray) -> tuple[list[int], int]:
|
|
331
|
+
bpe, offset = MCUnsignedByte._obj_deserialization(data)
|
|
332
|
+
if bpe >= cls.bpe:
|
|
333
|
+
result, l = MCLongArray._obj_deserialization(data[offset:])
|
|
334
|
+
offset += l
|
|
335
|
+
result = _unpack(result, bpe, cls.length)
|
|
336
|
+
elif bpe == 0:
|
|
337
|
+
result, l = MCVarInt._obj_deserialization(data[offset:])
|
|
338
|
+
offset += l
|
|
339
|
+
_, l = MCInt._obj_deserialization(data[offset:])
|
|
340
|
+
offset += l
|
|
341
|
+
result = [result] * cls.length
|
|
342
|
+
elif cls.min_bpe <= bpe <= cls.max_bpe:
|
|
343
|
+
i, l = MCVarIntArray._obj_deserialization(data[offset:])
|
|
344
|
+
offset += l
|
|
345
|
+
result, l = MCLongArray._obj_deserialization(data[offset:])
|
|
346
|
+
offset += l
|
|
347
|
+
result = _unpack(result, bpe, cls.length)
|
|
348
|
+
result = [i[j] for j in result]
|
|
349
|
+
else:
|
|
350
|
+
raise ValueError("BPE不正确")
|
|
351
|
+
return result, offset
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
class MCBlockPaletteContainer(MCPaletteContainer):
|
|
355
|
+
min_bpe = 4 # 间接模式最小BPE
|
|
356
|
+
max_bpe = 8 # 间接模式最大BPE
|
|
357
|
+
bpe = 15 # 直接模式最小BPE
|
|
358
|
+
length = 4096 # 一旦正确配置了length, 该类型的反序列化方法就能正常运作, 但我懒得配置了(), 以后补上:D (归档)但已于2026/7/23补上
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
class MCBiomePaletteContainer(MCPaletteContainer):
|
|
362
|
+
min_bpe = 1 # 间接模式最小BPE
|
|
363
|
+
max_bpe = 3 # 间接模式最大BPE
|
|
364
|
+
bpe = 7 # 直接模式最小BPE
|
|
365
|
+
length = 64
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
class MCProtocolChunkSection(MCObject):
|
|
369
|
+
def __init__(self, cs):
|
|
370
|
+
super().__init__(cs)
|
|
371
|
+
|
|
372
|
+
def _obj_serialization(self) -> bytearray:
|
|
373
|
+
result = bytearray(b'')
|
|
374
|
+
cs = self.data
|
|
375
|
+
result += MCShort(cs.get_block_count() - cs.get_air_count()) # 1~2ms -> 0~1ms -> 7~8μs -> 3~5μs
|
|
376
|
+
result += MCBlockPaletteContainer(cs.blocks_id) # 5~6ms -> 4~5ms -> 1.6~2.0ms
|
|
377
|
+
result += MCBiomePaletteContainer(cs.biomes_id) # 0ms -> 100~120μs -> 20~24μs
|
|
378
|
+
return result # 11~15ms -> 4~7ms -> 3~4ms -> 2.5~3.0ms -> 1.7~2.0ms
|
|
379
|
+
|
|
380
|
+
@staticmethod
|
|
381
|
+
def _obj_deserialization(data: bytearray) -> tuple[tuple[int, list[int], list[int]], int]:
|
|
382
|
+
block_count, offset = MCShort.deserialization(data)
|
|
383
|
+
block_palette, l = MCBlockPaletteContainer.deserialization(data[offset:])
|
|
384
|
+
offset += l
|
|
385
|
+
biome_palette, l = MCBiomePaletteContainer.deserialization(data[offset:])
|
|
386
|
+
offset += l
|
|
387
|
+
return (block_count, block_palette, biome_palette), offset
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
class MCChunkData(MCObject):
|
|
391
|
+
def __init__(self, chunk):
|
|
392
|
+
super().__init__(chunk)
|
|
393
|
+
|
|
394
|
+
def _obj_serialization(self) -> bytearray:
|
|
395
|
+
chunk = self.data
|
|
396
|
+
result = bytearray(b'')
|
|
397
|
+
for i in chunk.chunk_sections:
|
|
398
|
+
result += MCProtocolChunkSection(i) # 300~360ms -> 140~180ms -> 2ms
|
|
399
|
+
return MCVarInt(len(result)) + result
|
|
400
|
+
|
|
401
|
+
@staticmethod
|
|
402
|
+
def _obj_deserialization(data: bytearray) -> tuple[list[tuple[int, list[int], list[int]]], int]:
|
|
403
|
+
length, offset = MCVarInt.deserialization(data)
|
|
404
|
+
offset2 = 0
|
|
405
|
+
result = []
|
|
406
|
+
while offset2 < length:
|
|
407
|
+
pcs, l = MCProtocolChunkSection.deserialization(data)
|
|
408
|
+
result.append(pcs)
|
|
409
|
+
offset2 += l
|
|
410
|
+
return result, offset + offset2
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
class MCBlockEntity(MCObject):
|
|
414
|
+
def __init__(self, block: MCBlock):
|
|
415
|
+
if not block.is_block_entity:
|
|
416
|
+
raise ValueError("方块不是方块实体")
|
|
417
|
+
super().__init__(block)
|
|
418
|
+
|
|
419
|
+
def _obj_serialization(self) -> bytearray:
|
|
420
|
+
block: MCBlock = self.data
|
|
421
|
+
result = MCUnsignedByte(((block.x & 0x0f) << 4) | (block.z & 0x0f)).serialization()
|
|
422
|
+
result += MCShort(block.y)
|
|
423
|
+
result += MCVarInt(block.block_entity_id)
|
|
424
|
+
result += block.block_entity_data
|
|
425
|
+
return result
|
|
426
|
+
|
|
427
|
+
@staticmethod
|
|
428
|
+
def _obj_deserialization(data: bytearray) -> tuple[MCBlock, int]:
|
|
429
|
+
packed_xz, offset = MCUnsignedByte.deserialization(data)
|
|
430
|
+
x, z = packed_xz >> 4, packed_xz & 0x0f
|
|
431
|
+
y, l = MCShort.deserialization(data[offset:])
|
|
432
|
+
offset += l
|
|
433
|
+
id, l = MCVarInt.deserialization(data[offset:])
|
|
434
|
+
offset += l
|
|
435
|
+
nbt_data, l = MCNBT.deserialization(data[offset:])
|
|
436
|
+
offset += l
|
|
437
|
+
block: MCBlock = MCBlockEntitiesMap.get(id)()
|
|
438
|
+
block._set_block_pos(x, z)
|
|
439
|
+
block._set_y(y)
|
|
440
|
+
block.block_entity_data = TAGCompound("", nbt_data)
|
|
441
|
+
return block, offset
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
class MCBlockEntities(MCObjectArray):
|
|
445
|
+
MCObjectType = MCBlockEntity
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
if __name__ == "__main__":
|
|
449
|
+
data1 = [0] * (4096 * 26)
|
|
450
|
+
for i in range(1024):
|
|
451
|
+
data1[i] = 15
|
|
452
|
+
|
|
453
|
+
data2 = [0] * (4096 * 26)
|
|
454
|
+
for i in range(1024):
|
|
455
|
+
data2[i] = 15
|
|
456
|
+
|
|
457
|
+
t = time.perf_counter()
|
|
458
|
+
a = MCLightData(data1, data2).serialization()
|
|
459
|
+
print(time.perf_counter()-t)
|