Skip to content

Instantly share code, notes, and snippets.

@aadnk
Created June 22, 2013 18:17
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save aadnk/5841942 to your computer and use it in GitHub Desktop.
Save aadnk/5841942 to your computer and use it in GitHub Desktop.
LightSource Advanced - Update nearby chunks as well, but only if necessary.
package com.comphenix.example;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import net.minecraft.server.v1_5_R3.Entity;
import net.minecraft.server.v1_5_R3.EntityHuman;
import net.minecraft.server.v1_5_R3.EnumSkyBlock;
import net.minecraft.server.v1_5_R3.IWorldAccess;
import net.minecraft.server.v1_5_R3.PlayerChunkMap;
import net.minecraft.server.v1_5_R3.World;
import net.minecraft.server.v1_5_R3.WorldServer;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.craftbukkit.v1_5_R3.CraftWorld;
public class LightSourceEx {
private static Method cachedPlayerChunk;
private static Field cachedDirtyField;
// For choosing an adjacent air block
private static BlockFace[] SIDES = {
BlockFace.UP, BlockFace.DOWN, BlockFace.NORTH,
BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST };
/**
* Create light with level at a location.
* @param loc - which block to update.
* @param level - the new light level.
*/
public static void createLightSource(Location loc, int level) {
WorldServer nmsWorld = ((CraftWorld) loc.getWorld()).getHandle();
int oldLevel = loc.getBlock().getLightLevel();
// Sets the light source at the location to the level
nmsWorld.b(EnumSkyBlock.BLOCK, loc.getBlockX(), loc.getBlockY(), loc.getBlockZ(), level);
// Send packets to the area telling players to see this level
updateChunk(nmsWorld, loc);
// If you comment this out it is more likely to get light sources you can't remove
// but if you do comment it, light is consistent on relog and what not.
nmsWorld.b(EnumSkyBlock.BLOCK, loc.getBlockX(), loc.getBlockY(), loc.getBlockZ(), oldLevel);
}
private static Block getAdjacentAirBlock(Block block) {
// Find the first adjacent air block
for (BlockFace face : SIDES) {
// Don't use these sides
if (block.getY() == 0x0 && face == BlockFace.DOWN)
continue;
if (block.getY() == 0xFF && face == BlockFace.UP)
continue;
Block candidate = block.getRelative(face);
if (candidate.getType().isTransparent()) {
return candidate;
}
}
return block;
}
/**
* Gets all the chunks touching/diagonal to the chunk the location is in and updates players with them.
* @param loc - location to the block that was updated.
*/
@SuppressWarnings("rawtypes")
private static void updateChunk(WorldServer nmsWorld, Location loc) {
try {
PlayerChunkMap map = nmsWorld.getPlayerChunkMap();
IWorldAccess access = countLightUpdates(loc.getWorld(), map);
nmsWorld.addIWorldAccess(access);
// Update the light itself
Block adjacent = getAdjacentAirBlock(loc.getBlock());
nmsWorld.A(adjacent.getX(), adjacent.getY(), adjacent.getZ());
// Remove it again
Field field = World.class.getDeclaredField("u");
field.setAccessible(true);
((List) field.get(nmsWorld)).remove(access);
int chunkX = loc.getBlockX() >> 4;
int chunkZ = loc.getBlockZ() >> 4;
// Make sure the block itself is marked
map.flagDirty(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
// Look for player chunks immediately around the block
for (int dX = -1; dX <= 1; dX++) {
for (int dZ =-1; dZ <=1; dZ++) {
// That class is package private unfortunately
Object playerChunk = getPlayerCountMethod().invoke(map, chunkX + dX, chunkZ + dZ, false);
if (playerChunk != null) {
Field dirtyField = getDirtyField(playerChunk);
int dirtyCount = (Integer) dirtyField.get(playerChunk);
System.out.println("Dirty count: " + dirtyCount);
// Minecraft will automatically send out a Packet51MapChunk for us,
// with only those segments (16x16x16) that are needed.
if (dirtyCount > 0) {
dirtyField.set(playerChunk, 64);
}
}
}
}
map.flush();
} catch (SecurityException e) {
throw new RuntimeException("Access denied", e);
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Reflection problem.", e);
}
}
private static IWorldAccess countLightUpdates(final org.bukkit.World world, final PlayerChunkMap map) {
return new IWorldAccess() {
@Override
//markBlockForUpdate
public void a(int x, int y, int z) {
map.flagDirty(x, y, z);
}
@Override
//markBlockForRenderUpdate
public void b(int x, int y, int z) {
map.flagDirty(x, y, z);
}
@Override
//destroyBlockPartially
public void b(int arg0, int arg1, int arg2, int arg3, int arg4) { }
@Override
//playAuxSFX
public void a(EntityHuman arg0, int arg1, int arg2, int arg3, int arg4, int arg5) { }
@Override
//markBlockRangeForRenderUpdate
public void a(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) {
// Ignore
}
@Override
//broadcastSound
public void a(int arg0, int arg1, int arg2, int arg3, int arg4) { }
@Override
//playSound
public void a(String arg0, double arg1, double arg2, double arg3, float arg4, float arg5) {
}
@Override
//playSoundToNearExcept
public void a(EntityHuman arg0, String arg1, double arg2, double arg3, double arg4, float arg5,float arg6) {
}
@Override
//spawnParticle
public void a(String arg0, double arg1, double arg2, double arg3, double arg4, double arg5, double arg6) { }
@Override
//playRecord
public void a(String arg0, int arg1, int arg2, int arg3) { }
@Override
//onEntityCreate
public void a(Entity arg0) { }
@Override
//onEntityDestroy (probably)
public void b(Entity arg0) { }
};
}
private static Method getPlayerCountMethod() throws NoSuchMethodException, SecurityException {
if (cachedPlayerChunk == null) {
cachedPlayerChunk = PlayerChunkMap.class.getDeclaredMethod("a", int.class, int.class, boolean.class);
cachedPlayerChunk.setAccessible(true);
}
return cachedPlayerChunk;
}
private static Field getDirtyField(Object playerChunk) throws NoSuchFieldException, SecurityException {
if (cachedDirtyField == null) {
cachedDirtyField = playerChunk.getClass().getDeclaredField("dirtyCount");
cachedDirtyField.setAccessible(true);
}
return cachedDirtyField;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment