forked from aadnk/PacketWrapper
-
Notifications
You must be signed in to change notification settings - Fork 90
Expand file tree
/
Copy pathChunkPacketProcessor.java
More file actions
402 lines (359 loc) · 12 KB
/
ChunkPacketProcessor.java
File metadata and controls
402 lines (359 loc) · 12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
/*
* PacketWrapper - ProtocolLib wrappers for Minecraft packets
* Copyright (C) dmulloy2 <http://dmulloy2.net>
* Copyright (C) Kristian S. Strangeland
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.comphenix.packetwrapper;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.World.Environment;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.reflect.StructureModifier;
/**
* Used to process a chunk.
*
* @author Kristian
*/
public class ChunkPacketProcessor {
/**
* Contains the offset of the different block data in a chunk packet.
*
* @author Kristian
*/
public static class ChunkOffsets {
private int blockIdOffset;
private int dataOffset;
private int lightOffset;
private int skylightOffset;
private int extraOffset;
private ChunkOffsets(int blockIdOffset, int dataOffset,
int lightOffset, int skylightOffset, int extraOffset) {
this.blockIdOffset = blockIdOffset;
this.dataOffset = dataOffset;
this.lightOffset = lightOffset;
this.skylightOffset = skylightOffset;
this.extraOffset = extraOffset;
}
private void incrementIdIndex() {
blockIdOffset += ChunkPacketProcessor.BLOCK_ID_LENGTH;
dataOffset += ChunkPacketProcessor.BYTES_PER_NIBBLE_PART;
dataOffset += ChunkPacketProcessor.BYTES_PER_NIBBLE_PART;
if (skylightOffset >= 0) {
skylightOffset += ChunkPacketProcessor.BYTES_PER_NIBBLE_PART;
}
}
private void incrementExtraIndex() {
if (extraOffset >= 0) {
extraOffset += ChunkPacketProcessor.BYTES_PER_NIBBLE_PART;
}
}
/**
* Retrieve the starting index of the block ID data.
* <p>
* This will be 4096 bytes in length, one byte for each block in the
* 16x16x16 chunklet.
*
* @return The starting location of the block ID data.
*/
public int getBlockIdOffset() {
return blockIdOffset;
}
/**
* Retrieve the starting index of the meta data (4 bit per block).
* <p>
* This will be 2048 bytes in length, one nibblet for each block in the
* 16x16x16 chunklet.
*
* @return The starting location of the block meta data.
*/
public int getDataOffset() {
return dataOffset;
}
/**
* Retrieve the starting index of the torch light data (4 bit per
* block).
* <p>
* This will be 2048 bytes in length, one nibblet for each block in the
* 16x16x16 chunklet.
*
* @return The starting location of the torch light data.
*/
public int getLightOffset() {
return lightOffset;
}
/**
* Retrieve the starting index of the skylight data (4 bit per block).
* <p>
* This will be 2048 bytes in length if the skylight data exists (see
* {@link #hasSkylightOffset()}), no bytes if not.
*
* @return The starting location of the skylight data.
*/
public int getSkylightOffset() {
return skylightOffset;
}
/**
* Determine if the current chunklet contains skylight data.
*
* @return TRUE if it does, FALSE otherwise.
*/
public boolean hasSkylightOffset() {
return skylightOffset >= 0;
}
/**
* Retrieve the extra 4 bits in each block ID, if necessary.
* <p>
* This will be 2048 bytes in length if the extra data exists, no bytes
* if not.
*
* @return The starting location of the extra data.
*/
public int getExtraOffset() {
return extraOffset;
}
/**
* Determine if the current chunklet contains any extra block ID data.
*
* @return TRUE if it does, FALSE otherwise.
*/
public boolean hasExtraOffset() {
return extraOffset > 0;
}
}
/**
* Process the content of a single 16x16x16 chunklet in a 16x256x16 chunk.
*
* @author Kristian
*/
public interface ChunkletProcessor {
/**
* Process a given chunklet (16x16x16).
*
* @param origin - the block with the lowest x, y and z coordinate in
* the chunklet.
* @param data - the data array.
* @param offsets - the offsets with the data for the given chunklet.
*/
public void processChunklet(Location origin, byte[] data,
ChunkOffsets offsets);
/**
* Process the biome array for a chunk (16x256x16).
* <p>
* This method will not be called if the chunk is missing biome
* information.
*
* @param origin - the block with the lowest x, y and z coordinate in
* the chunk.
* @param data - the data array.
* @param biomeIndex - the starting index of the biome data (256 bytes
* in lenght).
*/
public void processBiomeArray(Location origin, byte[] data,
int biomeIndex);
}
// Useful Minecraft constants
protected static final int BYTES_PER_NIBBLE_PART = 2048;
protected static final int CHUNK_SEGMENTS = 16;
protected static final int NIBBLES_REQUIRED = 4;
public static final int BLOCK_ID_LENGTH = 4096;
/**Misspelled.
* @see #BLOCK_ID_LENGTH
*/
@Deprecated
public static final int BLOCK_ID_LENGHT = BLOCK_ID_LENGTH;
public static final int DATA_LENGTH = 2048;
/**Misspelled.
* @see #DATA_LENGTH
*/
@Deprecated
public static final int DATA_LENGHT = DATA_LENGTH;
public static final int BIOME_ARRAY_LENGTH = 256;
private int chunkX;
private int chunkZ;
private int chunkMask;
private int extraMask;
private int chunkSectionNumber;
private int extraSectionNumber;
private boolean hasContinuous = true;
private int startIndex;
private int size;
private byte[] data;
private World world;
private ChunkPacketProcessor() {
// Use factory methods
}
/**
* Construct a chunk packet processor from a given MAP_CHUNK packet.
*
* @param packet - the map chunk packet.
* @return The chunk packet processor.
*/
public static ChunkPacketProcessor fromMapPacket(PacketContainer packet,
World world) {
if (!packet.getType().equals(PacketType.Play.Server.MAP_CHUNK))
throw new IllegalArgumentException(packet
+ " must be a MAP_CHUNK packet.");
StructureModifier<Integer> ints = packet.getIntegers();
StructureModifier<byte[]> byteArray = packet.getByteArrays();
// Create an info objects
ChunkPacketProcessor processor = new ChunkPacketProcessor();
processor.world = world;
processor.chunkX = ints.read(0); // packet.a;
processor.chunkZ = ints.read(1); // packet.b;
processor.chunkMask = ints.read(2); // packet.c;
processor.extraMask = ints.read(3); // packet.d;
processor.data = byteArray.read(1); // packet.inflatedBuffer;
processor.startIndex = 0;
if (packet.getBooleans().size() > 0) {
processor.hasContinuous = packet.getBooleans().read(0);
}
return processor;
}
/**
* Construct an array of chunk packet processors from a given MAP_CHUNK_BULK
* packet.
*
* @param packet - the map chunk bulk packet.
* @return The chunk packet processors.
*/
// The MAP_CHUNK_BULK packet no longer exists
/*
* public static ChunkPacketProcessor[] fromMapBulkPacket(PacketContainer
* packet, World world) {
* if (!packet.getType().equals(PacketType.Play.Server.MAP_CHUNK_BULK))
* throw new IllegalArgumentException(packet +
* " must be a MAP_CHUNK_BULK packet.");
* StructureModifier<int[]> intArrays = packet.getIntegerArrays();
* StructureModifier<byte[]> byteArrays = packet.getByteArrays();
* int[] x = intArrays.read(0); // packet.c;
* int[] z = intArrays.read(1); // packet.d;
* ChunkPacketProcessor[] processors = new ChunkPacketProcessor[x.length];
* int[] chunkMask = intArrays.read(2); // packet.a;
* int[] extraMask = intArrays.read(3); // packet.b;
* int dataStartIndex = 0;
* for (int chunkNum = 0; chunkNum < processors.length; chunkNum++) {
* // Create an info objects
* ChunkPacketProcessor processor = new ChunkPacketProcessor();
* processors[chunkNum] = processor;
* processor.world = world;
* processor.chunkX = x[chunkNum];
* processor.chunkZ = z[chunkNum];
* processor.chunkMask = chunkMask[chunkNum];
* processor.extraMask = extraMask[chunkNum];
* processor.hasContinuous = true; // Always true
* processor.data = byteArrays.read(1); //packet.buildBuffer;
* // Check for Spigot
* if (processor.data == null || processor.data.length == 0) {
* processor.data =
* packet.getSpecificModifier(byte[][].class).read(0)[chunkNum];
* } else {
* processor.startIndex = dataStartIndex;
* }
* dataStartIndex += processor.size;
* }
* return processors;
* }
*/
/**
* Begin processing the current chunk with the provided processor.
*
* @param processor - the processor that will process the chunk.
*/
public void process(ChunkletProcessor processor) {
// Compute chunk number
for (int i = 0; i < CHUNK_SEGMENTS; i++) {
if ((chunkMask & (1 << i)) > 0) {
chunkSectionNumber++;
}
if ((extraMask & (1 << i)) > 0) {
extraSectionNumber++;
}
}
int skylightCount = getSkylightCount();
// The total size of a chunk is the number of blocks sent (depends on the number of sections) multiplied by the
// amount of bytes per block. This last figure can be calculated by adding together all the data parts:
// For any block:
// * Block ID - 8 bits per block (byte)
// * Block metadata - 4 bits per block (nibble)
// * Block light array - 4 bits per block
// If 'worldProvider.skylight' is TRUE
// * Sky light array - 4 bits per block
// If the segment has extra data:
// * Add array - 4 bits per block
// Biome array - only if the entire chunk (has continous) is sent:
// * Biome array - 256 bytes
//
// A section has 16 * 16 * 16 = 4096 blocks.
size =
BYTES_PER_NIBBLE_PART
* ((NIBBLES_REQUIRED + skylightCount)
* chunkSectionNumber + extraSectionNumber)
+ (hasContinuous ? BIOME_ARRAY_LENGTH : 0);
if ((getOffset(2) - startIndex) > data.length) {
return;
}
// Make sure the chunk is loaded
if (isChunkLoaded(world, chunkX, chunkZ)) {
translate(processor);
}
}
/**
* Retrieve the number of 2048 byte segments per chunklet.
* <p<
* This is usually one for The Overworld, and zero for both The End and The
* Nether.
*
* @return Number of skylight byte segments.
*/
protected int getSkylightCount() {
// There's no sun/moon in the end or in the nether, so Minecraft doesn't sent any skylight information
// This optimization was added in 1.4.6. Note that ideally you should get this from the "f" (skylight) field.
return world.getEnvironment() == Environment.NORMAL ? 1 : 0;
}
private int getOffset(int nibbles) {
return startIndex
+ (nibbles * chunkSectionNumber * ChunkPacketProcessor.BYTES_PER_NIBBLE_PART);
}
private void translate(ChunkletProcessor processor) {
// Loop over 16x16x16 chunks in the 16x256x16 column
int current = 4;
ChunkOffsets offsets =
new ChunkOffsets(getOffset(0), getOffset(2), getOffset(3),
getSkylightCount() > 0 ? getOffset(current++) : -1,
extraSectionNumber > 0 ? getOffset(current++) : -1);
for (int i = 0; i < 16; i++) {
// If the bitmask indicates this chunk is sent
if ((chunkMask & 1 << i) > 0) {
// The lowest block (in x, y, z) in this chunklet
Location origin =
new Location(world, chunkX << 4, i * 16, chunkZ << 4);
processor.processChunklet(origin, data, offsets);
offsets.incrementIdIndex();
}
if ((extraMask & 1 << i) > 0) {
offsets.incrementExtraIndex();
}
}
if (hasContinuous) {
processor.processBiomeArray(new Location(world, chunkX << 4, 0,
chunkZ << 4), data, startIndex + size - BIOME_ARRAY_LENGTH);
}
}
private boolean isChunkLoaded(World world, int x, int z) {
return world.isChunkLoaded(x, z);
}
}