Skip to content

Instantly share code, notes, and snippets.

@aadnk

aadnk/RandomBiomes.java

Last active Dec 12, 2015
Embed
What would you like to do?
Demonstration of how to modify the biome of each chunk.
package com.comphenix.example;
import java.util.Collections;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.zip.Deflater;
import org.bukkit.Chunk;
import org.bukkit.World.Environment;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.bukkit.plugin.java.JavaPlugin;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.events.ListenerPriority;
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.MapMaker;
public class RandomBiomes extends JavaPlugin implements Listener {
private static final int BYTES_PER_NIBBLE_PART = 2048;
private static final int CHUNK_SEGMENTS = 16;
private static final int NIBBLES_REQUIRED = 4;
private static final int BIOME_ARRAY_LENGTH = 256;
// Look this up in net.minecraft.server.Biome
private static final byte BIOME_HELL = 8;
// Used to pass around detailed information about chunks
private static class ChunkInfo {
public int chunkX;
public int chunkZ;
public int chunkMask;
public int extraMask;
public int chunkSectionNumber;
public int extraSectionNumber;
public boolean hasContinous;
public byte[] data;
public Player player;
public int startIndex;
public int size;
}
private Set<Object> changed = Collections.newSetFromMap(new MapMaker().weakKeys().<Object, Boolean>makeMap());
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (sender instanceof Player) {
Player player = (Player) sender;
Chunk chunk = player.getLocation().getChunk();
player.getWorld().refreshChunk(chunk.getX(), chunk.getZ());
}
return true;
}
public void onEnable() {
getServer().getPluginManager().registerEvents(this, this);
// Transform packets
ProtocolLibrary.getProtocolManager().addPacketListener(
new PacketAdapter(this, PacketType.Play.Server.MAP_CHUNK, PacketType.Play.Server.MAP_CHUNK_BULK) {
@Override
public void onPacketSending(PacketEvent event) {
final PacketType type = event.getPacketType();
if (type == PacketType.Play.Server.MAP_CHUNK) {
translateMapChunk(event.getPacket(), event.getPlayer());
} else if (type == PacketType.Play.Server.MAP_CHUNK_BULK) {
translateMapChunkBulk(event.getPacket(), event.getPlayer());
}
}
});
// Finalize packets
ProtocolLibrary.getProtocolManager().addPacketListener(
new PacketAdapter(this, ListenerPriority.MONITOR, PacketType.Play.Server.MAP_CHUNK) {
@Override
public void onPacketSending(PacketEvent event) {
finalizeMapChunk(event.getPacket());
}
});
}
private void finalizeMapChunk(PacketContainer packet) {
// Ensure the packet has changed
if (changed.remove(packet.getHandle())) {
StructureModifier<Integer> ints = packet.getIntegers();
StructureModifier<byte[]> byteArray = packet.getByteArrays();
Deflater localDeflater = new Deflater(-1);
byte[] output = byteArray.read(0);
byte[] data = byteArray.read(1);
// Occurs if the packet has already been compressed
if (data == null) {
return;
}
try {
localDeflater.setInput(data, 0, data.length);
localDeflater.finish();
ints.write(4, localDeflater.deflate(output));
// Indicate that the packet has been compressed
byteArray.write(1, null);
} finally {
localDeflater.end();
}
}
}
public void translateMapChunk(PacketContainer packet, Player player) throws FieldAccessException {
StructureModifier<Integer> ints = packet.getIntegers();
StructureModifier<byte[]> byteArray = packet.getByteArrays();
// Create an info objects
ChunkInfo info = new ChunkInfo();
info.player = player;
info.chunkX = ints.read(0); // packet.a;
info.chunkZ = ints.read(1); // packet.b;
info.chunkMask = ints.read(2); // packet.c;
info.extraMask = ints.read(3); // packet.d;
info.hasContinous = getOrDefault(packet.getBooleans().readSafely(0), true);
info.data = byteArray.read(1); // packet.inflatedBuffer;
info.startIndex = 0;
if (info.data != null) {
if (translateChunkInfo(info, info.data)) {
changed.add(packet.getHandle());
}
}
}
// Mimic the ?? operator in C#
private <T> T getOrDefault(T value, T defaultIfNull) {
return value != null ? value : defaultIfNull;
}
public void translateMapChunkBulk(PacketContainer packet, Player player) throws FieldAccessException {
StructureModifier<int[]> intArrays = packet.getIntegerArrays();
StructureModifier<byte[]> byteArrays = packet.getSpecificModifier(byte[].class);
int[] x = intArrays.read(0); // getPrivateField(packet, "c");
int[] z = intArrays.read(1); // getPrivateField(packet, "d");
ChunkInfo[] infos = new ChunkInfo[x.length];
int dataStartIndex = 0;
int[] chunkMask = intArrays.read(2); // packet.a;
int[] extraMask = intArrays.read(3); // packet.b;
for (int chunkNum = 0; chunkNum < infos.length; chunkNum++) {
// Create an info objects
ChunkInfo info = new ChunkInfo();
infos[chunkNum] = info;
info.player = player;
info.chunkX = x[chunkNum];
info.chunkZ = z[chunkNum];
info.chunkMask = chunkMask[chunkNum];
info.extraMask = extraMask[chunkNum];
info.hasContinous = true; // Always TRUE here
info.data = byteArrays.read(1); //packet.buildBuffer;
// Check for Spigot
if (info.data == null || info.data.length == 0) {
info.data = packet.getSpecificModifier(byte[][].class).read(0)[chunkNum];
} else {
info.startIndex = dataStartIndex;
}
translateChunkInfo(info, info.data);
dataStartIndex += info.size;
}
}
private boolean translateChunkInfo(ChunkInfo info, byte[] returnData) {
// Compute chunk number
for (int i = 0; i < CHUNK_SEGMENTS; i++) {
if ((info.chunkMask & (1 << i)) > 0) {
info.chunkSectionNumber++;
}
if ((info.extraMask & (1 << i)) > 0) {
info.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 = info.player.getWorld().getEnvironment() == Environment.NORMAL ? 1 : 0;
// To calculate the size of each chunk, we need to take into account the number of segments (out of 16)
// that have been sent. Each segment sent is encoded in the chunkMask bit field, where every binary 1
// indicates that a segment is present and every 0 indicates that it's not.
// 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.
info.size = BYTES_PER_NIBBLE_PART * (
(NIBBLES_REQUIRED + skylightCount) * info.chunkSectionNumber +
info.extraSectionNumber) +
(info.hasContinous ? BIOME_ARRAY_LENGTH : 0);
if (info.hasContinous) {
int biomeStart = info.startIndex + info.size - BIOME_ARRAY_LENGTH;
byte value = (byte) (Math.abs(info.chunkX + info.chunkZ) % 22);
for (int i = 0; i < BIOME_ARRAY_LENGTH; i++) {
info.data[biomeStart + i] = value;
}
// If has changed
return true;
}
return false;
}
}
@Skyost

This comment has been minimized.

Copy link

@Skyost Skyost commented Aug 11, 2015

Hi,
It did not work anymore in 1.8.x, a lot of fields have changed in those packets.

Any help ?

Thanks !

@fhntv24

This comment has been minimized.

Copy link

@fhntv24 fhntv24 commented Aug 13, 2015

Use PacketWrapper.
Also in 1.8 world.updateChunk is buggy i think.

@Skyost

This comment has been minimized.

Copy link

@Skyost Skyost commented Aug 13, 2015

Yeah, thanks. I've updated it, as you can see here.
Edit : And yeah, world.updateChunk(...) is buggy so I've made this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.