Created
May 17, 2013 04:42
-
-
Save aadnk/5596971 to your computer and use it in GitHub Desktop.
Disguise a block (like a chest) as an arbitrary block.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.comphenix.example; | |
import java.nio.ByteBuffer; | |
import java.nio.IntBuffer; | |
import org.bukkit.Location; | |
import org.bukkit.World; | |
/** | |
* Utility class for creating arrays of block changes. | |
* <p> | |
* See also {@link Packet34MultiBlockChange}. | |
* | |
* @author Kristian | |
*/ | |
public class BlockChangeArray { | |
/** | |
* Represents a single block change. | |
* <p> | |
* Retrieved by {@link BlockChangeArray#getBlockChange(int)}. | |
* | |
* @author Kristian | |
*/ | |
public class BlockChange { | |
// Index of the block change entry that we may change | |
private final int index; | |
private BlockChange(int index) { | |
this.index = index; | |
} | |
/** | |
* Set the location of the block change. | |
* <p< | |
* The coordinates will be correctly converted to relative coordinates, provided that all the blocks | |
* are from the same chunk (16x16 column of blocks). | |
* @param loc - location. | |
* @return This block change, for chaining. | |
*/ | |
public BlockChange setLocation(Location loc) { | |
setRelativeX(loc.getBlockX() & 0xF); | |
setRelativeZ(loc.getBlockZ() & 0xF); | |
setAbsoluteY(loc.getBlockY()); | |
return this; | |
} | |
/** | |
* Retrieve the location of this block change. | |
* <p> | |
* The world and absolute chunk position must be provided. | |
* @param world - the world the block belongs to. | |
* @param chunkX - the x position of the origin chunk | |
* @param chunkZ - the y position of the origin chunk | |
* @return The location. | |
*/ | |
public Location getLocation(World world, int chunkX, int chunkZ) { | |
if (world == null) | |
throw new IllegalArgumentException("World cannot be NULL."); | |
return new Location( | |
world, | |
(chunkX << 4) + getRelativeX(), | |
getAbsoluteY(), | |
(chunkZ << 4) + getRelativeZ() | |
); | |
} | |
/** | |
* Set the relative x-axis position of current block change in the chunk. | |
* @param relativeX - relative block change location. | |
* @return This block change, for chaining. | |
*/ | |
public BlockChange setRelativeX(int relativeX) { | |
setValue(relativeX, 28, 0xF0000000); | |
return this; | |
} | |
/** | |
* Retrieve the relative x-axis position of the current block change. | |
* @return X-axis position of the block change. | |
*/ | |
public int getRelativeX() { | |
return getValue(28, 0xF0000000); | |
} | |
/** | |
* Set the relative z-axis position of current block change in the chunk. | |
* @param relativeZ - relative block change location. | |
* @return This block change, for chaining. | |
*/ | |
public BlockChange setRelativeZ(int relativeX) { | |
setValue(relativeX, 24, 0xF000000); | |
return this; | |
} | |
/** | |
* Retrieve the relative z-axis position of the current block change. | |
* @return Z-axis position of the block change. | |
*/ | |
public byte getRelativeZ() { | |
return (byte) getValue(24, 0xF000000); | |
} | |
/** | |
* Set the absolute y-axis position of the current block change. | |
* @param absoluteY - the absolute y-axis position. | |
* @return This block change, for chaining. | |
*/ | |
public BlockChange setAbsoluteY(int absoluteY) { | |
setValue(absoluteY, 16, 0xFF0000); | |
return this; | |
} | |
/** | |
* Retrieve the absolute y-axis position of the current block change. | |
* @return Y-axis position of the block change. | |
*/ | |
public int getAbsoluteY() { | |
return getValue(16, 0xFF0000); | |
} | |
/** | |
* Set the block ID of the current block change. | |
* @param blockID - ID that the changed block will have. | |
* @return This block change, for chaining. | |
*/ | |
public BlockChange setBlockID(int blockID) { | |
setValue(blockID, 4, 0xFFF0); | |
return this; | |
} | |
/** | |
* Retrieve the block ID of the current block change. | |
* @return The block ID that the block will change into. | |
*/ | |
public int getBlockID() { | |
return getValue(4, 0xFFF0); | |
} | |
/** | |
* Set the block metadata of the current block change. | |
* @param metadata - metadata that the changed block will have. | |
* @return This block change, for chaining. | |
*/ | |
public BlockChange setMetadata(int metadata) { | |
setValue(metadata, 0, 0xF); | |
return this; | |
} | |
/** | |
* Retrieve the block metadata of the current block change. | |
* @return The block metadata that the block will change into. | |
*/ | |
public int getMetadata() { | |
return getValue(0, 0xF); | |
} | |
/** | |
* Retrieve the index of the current block change. | |
* @return Index of the current block change. | |
*/ | |
public int getIndex() { | |
return index; | |
} | |
/** | |
* Retrieve the integer representation of this block change. | |
* @return Integer representation. | |
*/ | |
private int asInteger() { | |
return data[index]; | |
} | |
// Should be inlined | |
private void setValue(int value, int leftShift, int updateMask) { | |
data[index] = ((value << leftShift) & updateMask) | (data[index] & ~updateMask); | |
} | |
private int getValue(int rightShift, int updateMask) { | |
return (data[index] & updateMask) >> rightShift; | |
} | |
} | |
/** | |
* Single of a single block change record in bytes. | |
*/ | |
private static final int RECORD_SIZE = 4; | |
/** | |
* The internally backed array. | |
*/ | |
private int[] data; | |
/** | |
* Construct a new array of block changes. | |
* @param blockChanges - the number of blocks that have been changed. | |
*/ | |
public BlockChangeArray(int blockChanges) { | |
data = new int[blockChanges]; | |
} | |
/** | |
* Construct a new block change array from the copy of a given data array. | |
* @param data - the data array to store internally. | |
*/ | |
public BlockChangeArray(byte[] input) { | |
if ((input.length % RECORD_SIZE) != 0) | |
throw new IllegalArgumentException("The lenght of the input data array should be a multiple of " + RECORD_SIZE + "."); | |
IntBuffer source = ByteBuffer.wrap(input).asIntBuffer(); | |
IntBuffer destination = IntBuffer.allocate(input.length / RECORD_SIZE); | |
destination.put(source); | |
// Get the copied array | |
data = destination.array(); | |
} | |
/** | |
* Retrieve a view of the block change entry at the given index. | |
* <p> | |
* Any modification to this view will be stored in the block change array itself. | |
* @param index - index of the block change to retrieve. | |
* @return A view of the block change entry. | |
*/ | |
public BlockChange getBlockChange(int index) { | |
if (index < 0 || index >= getSize()) | |
throw new IllegalArgumentException("Index is out of bounds."); | |
return new BlockChange(index); | |
} | |
/** | |
* Set the block change at the specified index to contain the given block. | |
* @param loc - the location that will be converted. | |
* @param block - the new content of the block change. | |
*/ | |
public void setBlockChange(int index, BlockChange change) { | |
if (change == null) | |
throw new IllegalArgumentException("Block change cannot be NULL."); | |
data[index] = change.asInteger(); | |
} | |
/** | |
* Retrieve the number of block changes. | |
* @return The number of block changes. | |
*/ | |
public int getSize() { | |
return data.length; | |
} | |
/** | |
* Convert this block change array to a byte array. | |
* @return The resulting byte array. | |
*/ | |
public byte[] toByteArray() { | |
ByteBuffer copy = ByteBuffer.allocate(data.length * RECORD_SIZE); | |
// Copy in the integer array | |
copy.asIntBuffer().put(data); | |
return copy.array(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.comphenix.example; | |
import java.io.Serializable; | |
import java.util.Arrays; | |
import org.bukkit.Location; | |
import org.bukkit.World; | |
import org.bukkit.block.Block; | |
class BlockCoordinate implements Serializable { | |
private static final long serialVersionUID = 1L; | |
private final int x; | |
private final int y; | |
private final int z; | |
public BlockCoordinate(int x, int y, int z) { | |
this.x = x; | |
this.y = y; | |
this.z = z; | |
} | |
public BlockCoordinate(Location loc) { | |
this(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()); | |
} | |
@Override | |
public int hashCode() { | |
return Arrays.hashCode(new int[] {x, y, z }); | |
} | |
@Override | |
public boolean equals(Object obj) { | |
if (this == obj) | |
return true; | |
if (obj instanceof BlockCoordinate) { | |
BlockCoordinate other = (BlockCoordinate) obj; | |
return x == other.x && y == other.y && z == other.z; | |
} | |
return true; | |
} | |
public int getX() { | |
return x; | |
} | |
public int getY() { | |
return y; | |
} | |
public int getZ() { | |
return z; | |
} | |
public Block toBlock(World world) { | |
return world.getBlockAt(x, y, z); | |
} | |
@Override | |
public String toString() { | |
return "[x: " + x + ", y: " + y + ", z: " + z + "]"; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.comphenix.example; | |
import java.io.BufferedInputStream; | |
import java.io.BufferedOutputStream; | |
import java.io.File; | |
import java.io.FileInputStream; | |
import java.io.FileOutputStream; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.io.OutputStream; | |
import org.apache.commons.lang.SerializationUtils; | |
import org.bukkit.Location; | |
import org.bukkit.World; | |
import org.bukkit.plugin.Plugin; | |
import com.comphenix.example.BlockChangeArray.BlockChange; | |
import com.comphenix.example.ChunkPacketProcessor.ChunkletProcessor; | |
import com.comphenix.protocol.Packets; | |
import com.comphenix.protocol.ProtocolLibrary; | |
import com.comphenix.protocol.events.ConnectionSide; | |
import com.comphenix.protocol.events.PacketAdapter; | |
import com.comphenix.protocol.events.PacketContainer; | |
import com.comphenix.protocol.events.PacketEvent; | |
import com.comphenix.protocol.reflect.FieldAccessException; | |
import com.comphenix.protocol.reflect.StructureModifier; | |
import com.google.common.collect.HashBasedTable; | |
/** | |
* Simple class that can be used to alter the apperance of a number of blocks. | |
* @author Kristian | |
*/ | |
public class BlockDisguiser { | |
private HashBasedTable<ChunkCoordinate, BlockCoordinate, Integer> translations = HashBasedTable.create(); | |
// The current listener | |
private PacketAdapter listener; | |
/** | |
* Construct a new block changer. | |
* @param parent - the owner plugin. | |
*/ | |
public BlockDisguiser(Plugin parent) { | |
registerListener(parent); | |
} | |
@SuppressWarnings("unchecked") | |
public void loadState(InputStream stream) { | |
translations = (HashBasedTable<ChunkCoordinate, BlockCoordinate, Integer>) | |
SerializationUtils.deserialize(stream); | |
} | |
public void loadState(File source) throws IOException { | |
InputStream io = null; | |
try { | |
io = new BufferedInputStream(new FileInputStream(source)); | |
loadState(io); | |
} finally { | |
if (io != null) { | |
io.close(); | |
} | |
} | |
} | |
public void saveState(OutputStream stream) { | |
SerializationUtils.serialize(translations, stream); | |
} | |
public void saveState(File destination) throws IOException { | |
OutputStream io = null; | |
try { | |
io = new BufferedOutputStream(new FileOutputStream(destination)); | |
saveState(io); | |
} finally { | |
if (io != null) { | |
io.close(); | |
} | |
} | |
} | |
/** | |
* Create a new translated block that have a different block ID on the server and visually for a client. | |
* @param loc - the location of the block. | |
* @param serverBlockID - the block ID on the server side. | |
* @param clientBlockID - the block ID on the client side. | |
*/ | |
public void setTranslatedBlock(Location loc, int serverBlockID, int clientBlockID) { | |
// Make this block appear as the client block | |
translations.put(ChunkCoordinate.fromBlock(loc), new BlockCoordinate(loc), clientBlockID); | |
// Set the server side block | |
loc.getBlock().setTypeId(serverBlockID); | |
} | |
private void registerListener(Plugin plugin) { | |
final ChunkletProcessor processor = getChunkletProcessor(); | |
ProtocolLibrary.getProtocolManager().addPacketListener( | |
listener = new PacketAdapter(plugin, ConnectionSide.SERVER_SIDE, | |
Packets.Server.BLOCK_CHANGE, Packets.Server.MULTI_BLOCK_CHANGE, | |
Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK) { | |
@Override | |
public void onPacketSending(PacketEvent event) { | |
PacketContainer packet = event.getPacket(); | |
World world = event.getPlayer().getWorld(); | |
switch (event.getPacketID()) { | |
case Packets.Server.BLOCK_CHANGE: | |
translateBlockChange(packet, world); | |
break; | |
case Packets.Server.MULTI_BLOCK_CHANGE: | |
translateMultiBlockChange(packet, world); | |
break; | |
case Packets.Server.MAP_CHUNK: | |
ChunkPacketProcessor.fromMapPacket(packet, world).process(processor); | |
break; | |
case Packets.Server.MAP_CHUNK_BULK: | |
for (ChunkPacketProcessor chunk : ChunkPacketProcessor.fromMapBulkPacket(packet, world)) { | |
chunk.process(processor); | |
} | |
break; | |
} | |
} | |
}); | |
} | |
public void close() { | |
if (listener != null) { | |
ProtocolLibrary.getProtocolManager().removePacketListener(listener); | |
listener = null; | |
} | |
} | |
private ChunkletProcessor getChunkletProcessor() { | |
return new ChunkletProcessor() { | |
@Override | |
public void processChunklet(Location origin, byte[] data, int blockIndex, int dataIndex) { | |
ChunkCoordinate coord = ChunkCoordinate.fromBlock(origin); | |
World world = origin.getWorld(); | |
int originX = origin.getBlockX(); | |
int originY = origin.getBlockY(); | |
int originZ = origin.getBlockZ(); | |
for (BlockCoordinate position : translations.row(coord).keySet()) { | |
int posX = position.getX(); | |
int posY = position.getY(); | |
int posZ = position.getZ(); | |
// Make sure we're inside the chunklet | |
if (posY >= originY && posY - originY < 16) { | |
int offset = blockIndex + (posX - originX) + (posZ - originZ) * 16 + (posY - originY) * 256; | |
data[offset] = (byte) translateBlockID(world, posX, posY, posZ, data[offset] & 0xFF); | |
} | |
} | |
} | |
}; | |
} | |
private void translateBlockChange(PacketContainer packet, World world) throws FieldAccessException { | |
StructureModifier<Integer> ints = packet.getIntegers(); | |
int x = ints.read(0); | |
int y = ints.read(1); | |
int z = ints.read(2); | |
int blockID = ints.read(3); | |
System.out.println("Block change: " + x + ", " + y + ", " + z); | |
// Convert using the tables | |
ints.write(3, translateBlockID(world, x, y, z, blockID)); | |
} | |
private void translateMultiBlockChange(PacketContainer packet, World world) throws FieldAccessException { | |
StructureModifier<byte[]> byteArrays = packet.getByteArrays(); | |
StructureModifier<Integer> ints = packet.getIntegers(); | |
int baseX = ints.read(0) << 4; | |
int baseZ = ints.read(1) << 4; | |
BlockChangeArray data = new BlockChangeArray(byteArrays.read(0)); | |
for (int i = 0; i < data.getSize(); i++) { | |
BlockChange change = data.getBlockChange(i); | |
change.setBlockID(translateBlockID( | |
world, | |
baseX + change.getRelativeX(), | |
change.getAbsoluteY(), | |
baseZ + change.getRelativeZ(), | |
change.getBlockID() | |
)); | |
} | |
byteArrays.write(0, data.toByteArray()); | |
} | |
private int translateBlockID(World world, int x, int y, int z, int blockID) { | |
Integer translate = translations.get( | |
ChunkCoordinate.fromBlock(world, x, z), new BlockCoordinate(x, y, z)); | |
// Use the existing block ID if not found | |
return translate != null ? translate : blockID; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.comphenix.example; | |
import java.io.Serializable; | |
import org.bukkit.Location; | |
import org.bukkit.World; | |
import com.google.common.base.Objects; | |
class ChunkCoordinate implements Serializable { | |
private static final long serialVersionUID = 1L; | |
private final String worldID; | |
private final int chunkX; | |
private final int chunkZ; | |
private ChunkCoordinate(World world, int chunkX, int chunkZ) { | |
this.worldID = world.getName(); | |
this.chunkX = chunkX; | |
this.chunkZ = chunkZ; | |
} | |
public static ChunkCoordinate fromBlock(World world, int x, int z) { | |
return new ChunkCoordinate(world, x >> 4, z >> 4); | |
} | |
public static ChunkCoordinate fromBlock(Location loc) { | |
return fromBlock(loc.getWorld(), loc.getBlockX(), loc.getBlockZ()); | |
} | |
@Override | |
public int hashCode() { | |
return Objects.hashCode(worldID, chunkX, chunkZ); | |
} | |
public int getChunkX() { | |
return chunkX; | |
} | |
public int getChunkZ() { | |
return chunkZ; | |
} | |
public String getWorldID() { | |
return worldID; | |
} | |
@Override | |
public boolean equals(Object obj) { | |
if (this == obj) | |
return true; | |
if (obj instanceof ChunkCoordinate) { | |
ChunkCoordinate other = (ChunkCoordinate) obj; | |
return worldID == other.worldID && chunkX == other.chunkX && chunkZ == other.chunkZ; | |
} | |
return true; | |
} | |
@Override | |
public String toString() { | |
return "[worldID: " + worldID + ", chunkX: " + chunkX + ", chunkZ: " + chunkZ + "]"; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.comphenix.example; | |
import org.bukkit.Location; | |
import org.bukkit.World; | |
import org.bukkit.World.Environment; | |
import com.comphenix.protocol.Packets; | |
import com.comphenix.protocol.events.PacketContainer; | |
import com.comphenix.protocol.reflect.StructureModifier; | |
/** | |
* Used to process a chunk. | |
* | |
* @author Kristian | |
*/ | |
public class ChunkPacketProcessor { | |
/** | |
* Process the content of a single 16x16x16 chunklet in a 16x256x16 chunk. | |
* @author Kristian | |
*/ | |
public interface ChunkletProcessor { | |
public void processChunklet(Location origin, byte[] data, int blockIndex, int dataIndex); | |
} | |
// 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; | |
protected 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 hasContinous = true; | |
private int startIndex; | |
private int size; | |
private int blockSize; | |
private byte[] data; | |
private World world; | |
private ChunkPacketProcessor() { | |
// Use factory methods | |
} | |
/** | |
* Construct a chunk packet processor from a givne MAP_CHUNK packet. | |
* @param packet - the map chunk packet. | |
* @return The chunk packet processor. | |
*/ | |
public static ChunkPacketProcessor fromMapPacket(PacketContainer packet, World world) { | |
if (packet.getID() != Packets.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.hasContinous = 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. | |
*/ | |
public static ChunkPacketProcessor[] fromMapBulkPacket(PacketContainer packet, World world) { | |
if (packet.getID() != Packets.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.hasContinous = 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; | |
} | |
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++; | |
} | |
} | |
// 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. | |
int skylightCount = world.getEnvironment() == Environment.NORMAL ? 1 : 0; | |
// 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) + | |
(hasContinous ? BIOME_ARRAY_LENGTH : 0); | |
blockSize = 4096 * chunkSectionNumber; | |
if (startIndex + blockSize > data.length) { | |
return; | |
} | |
// Make sure the chunk is loaded | |
if (isChunkLoaded(world, chunkX, chunkZ)) { | |
translate(processor); | |
} | |
} | |
private void translate(ChunkletProcessor processor) { | |
// Loop over 16x16x16 chunks in the 16x256x16 column | |
int idIndexModifier = 0; | |
int idOffset = startIndex; | |
int dataOffset = idOffset + chunkSectionNumber * 4096; | |
for (int i = 0; i < 16; i++) { | |
// If the bitmask indicates this chunk is sent | |
if ((chunkMask & 1 << i) > 0) { | |
int relativeIDStart = idIndexModifier * 4096; | |
int relativeDataStart = idIndexModifier * 2048; | |
int blockIndex = idOffset + relativeIDStart; | |
int dataIndex = dataOffset + relativeDataStart; | |
// 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, blockIndex, dataIndex); | |
idIndexModifier++; | |
} | |
} | |
} | |
private boolean isChunkLoaded(World world, int x, int z) { | |
return world.isChunkLoaded(x, z); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment