diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_14/BukkitAdapter_1_14.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_14/BukkitAdapter_1_14.java index 7998d58b5..b2ed81ce9 100644 --- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_14/BukkitAdapter_1_14.java +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_14/BukkitAdapter_1_14.java @@ -30,6 +30,7 @@ import net.minecraft.server.v1_14_R1.DataPaletteBlock; import net.minecraft.server.v1_14_R1.DataPaletteLinear; import net.minecraft.server.v1_14_R1.GameProfileSerializer; import net.minecraft.server.v1_14_R1.IBlockData; +import net.minecraft.server.v1_14_R1.PacketPlayOutLightUpdate; import net.minecraft.server.v1_14_R1.PlayerChunk; import net.minecraft.server.v1_14_R1.PlayerChunkMap; import net.minecraft.server.v1_14_R1.World; @@ -167,7 +168,7 @@ public final class BukkitAdapter_1_14 extends NMSAdapter { } } - public static void sendChunk(net.minecraft.server.v1_14_R1.WorldServer nmsWorld, int X, int Z, int mask) { + public static void sendChunk(net.minecraft.server.v1_14_R1.WorldServer nmsWorld, int X, int Z, int mask, boolean lighting) { PlayerChunk playerChunk = getPlayerChunk(nmsWorld, X, Z); if (playerChunk == null) { return; @@ -187,6 +188,15 @@ public final class BukkitAdapter_1_14 extends NMSAdapter { fieldDirtyBits.set(playerChunk, dirtyBits); fieldDirtyCount.set(playerChunk, 64); + + if (lighting) { + ChunkCoordIntPair chunkCoordIntPair = new ChunkCoordIntPair(X, Z); + PacketPlayOutLightUpdate packet = new PacketPlayOutLightUpdate(chunkCoordIntPair, nmsWorld.getChunkProvider().getLightEngine()); + playerChunk.players.a(chunkCoordIntPair, false).forEach(p -> { + p.playerConnection.sendPacket(packet); + }); + } + } catch (IllegalAccessException e) { e.printStackTrace(); } diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_14/BukkitGetBlocks_1_14.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_14/BukkitGetBlocks_1_14.java index db000c51d..848d127c5 100644 --- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_14/BukkitGetBlocks_1_14.java +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_14/BukkitGetBlocks_1_14.java @@ -54,10 +54,13 @@ import net.minecraft.server.v1_14_R1.DataPaletteHash; import net.minecraft.server.v1_14_R1.DataPaletteLinear; import net.minecraft.server.v1_14_R1.Entity; import net.minecraft.server.v1_14_R1.EntityTypes; +import net.minecraft.server.v1_14_R1.EnumSkyBlock; import net.minecraft.server.v1_14_R1.IBlockData; import net.minecraft.server.v1_14_R1.LightEngineThreaded; import net.minecraft.server.v1_14_R1.NBTTagCompound; import net.minecraft.server.v1_14_R1.NBTTagInt; +import net.minecraft.server.v1_14_R1.NibbleArray; +import net.minecraft.server.v1_14_R1.SectionPosition; import net.minecraft.server.v1_14_R1.TileEntity; import net.minecraft.server.v1_14_R1.WorldServer; import org.bukkit.World; @@ -72,6 +75,8 @@ public class BukkitGetBlocks_1_14 extends CharGetBlocks { public Chunk nmsChunk; public WorldServer world; public int X, Z; + public NibbleArray[] blockLight = new NibbleArray[16]; + public NibbleArray[] skyLight = new NibbleArray[16]; public BukkitGetBlocks_1_14(World world, int X, int Z) { this(((CraftWorld) world).getHandle(), X, Z); @@ -119,6 +124,26 @@ public class BukkitGetBlocks_1_14 extends CharGetBlocks { return AdaptedMap.immutable(nmsTiles, posNms2We, nmsTile2We); } + @Override + public int getSkyLight(int x, int y, int z) { + int layer = y >> 4; + if (skyLight[layer] == null) { + skyLight[layer] = world.getChunkProvider().getLightEngine().a(EnumSkyBlock.SKY).a(SectionPosition.a(nmsChunk.getPos(), layer)); + } + long l = BlockPosition.a(x, y, z); + return skyLight[layer].a(SectionPosition.b(BlockPosition.b(l)), SectionPosition.b(BlockPosition.c(l)), SectionPosition.b(BlockPosition.d(l))); + } + + @Override + public int getEmmittedLight(int x, int y, int z) { + int layer = y >> 4; + if (blockLight[layer] == null) { + blockLight[layer] = world.getChunkProvider().getLightEngine().a(EnumSkyBlock.BLOCK).a(SectionPosition.a(nmsChunk.getPos(), layer)); + } + long l = BlockPosition.a(x, y, z); + return blockLight[layer].a(SectionPosition.b(BlockPosition.b(l)), SectionPosition.b(BlockPosition.c(l)), SectionPosition.b(BlockPosition.d(l))); + } + @Override public CompoundTag getEntity(UUID uuid) { Entity entity = world.getEntity(uuid); @@ -322,6 +347,29 @@ public class BukkitGetBlocks_1_14 extends CharGetBlocks { } } + boolean lightUpdate = false; + + // Lighting + char[][] light = set.getLight(); + if (light != null) { + lightUpdate = true; + try { + fillLightNibble(light, EnumSkyBlock.BLOCK); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + char[][] skyLight = set.getSkyLight(); + if (skyLight != null) { + lightUpdate = true; + try { + fillLightNibble(skyLight, EnumSkyBlock.SKY); + } catch (Throwable e) { + e.printStackTrace(); + } + } + Runnable[] syncTasks = null; int bx = X << 4; @@ -427,24 +475,19 @@ public class BukkitGetBlocks_1_14 extends CharGetBlocks { }; } - {//Lighting - // TODO optimize, cause this is really slow - LightEngineThreaded engine = (LightEngineThreaded) nmsChunk.e(); - engine.a(nmsChunk, false); - } - Runnable callback; - if (bitMask == 0 && biomes == null) { + if (bitMask == 0 && biomes == null && !lightUpdate) { callback = null; } else { - int finalMask = bitMask; + int finalMask = bitMask != 0 ? bitMask : lightUpdate ? set.getBitMask() : 0; + boolean finalLightUpdate = lightUpdate; callback = () -> { // Set Modified nmsChunk.d(true); // Set Modified nmsChunk.mustNotSave = false; nmsChunk.markDirty(); // send to player - BukkitAdapter_1_14.sendChunk(nmsWorld, X, Z, finalMask); + BukkitAdapter_1_14.sendChunk(nmsWorld, X, Z, finalMask, finalLightUpdate); if (finalizer != null) finalizer.run(); }; } @@ -617,6 +660,25 @@ public class BukkitGetBlocks_1_14 extends CharGetBlocks { return tmp; } + private void fillLightNibble(char[][] light, EnumSkyBlock skyBlock) { + for (int Y = 0; Y < 16; Y++) { + if (light[Y] == null) { + continue; + } + NibbleArray nibble = world.getChunkProvider().getLightEngine().a(skyBlock).a(SectionPosition.a(nmsChunk.getPos(), Y)); + if (nibble == null) { + continue; + } + synchronized (nibble) { + for (int i = 0; i < 4096; i++) { + if (light[Y][i] < 16) { + nibble.a(i, light[Y][i]); + } + } + } + } + } + @Override public boolean hasSection(int layer) { return getSections()[layer] != null; diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15/BukkitAdapter_1_15.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15/BukkitAdapter_1_15.java index d2c2c4c29..be791aa66 100644 --- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15/BukkitAdapter_1_15.java +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15/BukkitAdapter_1_15.java @@ -24,6 +24,7 @@ import net.minecraft.server.v1_15_R1.DataPaletteBlock; import net.minecraft.server.v1_15_R1.DataPaletteLinear; import net.minecraft.server.v1_15_R1.GameProfileSerializer; import net.minecraft.server.v1_15_R1.IBlockData; +import net.minecraft.server.v1_15_R1.PacketPlayOutLightUpdate; import net.minecraft.server.v1_15_R1.PlayerChunk; import net.minecraft.server.v1_15_R1.PlayerChunkMap; import net.minecraft.server.v1_15_R1.World; @@ -85,6 +86,8 @@ public final class BukkitAdapter_1_15 extends NMSAdapter { fieldDirtyBits = PlayerChunk.class.getDeclaredField("r"); fieldDirtyBits.setAccessible(true); + fieldTickingBlockCount.setAccessible(true); + Method declaredGetVisibleChunk = PlayerChunkMap.class.getDeclaredMethod("getVisibleChunk", long.class); declaredGetVisibleChunk.setAccessible(true); methodGetVisibleChunk = MethodHandles.lookup().unreflect(declaredGetVisibleChunk); @@ -165,7 +168,7 @@ public final class BukkitAdapter_1_15 extends NMSAdapter { } } - public static void sendChunk(net.minecraft.server.v1_15_R1.WorldServer nmsWorld, int X, int Z, int mask) { + public static void sendChunk(net.minecraft.server.v1_15_R1.WorldServer nmsWorld, int X, int Z, int mask, boolean lighting) { PlayerChunk playerChunk = getPlayerChunk(nmsWorld, X, Z); if (playerChunk == null) { return; @@ -185,6 +188,15 @@ public final class BukkitAdapter_1_15 extends NMSAdapter { fieldDirtyBits.set(playerChunk, dirtyBits); fieldDirtyCount.set(playerChunk, 64); + + if (lighting) { + ChunkCoordIntPair chunkCoordIntPair = new ChunkCoordIntPair(X, Z); + PacketPlayOutLightUpdate packet = new PacketPlayOutLightUpdate(chunkCoordIntPair, nmsWorld.getChunkProvider().getLightEngine()); + playerChunk.players.a(chunkCoordIntPair, false).forEach(p -> { + p.playerConnection.sendPacket(packet); + }); + } + } catch (IllegalAccessException e) { e.printStackTrace(); } diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15/BukkitGetBlocks_1_15.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15/BukkitGetBlocks_1_15.java index 21c5f48f7..c9fbca585 100644 --- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15/BukkitGetBlocks_1_15.java +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15/BukkitGetBlocks_1_15.java @@ -55,10 +55,13 @@ import net.minecraft.server.v1_15_R1.DataPaletteHash; import net.minecraft.server.v1_15_R1.DataPaletteLinear; import net.minecraft.server.v1_15_R1.Entity; import net.minecraft.server.v1_15_R1.EntityTypes; +import net.minecraft.server.v1_15_R1.EnumSkyBlock; import net.minecraft.server.v1_15_R1.IBlockData; import net.minecraft.server.v1_15_R1.LightEngineThreaded; import net.minecraft.server.v1_15_R1.NBTTagCompound; import net.minecraft.server.v1_15_R1.NBTTagInt; +import net.minecraft.server.v1_15_R1.NibbleArray; +import net.minecraft.server.v1_15_R1.SectionPosition; import net.minecraft.server.v1_15_R1.TileEntity; import net.minecraft.server.v1_15_R1.WorldServer; import org.bukkit.World; @@ -75,6 +78,8 @@ public class BukkitGetBlocks_1_15 extends CharGetBlocks { public Chunk nmsChunk; public WorldServer world; public int X, Z; + public NibbleArray[] blockLight = new NibbleArray[16]; + public NibbleArray[] skyLight = new NibbleArray[16]; public BukkitGetBlocks_1_15(World world, int X, int Z) { this(((CraftWorld) world).getHandle(), X, Z); @@ -127,6 +132,26 @@ public class BukkitGetBlocks_1_15 extends CharGetBlocks { return AdaptedMap.immutable(nmsTiles, posNms2We, nmsTile2We); } + @Override + public int getSkyLight(int x, int y, int z) { + int layer = y >> 4; + if (skyLight[layer] == null) { + skyLight[layer] = world.getChunkProvider().getLightEngine().a(EnumSkyBlock.SKY).a(SectionPosition.a(nmsChunk.getPos(), layer)); + } + long l = BlockPosition.a(x, y, z); + return skyLight[layer].a(SectionPosition.b(BlockPosition.b(l)), SectionPosition.b(BlockPosition.c(l)), SectionPosition.b(BlockPosition.d(l))); + } + + @Override + public int getEmmittedLight(int x, int y, int z) { + int layer = y >> 4; + if (blockLight[layer] == null) { + blockLight[layer] = world.getChunkProvider().getLightEngine().a(EnumSkyBlock.BLOCK).a(SectionPosition.a(nmsChunk.getPos(), layer)); + } + long l = BlockPosition.a(x, y, z); + return blockLight[layer].a(SectionPosition.b(BlockPosition.b(l)), SectionPosition.b(BlockPosition.c(l)), SectionPosition.b(BlockPosition.d(l))); + } + @Override public CompoundTag getEntity(UUID uuid) { Entity entity = world.getEntity(uuid); @@ -335,6 +360,29 @@ public class BukkitGetBlocks_1_15 extends CharGetBlocks { } } + boolean lightUpdate = false; + + // Lighting + char[][] light = set.getLight(); + if (light != null) { + lightUpdate = true; + try { + fillLightNibble(light, EnumSkyBlock.BLOCK); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + char[][] skyLight = set.getSkyLight(); + if (skyLight != null) { + lightUpdate = true; + try { + fillLightNibble(skyLight, EnumSkyBlock.SKY); + } catch (Throwable e) { + e.printStackTrace(); + } + } + Runnable[] syncTasks = null; int bx = X << 4; @@ -440,25 +488,19 @@ public class BukkitGetBlocks_1_15 extends CharGetBlocks { }; } - //Lighting - // TODO optimize, cause this is really slow - LightEngineThreaded engine = (LightEngineThreaded) nmsChunk.e(); - //lightChunk() - engine.a(nmsChunk, false); - Runnable callback; - if (bitMask == 0 && biomes == null) { + if (bitMask == 0 && biomes == null && !lightUpdate) { callback = null; } else { - int finalMask = bitMask; + int finalMask = bitMask != 0 ? bitMask : lightUpdate ? set.getBitMask() : 0; + boolean finalLightUpdate = lightUpdate; callback = () -> { // Set Modified - //setLastSaveHadEntities nmsChunk.d(true); // Set Modified nmsChunk.mustNotSave = false; nmsChunk.markDirty(); // send to player - BukkitAdapter_1_15.sendChunk(nmsWorld, X, Z, finalMask); + BukkitAdapter_1_15.sendChunk(nmsWorld, X, Z, finalMask, finalLightUpdate); if (finalizer != null) finalizer.run(); }; } @@ -637,6 +679,25 @@ public class BukkitGetBlocks_1_15 extends CharGetBlocks { return tmp; } + private void fillLightNibble(char[][] light, EnumSkyBlock skyBlock) { + for (int Y = 0; Y < 16; Y++) { + if (light[Y] == null) { + continue; + } + NibbleArray nibble = world.getChunkProvider().getLightEngine().a(skyBlock).a(SectionPosition.a(nmsChunk.getPos(), Y)); + if (nibble == null) { + continue; + } + synchronized (nibble) { + for (int i = 0; i < 4096; i++) { + if (light[Y][i] < 16) { + nibble.a(i, light[Y][i]); + } + } + } + } + } + @Override public boolean hasSection(int layer) { return getSections()[layer] != null; diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/BukkitAdapter_1_15_2.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/BukkitAdapter_1_15_2.java index 466cb233a..27cceceee 100644 --- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/BukkitAdapter_1_15_2.java +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/BukkitAdapter_1_15_2.java @@ -13,12 +13,6 @@ import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockTypesCache; import io.papermc.lib.PaperLib; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.locks.ReentrantLock; import net.jpountz.util.UnsafeUtils; import net.minecraft.server.v1_15_R1.Block; import net.minecraft.server.v1_15_R1.Chunk; @@ -30,6 +24,11 @@ import net.minecraft.server.v1_15_R1.DataPaletteBlock; import net.minecraft.server.v1_15_R1.DataPaletteLinear; import net.minecraft.server.v1_15_R1.GameProfileSerializer; import net.minecraft.server.v1_15_R1.IBlockData; +import net.minecraft.server.v1_15_R1.LightEngine; +import net.minecraft.server.v1_15_R1.LightEngineLayer; +import net.minecraft.server.v1_15_R1.LightEngineStorage; +import net.minecraft.server.v1_15_R1.NibbleArray; +import net.minecraft.server.v1_15_R1.PacketPlayOutLightUpdate; import net.minecraft.server.v1_15_R1.PlayerChunk; import net.minecraft.server.v1_15_R1.PlayerChunkMap; import net.minecraft.server.v1_15_R1.World; @@ -38,9 +37,15 @@ import org.bukkit.craftbukkit.v1_15_R1.CraftChunk; import org.bukkit.craftbukkit.v1_15_R1.CraftWorld; import sun.misc.Unsafe; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.locks.ReentrantLock; import java.util.function.Function; public final class BukkitAdapter_1_15_2 extends NMSAdapter { @@ -60,6 +65,8 @@ public final class BukkitAdapter_1_15_2 extends NMSAdapter { private final static MethodHandle methodGetVisibleChunk; + public final static MethodHandle methodSetLightNibbleArray; + private static final int CHUNKSECTION_BASE; private static final int CHUNKSECTION_SHIFT; @@ -90,6 +97,10 @@ public final class BukkitAdapter_1_15_2 extends NMSAdapter { declaredGetVisibleChunk.setAccessible(true); methodGetVisibleChunk = MethodHandles.lookup().unreflect(declaredGetVisibleChunk); + Method declaredSetLightNibbleArray = LightEngineStorage.class.getDeclaredMethod("a", long.class, NibbleArray.class); + declaredSetLightNibbleArray.setAccessible(true); + methodSetLightNibbleArray = MethodHandles.lookup().unreflect(declaredSetLightNibbleArray); + Field tmp = DataPaletteBlock.class.getDeclaredField("j"); ReflectionUtils.setAccessibleNonFinal(tmp); fieldLock = tmp; @@ -167,7 +178,7 @@ public final class BukkitAdapter_1_15_2 extends NMSAdapter { } } - public static void sendChunk(WorldServer nmsWorld, int X, int Z, int mask) { + public static void sendChunk(WorldServer nmsWorld, int X, int Z, int mask, boolean lighting) { PlayerChunk playerChunk = getPlayerChunk(nmsWorld, X, Z); if (playerChunk == null) { return; @@ -187,6 +198,15 @@ public final class BukkitAdapter_1_15_2 extends NMSAdapter { fieldDirtyBits.set(playerChunk, dirtyBits); fieldDirtyCount.set(playerChunk, 64); + + if (lighting) { + ChunkCoordIntPair chunkCoordIntPair = new ChunkCoordIntPair(X, Z); + PacketPlayOutLightUpdate packet = new PacketPlayOutLightUpdate(chunkCoordIntPair, nmsWorld.getChunkProvider().getLightEngine()); + playerChunk.players.a(chunkCoordIntPair, false).forEach(p -> { + p.playerConnection.sendPacket(packet); + }); + } + } catch (IllegalAccessException e) { e.printStackTrace(); } diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/BukkitGetBlocks_1_15_2.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/BukkitGetBlocks_1_15_2.java index efe33059b..3292cc556 100644 --- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/BukkitGetBlocks_1_15_2.java +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/BukkitGetBlocks_1_15_2.java @@ -38,10 +38,12 @@ import net.minecraft.server.v1_15_R1.DataPaletteHash; import net.minecraft.server.v1_15_R1.DataPaletteLinear; import net.minecraft.server.v1_15_R1.Entity; import net.minecraft.server.v1_15_R1.EntityTypes; +import net.minecraft.server.v1_15_R1.EnumSkyBlock; import net.minecraft.server.v1_15_R1.IBlockData; -import net.minecraft.server.v1_15_R1.LightEngineThreaded; import net.minecraft.server.v1_15_R1.NBTTagCompound; import net.minecraft.server.v1_15_R1.NBTTagInt; +import net.minecraft.server.v1_15_R1.NibbleArray; +import net.minecraft.server.v1_15_R1.SectionPosition; import net.minecraft.server.v1_15_R1.TileEntity; import net.minecraft.server.v1_15_R1.WorldServer; import org.bukkit.World; @@ -80,6 +82,8 @@ public class BukkitGetBlocks_1_15_2 extends CharGetBlocks { public Chunk nmsChunk; public WorldServer world; public int X, Z; + public NibbleArray[] blockLight = new NibbleArray[16]; + public NibbleArray[] skyLight = new NibbleArray[16]; public BukkitGetBlocks_1_15_2(World world, int X, int Z) { this(((CraftWorld) world).getHandle(), X, Z); @@ -132,6 +136,26 @@ public class BukkitGetBlocks_1_15_2 extends CharGetBlocks { return AdaptedMap.immutable(nmsTiles, posNms2We, nmsTile2We); } + @Override + public int getSkyLight(int x, int y, int z) { + int layer = y >> 4; + if (skyLight[layer] == null) { + skyLight[layer] = world.getChunkProvider().getLightEngine().a(EnumSkyBlock.SKY).a(SectionPosition.a(nmsChunk.getPos(), layer)); + } + long l = BlockPosition.a(x, y, z); + return skyLight[layer].a(SectionPosition.b(BlockPosition.b(l)), SectionPosition.b(BlockPosition.c(l)), SectionPosition.b(BlockPosition.d(l))); + } + + @Override + public int getEmmittedLight(int x, int y, int z) { + int layer = y >> 4; + if (blockLight[layer] == null) { + blockLight[layer] = world.getChunkProvider().getLightEngine().a(EnumSkyBlock.BLOCK).a(SectionPosition.a(nmsChunk.getPos(), layer)); + } + long l = BlockPosition.a(x, y, z); + return blockLight[layer].a(SectionPosition.b(BlockPosition.b(l)), SectionPosition.b(BlockPosition.c(l)), SectionPosition.b(BlockPosition.d(l))); + } + @Override public CompoundTag getEntity(UUID uuid) { Entity entity = world.getEntity(uuid); @@ -347,6 +371,29 @@ public class BukkitGetBlocks_1_15_2 extends CharGetBlocks { } } + boolean lightUpdate = false; + + // Lighting + char[][] light = set.getLight(); + if (light != null) { + lightUpdate = true; + try { + fillLightNibble(light, EnumSkyBlock.BLOCK); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + char[][] skyLight = set.getSkyLight(); + if (skyLight != null) { + lightUpdate = true; + try { + fillLightNibble(skyLight, EnumSkyBlock.SKY); + } catch (Throwable e) { + e.printStackTrace(); + } + } + Runnable[] syncTasks = null; int bx = X << 4; @@ -452,23 +499,19 @@ public class BukkitGetBlocks_1_15_2 extends CharGetBlocks { }; } - //Lighting - // TODO optimize, cause this is really slow - LightEngineThreaded engine = (LightEngineThreaded) nmsChunk.e(); - engine.a(nmsChunk, false); - Runnable callback; - if (bitMask == 0 && biomes == null) { + if (bitMask == 0 && biomes == null && !lightUpdate) { callback = null; } else { - int finalMask = bitMask; + int finalMask = bitMask != 0 ? bitMask : lightUpdate ? set.getBitMask() : 0; + boolean finalLightUpdate = lightUpdate; callback = () -> { // Set Modified nmsChunk.d(true); // Set Modified nmsChunk.mustNotSave = false; nmsChunk.markDirty(); // send to player - BukkitAdapter_1_15_2.sendChunk(nmsWorld, X, Z, finalMask); + BukkitAdapter_1_15_2.sendChunk(nmsWorld, X, Z, finalMask, finalLightUpdate); if (finalizer != null) finalizer.run(); }; } @@ -641,6 +684,25 @@ public class BukkitGetBlocks_1_15_2 extends CharGetBlocks { return tmp; } + private void fillLightNibble(char[][] light, EnumSkyBlock skyBlock) { + for (int Y = 0; Y < 16; Y++) { + if (light[Y] == null) { + continue; + } + NibbleArray nibble = world.getChunkProvider().getLightEngine().a(skyBlock).a(SectionPosition.a(nmsChunk.getPos(), Y)); + if (nibble == null) { + continue; + } + synchronized (nibble) { + for (int i = 0; i < 4096; i++) { + if (light[Y][i] < 16) { + nibble.a(i, light[Y][i]); + } + } + } + } + } + @Override public boolean hasSection(int layer) { return getSections()[layer] != null; @@ -648,6 +710,8 @@ public class BukkitGetBlocks_1_15_2 extends CharGetBlocks { @Override public boolean trim(boolean aggressive) { + skyLight = new NibbleArray[16]; + blockLight = new NibbleArray[16]; if (aggressive) { sections = null; nmsChunk = null; diff --git a/worldedit-core/src/main/java/com/boydti/fawe/FaweAPI.java b/worldedit-core/src/main/java/com/boydti/fawe/FaweAPI.java index 3548112d7..245db7791 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/FaweAPI.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/FaweAPI.java @@ -2,8 +2,11 @@ package com.boydti.fawe; import com.boydti.fawe.beta.IQueueChunk; import com.boydti.fawe.beta.IQueueExtent; +import com.boydti.fawe.beta.implementation.lighting.NMSRelighter; +import com.boydti.fawe.beta.implementation.queue.ParallelQueueExtent; import com.boydti.fawe.config.Settings; import com.boydti.fawe.object.RegionWrapper; +import com.boydti.fawe.object.RelightMode; import com.boydti.fawe.object.changeset.DiskStorageHistory; import com.boydti.fawe.object.changeset.SimpleChangeSetSummary; import com.boydti.fawe.object.exception.FaweException; @@ -28,10 +31,13 @@ import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat; import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormats; import com.sk89q.worldedit.internal.registry.AbstractFactory; import com.sk89q.worldedit.internal.registry.InputParser; +import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.world.World; + +import javax.annotation.Nullable; import java.io.File; import java.io.IOException; import java.lang.reflect.Field; @@ -43,7 +49,6 @@ import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.UUID; -import javax.annotation.Nullable; /** * The FaweAPI class offers a few useful functions.
@@ -387,12 +392,52 @@ public class FaweAPI { * - Relights in parallel (if enabled) for best performance
* - Also resends chunks
* - * @param world - * @param selection (assumes cuboid) - * @return + * @param world World to relight in + * @param selection Region to relight + * @param queue Queue to relight in/from + * @param mode The mode to relight with + * @return Chunks changed */ - public static int fixLighting(World world, Region selection, @Nullable IQueueExtent queue) { - throw new UnsupportedOperationException(); + public static int fixLighting(World world, Region selection, @Nullable IQueueExtent queue, final RelightMode mode) { + final BlockVector3 bot = selection.getMinimumPoint(); + final BlockVector3 top = selection.getMaximumPoint(); + + final int minX = bot.getBlockX() >> 4; + final int minZ = bot.getBlockZ() >> 4; + + final int maxX = top.getBlockX() >> 4; + final int maxZ = top.getBlockZ() >> 4; + + int count = 0; + + if (queue == null) { + World unwrapped = WorldWrapper.unwrap(world); + if (unwrapped instanceof IQueueExtent) { + queue = (IQueueExtent) unwrapped; + } else if (Settings.IMP.QUEUE.PARALLEL_THREADS > 1) { + ParallelQueueExtent parallel = + new ParallelQueueExtent(Fawe.get().getQueueHandler(), world, true); + queue = parallel.getExtent(); + } else { + queue = Fawe.get().getQueueHandler().getQueue(world); + } + } + + NMSRelighter relighter = new NMSRelighter(queue); + for (int x = minX; x <= maxX; x++) { + for (int z = minZ; z <= maxZ; z++) { + relighter.addChunk(x, z, null, 65535); + count++; + } + } + if (mode != RelightMode.NONE) { + relighter.fixSkyLighting(); + relighter.fixBlockLighting(); + } else { + relighter.removeLighting(); + } + relighter.sendChunks(); + return count; } /** diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/IChunkGet.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/IChunkGet.java index 9061dfdf1..c0eb29a35 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/beta/IChunkGet.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/IChunkGet.java @@ -26,6 +26,12 @@ public interface IChunkGet extends IBlocks, Trimable, InputExtent, ITileInput { @Override BlockState getBlock(int x, int y, int z); + @Override + int getSkyLight(int x, int y, int z); + + @Override + int getEmmittedLight(int x, int y, int z); + default void optimize() { } diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/IChunkSet.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/IChunkSet.java index 168120f46..843922f45 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/beta/IChunkSet.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/IChunkSet.java @@ -28,6 +28,20 @@ public interface IChunkSet extends IBlocks, OutputExtent { @Override boolean setTile(int x, int y, int z, CompoundTag tile); + @Override + void setBlockLight(int x, int y, int z, int value); + + @Override + void setSkyLight(int x, int y, int z, int value); + + void setLightLayer(int layer, char[] toSet); + + void setSkyLightLayer(int layer, char[] toSet); + + void removeSectionLighting(int layer, boolean sky); + + void setFullBright(int layer); + void setEntity(CompoundTag tag); void removeEntity(UUID uuid); @@ -40,12 +54,27 @@ public interface IChunkSet extends IBlocks, OutputExtent { return getBiomes() != null; } + char[][] getLight(); + + char[][] getSkyLight(); + + default boolean hasLight() { + return getLight() != null; + } + + // Default to avoid tricky child classes. We only need it in a few cases anyway. + default void setFastMode(boolean fastMode){} + default boolean isFastMode() { return false; } - //default to avoid tricky child classes. We only need it in a few cases anyway. - default void setFastMode(boolean fastMode){} + // Allow setting for bitmask for flushing lighting. Default to avoid tricky child classes. + default void setBitMask(int bitMask){} + + default int getBitMask(){ + return -1; + } @Override IChunkSet reset(); diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/IChunkExtent.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/IChunkExtent.java index 146767571..5ebffe8cc 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/IChunkExtent.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/IChunkExtent.java @@ -55,4 +55,40 @@ public interface IChunkExtent extends Extent { final IChunk chunk = getOrCreateChunk(x >> 4, z >> 4); return chunk.getBiomeType(x & 15, y, z & 15); } + + @Override + default void setSkyLight(int x, int y, int z, int value) { + final IChunk chunk = getOrCreateChunk(x >> 4, z >> 4); + chunk.setSkyLight(x & 15, y, z & 15, value); + } + + @Override + default void setBlockLight(int x, int y, int z, int value) { + final IChunk chunk = getOrCreateChunk(x >> 4, z >> 4); + chunk.setSkyLight(x & 15, y, z & 15, value); + } + + @Override + default int getSkyLight(int x, int y, int z) { + final IChunk chunk = getOrCreateChunk(x >> 4, z >> 4); + return chunk.getSkyLight(x & 15, y, z & 15); + } + + @Override + default int getEmmittedLight(int x, int y, int z) { + final IChunk chunk = getOrCreateChunk(x >> 4, z >> 4); + return chunk.getEmmittedLight(x & 15, y, z & 15); + } + + @Override + default int getBrightness(int x, int y, int z) { + final IChunk chunk = getOrCreateChunk(x >> 4, z >> 4); + return chunk.getBrightness(x & 15, y, z & 15); + } + + @Override + default int getOpacity(int x, int y, int z) { + final IChunk chunk = getOrCreateChunk(x >> 4, z >> 4); + return chunk.getOpacity(x & 15, y, z & 15); + } } diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/blocks/BitSetBlocks.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/blocks/BitSetBlocks.java index 4d7a6a21b..34af72698 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/blocks/BitSetBlocks.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/blocks/BitSetBlocks.java @@ -66,6 +66,18 @@ public class BitSetBlocks implements IChunkSet { return false; } + @Override public void setBlockLight(int x, int y, int z, int value) {} + + @Override public void setSkyLight(int x, int y, int z, int value) {} + + @Override public void setLightLayer(int layer, char[] toSet) {} + + @Override public void setSkyLightLayer(int layer, char[] toSet) {} + + @Override public void removeSectionLighting(int layer, boolean sky) {} + + @Override public void setFullBright(int layer) {} + @Override public void setEntity(CompoundTag tag) { } @@ -118,6 +130,14 @@ public class BitSetBlocks implements IChunkSet { return null; } + @Override public char[][] getLight() { + return new char[0][]; + } + + @Override public char[][] getSkyLight() { + return new char[0][]; + } + @Override public BiomeType getBiomeType(int x, int y, int z) { return null; diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/blocks/CharSetBlocks.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/blocks/CharSetBlocks.java index b7eda2724..1dda73c3a 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/blocks/CharSetBlocks.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/blocks/CharSetBlocks.java @@ -5,24 +5,21 @@ import com.boydti.fawe.beta.IChunkSet; import com.boydti.fawe.beta.implementation.queue.Pool; import com.boydti.fawe.config.Settings; import com.boydti.fawe.object.collection.BlockVector3ChunkMap; -import com.boydti.fawe.util.MathMan; import com.sk89q.jnbt.CompoundTag; import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.world.biome.BiomeType; -import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockStateHolder; -import com.sk89q.worldedit.world.block.BlockTypes; -import com.sk89q.worldedit.world.block.BlockTypesCache; +import org.jetbrains.annotations.Range; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.stream.IntStream; -import org.jetbrains.annotations.Range; public class CharSetBlocks extends CharBlocks implements IChunkSet { private static final Pool POOL = FaweCache.IMP.registerPool(CharSetBlocks.class, CharSetBlocks::new, Settings.IMP.QUEUE.POOL); @@ -31,10 +28,13 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet { } public BiomeType[] biomes; + public char[][] light; + public char[][] skyLight; public BlockVector3ChunkMap tiles; public HashSet entities; public HashSet entityRemoves; private boolean fastMode = false; + private int bitMask = -1; private CharSetBlocks() {} @@ -111,6 +111,80 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet { return true; } + @Override public void setBlockLight(int x, int y, int z, int value) { + if (light == null) { + light = new char[16][]; + } + final int layer = y >> 4; + if (light[layer] == null) { + char[] c = new char[4096]; + Arrays.fill(c, (char) 16); + light[layer] = c; + } + final int index = (y & 15) << 8 | (z & 15) << 4 | (x & 15); + light[y >> 4][index] = (char) value; + } + + @Override public void setSkyLight(int x, int y, int z, int value) { + if (skyLight == null) { + skyLight = new char[16][]; + } + final int layer = y >> 4; + if (skyLight[layer] == null) { + char[] c = new char[4096]; + Arrays.fill(c, (char) 16); + skyLight[layer] = c; + } + final int index = (y & 15) << 8 | (z & 15) << 4 | (x & 15); + skyLight[y >> 4][index] = (char) value; + } + + @Override public void setLightLayer(int layer, char[] toSet) { + if (light == null) { + light = new char[16][]; + } + light[layer] = toSet; + } + + @Override public void setSkyLightLayer(int layer, char[] toSet) { + if (skyLight == null) { + skyLight = new char[16][]; + } + skyLight[layer] = toSet; + } + + @Override public char[][] getLight() { + return light; + } + + @Override public char[][] getSkyLight() { + return skyLight; + } + + @Override public void removeSectionLighting(int layer, boolean sky) { + if (light == null) { + light = new char[16][]; + } + if (light[layer] == null) { + light[layer] = new char[4096]; + } + Arrays.fill(light[layer], (char) 0); + if (sky) { + if (skyLight == null) { + skyLight = new char[16][]; + } + if (skyLight[layer] == null) { + skyLight[layer] = new char[4096]; + } + Arrays.fill(skyLight[layer], (char) 0); + } + } + + @Override public void setFullBright(int layer) { + Arrays.fill(light[layer], (char) 15); + Arrays.fill(skyLight[layer], (char) 15); + } + @Override public boolean setBiome(BlockVector2 position, BiomeType biome) { return setBiome(position.getX(),0, position.getZ(), biome); @@ -142,9 +216,19 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet { return fastMode; } + @Override + public void setBitMask(int bitMask) { + this.bitMask = bitMask; + } + + @Override + public int getBitMask() { + return bitMask; + } + @Override public boolean isEmpty() { - if (biomes != null) { + if (biomes != null || light != null || skyLight != null) { return false; } return IntStream.range(0, 16).noneMatch(this::hasSection); diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/blocks/FallbackChunkGet.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/blocks/FallbackChunkGet.java index c6b596f43..9da99934d 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/blocks/FallbackChunkGet.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/blocks/FallbackChunkGet.java @@ -41,6 +41,14 @@ public class FallbackChunkGet implements IChunkGet { return extent.getBlock(bx + x, y, bz + z); } + @Override public int getSkyLight(int x, int y, int z) { + return extent.getSkyLight(bx + x, y, bz + z); + } + + @Override public int getEmmittedLight(int x, int y, int z) { + return extent.getEmmittedLight(bx + x, y, bz + z); + } + @Override public CompoundTag getTile(int x, int y, int z) { return extent.getFullBlock(bx + x, y, bz + z).getNbtData(); diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/blocks/NullChunkGet.kt b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/blocks/NullChunkGet.kt index a7a5e43de..cd43cb7c1 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/blocks/NullChunkGet.kt +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/blocks/NullChunkGet.kt @@ -12,7 +12,6 @@ import com.sk89q.worldedit.world.block.BaseBlock import com.sk89q.worldedit.world.block.BlockState import com.sk89q.worldedit.world.block.BlockTypes -import java.util.Collections import java.util.UUID import java.util.concurrent.Future @@ -66,6 +65,14 @@ object NullChunkGet : IChunkGet { return false } + override fun getEmmittedLight(x: Int, y: Int, z: Int): Int { + return 15 + } + + override fun getSkyLight(x: Int, y: Int, z: Int): Int { + return 15 + } + override fun reset(): IBlocks? { return null } diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/chunk/ChunkHolder.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/chunk/ChunkHolder.java index 7449cbec0..4be438efc 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/chunk/ChunkHolder.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/chunk/ChunkHolder.java @@ -1,13 +1,13 @@ package com.boydti.fawe.beta.implementation.chunk; import com.boydti.fawe.FaweCache; -import com.boydti.fawe.beta.IQueueChunk; -import com.boydti.fawe.beta.implementation.filter.block.ChunkFilterBlock; import com.boydti.fawe.beta.Filter; import com.boydti.fawe.beta.IChunk; import com.boydti.fawe.beta.IChunkGet; import com.boydti.fawe.beta.IChunkSet; +import com.boydti.fawe.beta.IQueueChunk; import com.boydti.fawe.beta.IQueueExtent; +import com.boydti.fawe.beta.implementation.filter.block.ChunkFilterBlock; import com.boydti.fawe.beta.implementation.queue.Pool; import com.boydti.fawe.config.Settings; import com.sk89q.jnbt.CompoundTag; @@ -17,17 +17,18 @@ import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockStateHolder; +import org.jetbrains.annotations.Range; +import javax.annotation.Nullable; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.Future; -import javax.annotation.Nullable; -import org.jetbrains.annotations.Range; /** * An abstract {@link IChunk} class that implements basic get/set blocks */ +@SuppressWarnings("rawtypes") public class ChunkHolder> implements IQueueChunk { private static final Pool POOL = FaweCache.IMP.registerPool(ChunkHolder.class, ChunkHolder::new, Settings.IMP.QUEUE.POOL); @@ -43,6 +44,8 @@ public class ChunkHolder> implements IQueueChunk { private int chunkX; private int chunkZ; private boolean fastmode; + private int bitMask = -1; // Allow forceful setting of bitmask (for lighting) + private boolean isInit = false; // Lighting handles queue differently. It relies on the chunk cache and not doing init. private ChunkHolder() { this.delegate = NULL; @@ -91,6 +94,14 @@ public class ChunkHolder> implements IQueueChunk { return delegate.set(this).getBiomes(); // TODO return get? } + @Override public char[][] getLight() { + return delegate.set(this).getLight(); + } + + @Override public char[][] getSkyLight() { + return delegate.set(this).getSkyLight(); + } + @Override public void setBlocks(int layer, char[] data) { delegate.set(this).setBlocks(layer, data); @@ -111,6 +122,18 @@ public class ChunkHolder> implements IQueueChunk { this.fastmode = fastmode; } + public void setBitMask(int bitMask) { + this.bitMask = bitMask; + } + + public int getBitMask() { + return bitMask; + } + + public boolean isInit() { + return isInit; + } + @Override public CompoundTag getEntity(UUID uuid) { return delegate.get(this).getEntity(uuid); @@ -139,6 +162,36 @@ public class ChunkHolder> implements IQueueChunk { return chunk.chunkSet.setBlock(x, y, z, block); } + @Override + public void setSkyLight(ChunkHolder chunk, int x, int y, int z, int value) { + chunk.chunkSet.setSkyLight(x, y, z, value); + } + + @Override + public void setBlockLight(ChunkHolder chunk, int x, int y, int z, int value) { + chunk.chunkSet.setBlockLight(x, y, z, value); + } + + @Override + public void removeSectionLighting(ChunkHolder chunk, int layer, boolean sky) { + chunk.chunkSet.removeSectionLighting(layer, sky); + } + + @Override + public void setFullBright(ChunkHolder chunk, int layer){ + chunk.chunkSet.setFullBright(layer); + } + + @Override + public void setLightLayer(ChunkHolder chunk, int layer, char[] toSet) { + chunk.chunkSet.setLightLayer(layer, toSet); + } + + @Override + public void setSkyLightLayer(ChunkHolder chunk, int layer, char[] toSet) { + chunk.chunkSet.setSkyLightLayer(layer, toSet); + } + @Override public BiomeType getBiome(ChunkHolder chunk, int x, int y, int z) { return chunk.chunkExisting.getBiomeType(x, y, z); @@ -154,6 +207,44 @@ public class ChunkHolder> implements IQueueChunk { int z) { return chunk.chunkExisting.getFullBlock(x, y, z); } + + @Override + public int getSkyLight(ChunkHolder chunk, int x, int y, int z) { + if (chunk.chunkSet.getSkyLight() != null) { + int layer = y >> 4; + if (chunk.chunkSet.getSkyLight()[layer] != null) { + int setLightValue = chunk.chunkSet.getSkyLight()[layer][(y & 15) << 8 | (z & 15) << 4 | (x & 15)]; + if (setLightValue < 16) { + return setLightValue; + } + } + } + return chunk.chunkExisting.getSkyLight(x, y, z); + } + + @Override + public int getEmmittedLight(ChunkHolder chunk, int x, int y, int z) { + if (chunk.chunkSet.getLight() != null) { + int layer = y >> 4; + if (chunk.chunkSet.getLight()[layer] != null) { + int setLightValue = chunk.chunkSet.getLight()[layer][(y & 15) << 8 | (z & 15) << 4 | (x & 15)]; + if (setLightValue < 16) { + return setLightValue; + } + } + } + return chunk.chunkExisting.getEmmittedLight(x, y, z); + } + + @Override + public int getBrightness(ChunkHolder chunk, int x, int y, int z) { + return chunk.chunkExisting.getBrightness(x, y, z); + } + + @Override + public int getOpacity(ChunkHolder chunk, int x, int y, int z) { + return chunk.chunkExisting.getOpacity(x, y, z); + } }; private static final IBlockDelegate GET = new IBlockDelegate() { @@ -185,6 +276,48 @@ public class ChunkHolder> implements IQueueChunk { return chunk.setBlock(x, y, z, block); } + @Override + public void setSkyLight(ChunkHolder chunk, int x, int y, int z, int value) { + chunk.getOrCreateSet(); + chunk.delegate = BOTH; + chunk.setSkyLight(x, y, z, value); + } + + @Override + public void setBlockLight(ChunkHolder chunk, int x, int y, int z, int value) { + chunk.getOrCreateSet(); + chunk.delegate = BOTH; + chunk.setBlockLight(x, y, z, value); + } + + @Override + public void removeSectionLighting(ChunkHolder chunk, int layer, boolean sky) { + chunk.getOrCreateSet(); + chunk.delegate = BOTH; + chunk.removeSectionLighting(layer, sky); + } + + @Override + public void setFullBright(ChunkHolder chunk, int layer){ + chunk.getOrCreateSet(); + chunk.delegate = BOTH; + chunk.setFullBright(layer); + } + + @Override + public void setLightLayer(ChunkHolder chunk, int layer, char[] toSet) { + chunk.getOrCreateSet(); + chunk.delegate = BOTH; + chunk.setLightLayer(layer, toSet); + } + + @Override + public void setSkyLightLayer(ChunkHolder chunk, int layer, char[] toSet) { + chunk.getOrCreateSet(); + chunk.delegate = BOTH; + chunk.setSkyLightLayer(layer, toSet); + } + @Override public BiomeType getBiome(ChunkHolder chunk, int x, int y, int z) { return chunk.chunkExisting.getBiomeType(x, y, z); @@ -200,6 +333,26 @@ public class ChunkHolder> implements IQueueChunk { int z) { return chunk.chunkExisting.getFullBlock(x, y, z); } + + @Override + public int getSkyLight(ChunkHolder chunk, int x, int y, int z) { + return chunk.chunkExisting.getSkyLight(x, y, z); + } + + @Override + public int getEmmittedLight(ChunkHolder chunk, int x, int y, int z) { + return chunk.chunkExisting.getEmmittedLight(x, y, z); + } + + @Override + public int getBrightness(ChunkHolder chunk, int x, int y, int z) { + return chunk.chunkExisting.getBrightness(x, y, z); + } + + @Override + public int getOpacity(ChunkHolder chunk, int x, int y, int z) { + return chunk.chunkExisting.getOpacity(x, y, z); + } }; private static final IBlockDelegate SET = new IBlockDelegate() { @@ -226,6 +379,36 @@ public class ChunkHolder> implements IQueueChunk { return chunk.chunkSet.setBlock(x, y, z, block); } + @Override + public void setSkyLight(ChunkHolder chunk, int x, int y, int z, int value) { + chunk.chunkSet.setSkyLight(x, y, z, value); + } + + @Override + public void setBlockLight(ChunkHolder chunk, int x, int y, int z, int value) { + chunk.chunkSet.setBlockLight(x, y, z, value); + } + + @Override + public void removeSectionLighting(ChunkHolder chunk, int layer, boolean sky) { + chunk.chunkSet.removeSectionLighting(layer, sky); + } + + @Override + public void setFullBright(ChunkHolder chunk, int layer){ + chunk.chunkSet.setFullBright(layer); + } + + @Override + public void setLightLayer(ChunkHolder chunk, int layer, char[] toSet) { + chunk.chunkSet.setLightLayer(layer, toSet); + } + + @Override + public void setSkyLightLayer(ChunkHolder chunk, int layer, char[] toSet) { + chunk.chunkSet.setSkyLightLayer(layer, toSet); + } + @Override public BiomeType getBiome(ChunkHolder chunk, int x, int y, int z) { chunk.getOrCreateGet(); @@ -247,6 +430,52 @@ public class ChunkHolder> implements IQueueChunk { chunk.delegate = BOTH; return chunk.getFullBlock(x, y, z); } + + @Override + public int getSkyLight(ChunkHolder chunk, int x, int y, int z) { + if (chunk.chunkSet.getSkyLight() != null) { + int layer = y >> 4; + if (chunk.chunkSet.getSkyLight()[layer] != null) { + int setLightValue = chunk.chunkSet.getSkyLight()[layer][(y & 15) << 8 | (z & 15) << 4 | (x & 15)]; + if (setLightValue < 16) { + return setLightValue; + } + } + } + chunk.getOrCreateGet(); + chunk.delegate = BOTH; + return chunk.getSkyLight(x, y, z); + } + + @Override + public int getEmmittedLight(ChunkHolder chunk, int x, int y, int z) { + if (chunk.chunkSet.getLight() != null) { + int layer = y >> 4; + if (chunk.chunkSet.getLight()[layer] != null) { + int setLightValue = chunk.chunkSet.getLight()[layer][(y & 15) << 8 | (z & 15) << 4 | (x & 15)]; + if (setLightValue < 16) { + return setLightValue; + } + } + } + chunk.getOrCreateGet(); + chunk.delegate = BOTH; + return chunk.getEmmittedLight(x, y, z); + } + + @Override + public int getBrightness(ChunkHolder chunk, int x, int y, int z) { + chunk.getOrCreateGet(); + chunk.delegate = BOTH; + return chunk.getBrightness(x, y, z); + } + + @Override + public int getOpacity(ChunkHolder chunk, int x, int y, int z) { + chunk.getOrCreateGet(); + chunk.delegate = BOTH; + return chunk.getOpacity(x, y, z); + } }; private static final IBlockDelegate NULL = new IBlockDelegate() { @@ -303,6 +532,80 @@ public class ChunkHolder> implements IQueueChunk { chunk.chunkExisting.trim(false); return chunk.getFullBlock(x, y, z); } + + @Override + public void setSkyLight(ChunkHolder chunk, int x, int y, int z, int value) { + chunk.getOrCreateSet(); + chunk.delegate = SET; + chunk.setSkyLight(x, y, z, value); + } + + @Override + public void setBlockLight(ChunkHolder chunk, int x, int y, int z, int value) { + chunk.getOrCreateSet(); + chunk.delegate = SET; + chunk.setBlockLight(x, y, z, value); + } + + @Override + public void removeSectionLighting(ChunkHolder chunk, int layer, boolean sky) { + chunk.getOrCreateSet(); + chunk.delegate = SET; + chunk.removeSectionLighting(layer, sky); + } + + @Override + public void setFullBright(ChunkHolder chunk, int layer){ + chunk.getOrCreateSet(); + chunk.delegate = SET; + chunk.setFullBright(layer); + } + + @Override + public void setLightLayer(ChunkHolder chunk, int layer, char[] toSet) { + chunk.getOrCreateSet(); + chunk.delegate = SET; + chunk.setLightLayer(layer, toSet); + } + + @Override + public void setSkyLightLayer(ChunkHolder chunk, int layer, char[] toSet) { + chunk.getOrCreateSet(); + chunk.delegate = SET; + chunk.setSkyLightLayer(layer, toSet); + } + + @Override + public int getSkyLight(ChunkHolder chunk, int x, int y, int z) { + chunk.getOrCreateGet(); + chunk.delegate = GET; + chunk.chunkExisting.trim(false); + return chunk.getSkyLight(x, y, z); + } + + @Override + public int getEmmittedLight(ChunkHolder chunk, int x, int y, int z) { + chunk.getOrCreateGet(); + chunk.delegate = GET; + chunk.chunkExisting.trim(false); + return chunk.getEmmittedLight(x, y, z); + } + + @Override + public int getBrightness(ChunkHolder chunk, int x, int y, int z) { + chunk.getOrCreateGet(); + chunk.delegate = GET; + chunk.chunkExisting.trim(false); + return chunk.getBrightness(x, y, z); + } + + @Override + public int getOpacity(ChunkHolder chunk, int x, int y, int z) { + chunk.getOrCreateGet(); + chunk.delegate = GET; + chunk.chunkExisting.trim(false); + return chunk.getOpacity(x, y, z); + } }; @Override @@ -416,11 +719,13 @@ public class ChunkHolder> implements IQueueChunk { delegate = NULL; } chunkExisting = null; + isInit = true; } @Override public synchronized T call() { if (chunkSet != null) { + chunkSet.setBitMask(bitMask); return this.call(chunkSet, this::recycle); } return null; @@ -481,6 +786,54 @@ public class ChunkHolder> implements IQueueChunk { return delegate.getFullBlock(this, x, y, z); } + @Override + public void setSkyLight(int x, int y, int z, int value) { + delegate.setSkyLight(this, x, y, z, value); + } + + @Override public void removeSectionLighting(int layer, boolean sky) { + delegate.removeSectionLighting(this, layer, sky); + } + + @Override public void setFullBright(int layer) { + delegate.setFullBright(this, layer); + } + + @Override + public void setBlockLight(int x, int y, int z, int value) { + delegate.setBlockLight(this, x, y, z, value); + } + + @Override + public void setLightLayer(int layer, char[] toSet) { + delegate.setLightLayer(this, layer, toSet); + } + + @Override + public void setSkyLightLayer(int layer, char[] toSet) { + delegate.setSkyLightLayer(this, layer, toSet); + } + + @Override + public int getSkyLight(int x, int y, int z) { + return delegate.getSkyLight(this, x, y, z); + } + + @Override + public int getEmmittedLight(int x, int y, int z) { + return delegate.getEmmittedLight(this, x, y, z); + } + + @Override + public int getBrightness(int x, int y, int z) { + return delegate.getBrightness(this, x, y, z); + } + + @Override + public int getOpacity(int x, int y, int z) { + return delegate.getOpacity(this, x, y, z); + } + public interface IBlockDelegate { > IChunkGet get(ChunkHolder chunk); IChunkSet set(ChunkHolder chunk); @@ -494,5 +847,25 @@ public class ChunkHolder> implements IQueueChunk { BlockState getBlock(ChunkHolder chunk, int x, int y, int z); BaseBlock getFullBlock(ChunkHolder chunk, int x, int y, int z); + + void setSkyLight(ChunkHolder chunk, int x, int y, int z, int value); + + void setBlockLight(ChunkHolder chunk, int x, int y, int z, int value); + + void removeSectionLighting(ChunkHolder chunk, int layer, boolean sky); + + void setFullBright(ChunkHolder chunk, int layer); + + void setLightLayer(ChunkHolder chunk, int layer, char[] toSet); + + void setSkyLightLayer(ChunkHolder chunk, int layer, char[] toSet); + + int getSkyLight(ChunkHolder chunk, int x, int y, int z); + + int getEmmittedLight(ChunkHolder chunk, int x, int y, int z); + + int getBrightness(ChunkHolder chunk, int x, int y, int z); + + int getOpacity(ChunkHolder chunk, int x, int y, int z); } } diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/chunk/NullChunk.kt b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/chunk/NullChunk.kt index 6a6f33358..740b178ce 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/chunk/NullChunk.kt +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/chunk/NullChunk.kt @@ -62,6 +62,46 @@ object NullChunk : IQueueChunk { return null } + override fun getSkyLight(x: Int, y: Int, z: Int): Int { + return 15 + } + + override fun getLight(): Array { + return emptyArray() + } + + override fun getSkyLight(): Array { + return emptyArray() + } + + override fun getEmmittedLight(x: Int, y: Int, z: Int): Int { + return 15 + } + + override fun setSkyLight(x: Int, y: Int, z: Int, value: Int) { + + } + + override fun setBlockLight(x: Int, y: Int, z: Int, value: Int) { + + } + + override fun setFullBright(layer: Int) { + + } + + override fun removeSectionLighting(layer: Int, sky: Boolean) { + + } + + override fun setSkyLightLayer(layer: Int, toSet: CharArray?) { + + } + + override fun setLightLayer(layer: Int, toSet: CharArray?) { + + } + override fun getBiomes(): Array? { return null } diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/lighting/NMSRelighter.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/lighting/NMSRelighter.java new file mode 100644 index 000000000..505ae5f20 --- /dev/null +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/lighting/NMSRelighter.java @@ -0,0 +1,645 @@ +package com.boydti.fawe.beta.implementation.lighting; + +import com.boydti.fawe.beta.IQueueChunk; +import com.boydti.fawe.beta.IQueueExtent; +import com.boydti.fawe.beta.implementation.chunk.ChunkHolder; +import com.boydti.fawe.config.Settings; +import com.boydti.fawe.object.RunnableVal; +import com.boydti.fawe.object.collection.BlockVectorSet; +import com.boydti.fawe.util.MathMan; +import com.boydti.fawe.util.TaskManager; +import com.sk89q.worldedit.math.MutableBlockVector3; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicBoolean; + +public class NMSRelighter implements Relighter { + private final IQueueExtent queue; + + private final Map skyToRelight; + private final Object present = new Object(); + private final Map chunksToSend; + private final ConcurrentLinkedQueue extentdSkyToRelight = new ConcurrentLinkedQueue<>(); + + private final Map lightQueue; + private final AtomicBoolean lightLock = new AtomicBoolean(false); + private final ConcurrentHashMap concurrentLightQueue; + + private final int maxY; + + public final MutableBlockVector3 mutableBlockPos = new MutableBlockVector3(0, 0, 0); + + private static final int DISPATCH_SIZE = 64; + private boolean removeFirst; + + public NMSRelighter(IQueueExtent queue) { + this.queue = queue; + this.skyToRelight = new Long2ObjectOpenHashMap<>(12); + this.lightQueue = new Long2ObjectOpenHashMap<>(12); + this.chunksToSend = new Long2ObjectOpenHashMap<>(12); + this.concurrentLightQueue = new ConcurrentHashMap<>(12); + this.maxY = queue.getMaxY(); + } + + @Override + public boolean isEmpty() { + return skyToRelight.isEmpty() && lightQueue.isEmpty() && extentdSkyToRelight.isEmpty() && concurrentLightQueue.isEmpty(); + } + + @Override + public synchronized void removeAndRelight(boolean sky) { + removeFirst = true; + fixLightingSafe(sky); + removeFirst = false; + } + + /** + * Utility method to reduce duplicated code to ensure values are written to long[][][] without NPEs + * + * @param x x coordinate + * @param y y coordinate + * @param z z coordinate + * @param map long[][][] to add values to + */ + private void set(int x, int y, int z, long[][][] map) { + long[][] m1 = map[z]; + if (m1 == null) { + m1 = map[z] = new long[16][]; + } + long[] m2 = m1[x]; + if (m2 == null) { + m2 = m1[x] = new long[4]; + } + long value = m2[y >> 6] |= 1l << y; + } + + public void addLightUpdate(int x, int y, int z) { + long index = MathMan.pairInt(x >> 4, z >> 4); + if (lightLock.compareAndSet(false, true)) { + synchronized (lightQueue) { + try { + long[][][] currentMap = lightQueue.get(index); + if (currentMap == null) { + currentMap = new long[16][][]; + this.lightQueue.put(index, currentMap); + } + set(x & 15, y, z & 15, currentMap); + } finally { + lightLock.set(false); + } + } + } else { + long[][][] currentMap = concurrentLightQueue.get(index); + if (currentMap == null) { + currentMap = new long[16][][]; + this.concurrentLightQueue.put(index, currentMap); + } + set(x & 15, y, z & 15, currentMap); + } + } + + public synchronized void clear() { + extentdSkyToRelight.clear(); + skyToRelight.clear(); + chunksToSend.clear(); + lightQueue.clear(); + } + + public boolean addChunk(int cx, int cz, byte[] fix, int bitmask) { + RelightSkyEntry toPut = new RelightSkyEntry(cx, cz, fix, bitmask); + extentdSkyToRelight.add(toPut); + return true; + } + + private synchronized Map getSkyMap() { + RelightSkyEntry entry; + while ((entry = extentdSkyToRelight.poll()) != null) { + long pair = MathMan.pairInt(entry.x, entry.z); + RelightSkyEntry existing = skyToRelight.put(pair, entry); + if (existing != null) { + entry.bitmask |= existing.bitmask; + if (entry.fix != null) { + for (int i = 0; i < entry.fix.length; i++) { + entry.fix[i] &= existing.fix[i]; + } + } + } + } + return skyToRelight; + } + + public synchronized void removeLighting() { + Iterator> iter = getSkyMap().entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + RelightSkyEntry chunk = entry.getValue(); + long pair = entry.getKey(); + Integer existing = chunksToSend.get(pair); + chunksToSend.put(pair, chunk.bitmask | (existing != null ? existing : 0)); + ChunkHolder iChunk = (ChunkHolder) queue.getOrCreateChunk(chunk.x, chunk.z); + if (!iChunk.isInit()) { + iChunk.init(queue, chunk.x, chunk.z); + } + for (int i = 0; i < 16; i++) { + iChunk.removeSectionLighting(i, true); + } + iter.remove(); + } + } + + public void updateBlockLight(Map map) { + int size = map.size(); + if (size == 0) { + return; + } + Queue lightPropagationQueue = new ArrayDeque<>(32); + Queue lightRemovalQueue = new ArrayDeque<>(32); + Map visited = new HashMap<>(32); + Map removalVisited = new HashMap<>(32); + + Iterator> iter = map.entrySet().iterator(); + while (iter.hasNext() && size-- > 0) { + Map.Entry entry = iter.next(); + long index = entry.getKey(); + long[][][] blocks = entry.getValue(); + int chunkX = MathMan.unpairIntX(index); + int chunkZ = MathMan.unpairIntY(index); + int bx = chunkX << 4; + int bz = chunkZ << 4; + ChunkHolder iChunk = (ChunkHolder) queue.getOrCreateChunk(chunkX, chunkZ); + if (!iChunk.isInit()) { + iChunk.init(queue, chunkX, chunkZ); + } + for (int lz = 0; lz < blocks.length; lz++) { + long[][] m1 = blocks[lz]; + if (m1 == null) continue; + for (int lx = 0; lx < m1.length; lx++) { + long[] m2 = m1[lx]; + if (m2 == null) continue; + for (int i = 0; i < m2.length; i++) { + int yStart = i << 6; + long value = m2[i]; + if (value != 0) { + for (int j = 0; j < 64; j++) { + if (((value >> j) & 1) == 1) { + int x = lx + bx; + int y = yStart + j; + int z = lz + bz; + int oldLevel = iChunk.getEmmittedLight(lx, y, lz); + int newLevel = iChunk.getBrightness(lx, y, lz); + if (oldLevel != newLevel) { + iChunk.setBlockLight(lx, y, lz, newLevel); + MutableBlockVector3 node = new MutableBlockVector3(x, y, z); + if (newLevel < oldLevel) { + removalVisited.put(node, present); + lightRemovalQueue.add(new Object[]{node, oldLevel}); + } else { + visited.put(node, present); + lightPropagationQueue.add(node); + } + } + } + } + } + } + } + } + iter.remove(); + } + + while (!lightRemovalQueue.isEmpty()) { + Object[] val = lightRemovalQueue.poll(); + MutableBlockVector3 node = (MutableBlockVector3) val[0]; + int lightLevel = (int) val[1]; + + this.computeRemoveBlockLight(node.getX() - 1, node.getY(), node.getZ(), lightLevel, lightRemovalQueue, lightPropagationQueue, removalVisited, visited); + this.computeRemoveBlockLight(node.getX() + 1, node.getY(), node.getZ(), lightLevel, lightRemovalQueue, lightPropagationQueue, removalVisited, visited); + if (node.getY() > 0) { + this.computeRemoveBlockLight(node.getX(), node.getY() - 1, node.getZ(), lightLevel, lightRemovalQueue, lightPropagationQueue, removalVisited, visited); + } + if (node.getY() < 255) { + this.computeRemoveBlockLight(node.getX(), node.getY() + 1, node.getZ(), lightLevel, lightRemovalQueue, lightPropagationQueue, removalVisited, visited); + } + this.computeRemoveBlockLight(node.getX(), node.getY(), node.getZ() - 1, lightLevel, lightRemovalQueue, lightPropagationQueue, removalVisited, visited); + this.computeRemoveBlockLight(node.getX(), node.getY(), node.getZ() + 1, lightLevel, lightRemovalQueue, lightPropagationQueue, removalVisited, visited); + } + + while (!lightPropagationQueue.isEmpty()) { + MutableBlockVector3 node = lightPropagationQueue.poll(); + ChunkHolder iChunk = (ChunkHolder) queue.getOrCreateChunk(node.getX() >> 4, node.getZ() >> 4); + if (!iChunk.isInit()) { + iChunk.init(queue, node.getX() >> 4, node.getZ() >> 4); + } + int lightLevel = iChunk.getEmmittedLight(node.getX() & 15, node.getY(), node.getZ() & 15); + if (lightLevel > 1) { + this.computeSpreadBlockLight(node.getX() - 1, node.getY(), node.getZ(), lightLevel, lightPropagationQueue, visited); + this.computeSpreadBlockLight(node.getX() + 1, node.getY(), node.getZ(), lightLevel, lightPropagationQueue, visited); + if (node.getY() > 0) { + this.computeSpreadBlockLight(node.getX(), node.getY() - 1, node.getZ(), lightLevel, lightPropagationQueue, visited); + } + if (node.getY() < 255) { + this.computeSpreadBlockLight(node.getX(), node.getY() + 1, node.getZ(), lightLevel, lightPropagationQueue, visited); + } + this.computeSpreadBlockLight(node.getX(), node.getY(), node.getZ() - 1, lightLevel, lightPropagationQueue, visited); + this.computeSpreadBlockLight(node.getX(), node.getY(), node.getZ() + 1, lightLevel, lightPropagationQueue, visited); + } + } + } + + private void computeRemoveBlockLight(int x, int y, int z, int currentLight, Queue queue, Queue spreadQueue, Map visited, + Map spreadVisited) { + ChunkHolder iChunk = (ChunkHolder) this.queue.getOrCreateChunk(x >> 4, z >> 4); + if (!iChunk.isInit()) { + iChunk.init(this.queue, x >> 4, z >> 4); + } + int current = iChunk.getEmmittedLight(x & 15, y, z & 15); + if (current != 0 && current < currentLight) { + iChunk.setBlockLight(x, y, z, 0); + if (current > 1) { + if (!visited.containsKey(mutableBlockPos)) { + MutableBlockVector3 index = new MutableBlockVector3(x, y, z); + visited.put(index, present); + queue.add(new Object[]{index, current}); + } + } + } else if (current >= currentLight) { + mutableBlockPos.setComponents(x, y, z); + if (!spreadVisited.containsKey(mutableBlockPos)) { + MutableBlockVector3 index = new MutableBlockVector3(x, y, z); + spreadVisited.put(index, present); + spreadQueue.add(index); + } + } + } + + private void computeSpreadBlockLight(int x, int y, int z, int currentLight, Queue queue, Map visited) { + currentLight = currentLight - Math.max(1, this.queue.getOpacity(x, y, z)); + if (currentLight > 0) { + ChunkHolder iChunk = (ChunkHolder) this.queue.getOrCreateChunk(x >> 4, z >> 4); + if (!iChunk.isInit()) { + iChunk.init(this.queue, x >> 4, z >> 4); + } + int current = iChunk.getEmmittedLight(x & 15, y, z & 15); + if (current < currentLight) { + iChunk.setBlockLight(x, y, z, currentLight); + mutableBlockPos.setComponents(x, y, z); + if (!visited.containsKey(mutableBlockPos)) { + visited.put(new MutableBlockVector3(x, y, z), present); + if (currentLight > 1) { + queue.add(new MutableBlockVector3(x, y, z)); + } + } + } + } + } + + public void fixLightingSafe(boolean sky) { + if (isEmpty()) return; + try { + if (sky) { + fixSkyLighting(); + } else { + synchronized (this) { + Map map = getSkyMap(); + Iterator> iter = map.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + chunksToSend.put(entry.getKey(), entry.getValue().bitmask); + iter.remove(); + } + } + } + fixBlockLighting(); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + public void fixBlockLighting() { + synchronized (lightQueue) { + while (!lightLock.compareAndSet(false, true)); + try { + updateBlockLight(this.lightQueue); + } finally { + lightLock.set(false); + } + } + } + + public synchronized void sendChunks() { + Iterator> iter = chunksToSend.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + long pair = entry.getKey(); + int bitMask = entry.getValue(); + int x = MathMan.unpairIntX(pair); + int z = MathMan.unpairIntY(pair); + ChunkHolder chunk = (ChunkHolder) queue.getOrCreateChunk(x, z); + chunk.setBitMask(bitMask); + iter.remove(); + } + if (Settings.IMP.LIGHTING.ASYNC) { + queue.flush(); + } else { + TaskManager.IMP.sync(new RunnableVal() { + @Override public void run(Object value) { + queue.flush(); + } + }); + } + } + + public synchronized void fixSkyLighting() { + // Order chunks + Map map = getSkyMap(); + ArrayList chunksList = new ArrayList<>(map.size()); + Iterator> iter = map.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + chunksToSend.put(entry.getKey(), entry.getValue().bitmask); + chunksList.add(entry.getValue()); + iter.remove(); + } + Collections.sort(chunksList); + int size = chunksList.size(); + if (size > DISPATCH_SIZE) { + int amount = (size + DISPATCH_SIZE - 1) / DISPATCH_SIZE; + for (int i = 0; i < amount; i++) { + int start = i * DISPATCH_SIZE; + int end = Math.min(size, start + DISPATCH_SIZE); + List sub = chunksList.subList(start, end); + fixSkyLighting(sub); + } + } else { + fixSkyLighting(chunksList); + } + } + + public void fill(byte[] mask, int chunkX, int y, int chunkZ, byte reason) { + if (y >= 16) { + Arrays.fill(mask, (byte) 15); + return; + } + switch (reason) { + case SkipReason.SOLID: { + Arrays.fill(mask, (byte) 0); + return; + } + case SkipReason.AIR: { + int bx = chunkX << 4; + int bz = chunkZ << 4; + int index = 0; + for (int z = 0; z < 16; z++) { + for (int x = 0; x < 16; x++) { + mask[index++] = (byte) queue.getSkyLight(bx + x, y, bz + z); + } + } + } + } + } + + private void fixSkyLighting(List sorted) { + RelightSkyEntry[] chunks = sorted.toArray(new RelightSkyEntry[sorted.size()]); + boolean remove = this.removeFirst; + BlockVectorSet chunkSet = null; + if (remove) { + chunkSet = new BlockVectorSet(); + BlockVectorSet tmpSet = new BlockVectorSet(); + for (RelightSkyEntry chunk : chunks) { + tmpSet.add(chunk.x, 0, chunk.z); + } + for (RelightSkyEntry chunk : chunks) { + int x = chunk.x; + int z = chunk.z; + if (tmpSet.contains(x + 1, 0, z) && tmpSet.contains(x - 1, 0, z) && tmpSet.contains(x, 0, z + 1) && tmpSet.contains(x, 0, z - 1)) { + chunkSet.add(x, 0, z); + } + } + } + for (int y = 255; y > 0; y--) { + for (RelightSkyEntry chunk : chunks) { // Propogate skylight + int layer = y >> 4; + byte[] mask = chunk.mask; + if ((y & 15) == 15 && chunk.fix[layer] != SkipReason.NONE) { + if ((y & 15) == 0 && layer != 0 && chunk.fix[layer - 1] == SkipReason.NONE) { + fill(mask, chunk.x, y, chunk.z, chunk.fix[layer]); + } + continue; + } + int bx = chunk.x << 4; + int bz = chunk.z << 4; + ChunkHolder iChunk = (ChunkHolder) queue.getOrCreateChunk(chunk.x, chunk.z); + if (!iChunk.isInit()) { + iChunk.init(queue, chunk.x, chunk.z); + } + chunk.smooth = false; + + if (remove && (y & 15) == 15 && chunkSet.contains(chunk.x, 0, chunk.z)) { + iChunk.removeSectionLighting(y >> 4, true); + } + + for (int j = 0; j < 256; j++) { + int x = j & 15; + int z = j >> 4; + byte value = mask[j]; + byte pair = MathMan.pair16(iChunk.getOpacity(x, y, z), iChunk.getBrightness(x, y, z)); + int opacity = MathMan.unpair16x(pair); + int brightness = MathMan.unpair16y(pair); + if (brightness > 1) { + addLightUpdate(bx + x, y, bz + z); + } + switch (value) { + case 0: + if (opacity > 1) { + iChunk.setSkyLight(x, y, z, 0); + continue; + } + break; + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + case 9: + case 10: + case 11: + case 12: + case 13: + case 14: + if (opacity >= value) { + mask[j] = 0; + iChunk.setSkyLight(x, y, z, 0); + continue; + } + if (opacity <= 1) { + mask[j] = --value; + } else { + mask[j] = value = (byte) Math.max(0, value - opacity); + } + break; + case 15: + if (opacity > 1) { + value -= opacity; + mask[j] = value; + } + iChunk.setSkyLight(x, y, z, value); + continue; + } + chunk.smooth = true; + iChunk.setSkyLight(x, y, z, value); + } + } + for (RelightSkyEntry chunk : chunks) { // Smooth forwards + if (chunk.smooth) { + smoothSkyLight(chunk, y, true); + } + } + for (int i = chunks.length - 1; i >= 0; i--) { // Smooth backwards + RelightSkyEntry chunk = chunks[i]; + if (chunk.smooth) { + smoothSkyLight(chunk, y, false); + } + } + } + } + + public void smoothSkyLight(RelightSkyEntry chunk, int y, boolean direction) { + byte[] mask = chunk.mask; + ChunkHolder iChunk = (ChunkHolder) queue.getOrCreateChunk(chunk.x, chunk.z); + ChunkHolder iChunkx; + ChunkHolder iChunkz; + if (!iChunk.isInit()) { + iChunk.init(queue, chunk.x, chunk.z); + } + if (direction) { + iChunkx = (ChunkHolder) queue.getOrCreateChunk(chunk.x - 1, chunk.z); + iChunkz = (ChunkHolder) queue.getOrCreateChunk(chunk.x, chunk.z - 1); + if (!iChunkx.isInit()) { + iChunkx.init(queue, chunk.x - 1, chunk.z); + } + if (!iChunkz.isInit()) { + iChunkz.init(queue, chunk.x, chunk.z - 1); + } + for (int j = 0; j < 256; j++) { + int x = j & 15; + int z = j >> 4; + if (mask[j] >= 14 || (mask[j] == 0 && iChunk.getOpacity(x, y, z) > 1)) { + continue; + } + byte value = mask[j]; + if (x != 0 && z != 0) { + if ((value = (byte) Math.max(iChunk.getSkyLight(x - 1, y, z) - 1, value)) >= 14) ; + else if ((value = (byte) Math.max(iChunk.getSkyLight(x, y, z - 1) - 1, value)) >= 14) ; + if (value > mask[j]) iChunk.setSkyLight(x, y, z, mask[j] = value); + } else if (x == 0 && z == 0) { + if ((value = (byte) Math.max(iChunkx.getSkyLight(15, y, z) - 1, value)) >= 14) ; + else if ((value = (byte) Math.max(iChunkz.getSkyLight(x, y, 15) - 1, value)) >= 14) ; + if (value > mask[j]) iChunk.setSkyLight(x, y, z, mask[j] = value); + } else if (x == 0) { + if ((value = (byte) Math.max(iChunkx.getSkyLight(15, y, z) - 1, value)) >= 14) ; + else if ((value = (byte) Math.max(iChunk.getSkyLight(x, y, z - 1) - 1, value)) >= 14) ; + if (value > mask[j]) iChunk.setSkyLight(x, y, z, mask[j] = value); + } else { + if ((value = (byte) Math.max(iChunk.getSkyLight(x - 1, y, z) - 1, value)) >= 14) ; + else if ((value = (byte) Math.max(iChunkz.getSkyLight(x, y, 15) - 1, value)) >= 14) ; + if (value > mask[j]) iChunk.setSkyLight(x, y, z, mask[j] = value); + } + } + } else { + iChunkx = (ChunkHolder) queue.getOrCreateChunk(chunk.x + 1, chunk.z); + iChunkz = (ChunkHolder) queue.getOrCreateChunk(chunk.x, chunk.z + 1); + if (!iChunkx.isInit()) { + iChunkx.init(queue, chunk.x - 1, chunk.z); + } + if (!iChunkz.isInit()) { + iChunkz.init(queue, chunk.x, chunk.z - 1); + } + for (int j = 255; j >= 0; j--) { + int x = j & 15; + int z = j >> 4; + if (mask[j] >= 14 || (mask[j] == 0 && iChunk.getOpacity(x, y, z) > 1)) { + continue; + } + byte value = mask[j]; + if ( x != 15 && z != 15) { + if ((value = (byte) Math.max(iChunk.getSkyLight(x + 1, y, z) - 1, value)) >= 14) ; + else if ((value = (byte) Math.max(iChunk.getSkyLight(x, y, z + 1) - 1, value)) >= 14) ; + if (value > mask[j]) iChunk.setSkyLight(x, y, z, mask[j] = value); + } else if (x == 15 && z == 15) { + if ((value = (byte) Math.max(iChunkx.getSkyLight(0, y, z) - 1, value)) >= 14) ; + else if ((value = (byte) Math.max(iChunkz.getSkyLight(x, y, 0) - 1, value)) >= 14) ; + if (value > mask[j]) iChunk.setSkyLight(x, y, z, mask[j] = value); + } else if (x == 15) { + if ((value = (byte) Math.max(iChunkx.getSkyLight(0, y, z) - 1, value)) >= 14) ; + else if ((value = (byte) Math.max(iChunk.getSkyLight(x, y, z + 1) - 1, value)) >= 14) ; + if (value > mask[j]) iChunk.setSkyLight(x, y, z, mask[j] = value); + } else { + if ((value = (byte) Math.max(iChunk.getSkyLight(x + 1, y, z) - 1, value)) >= 14) ; + else if ((value = (byte) Math.max(iChunkz.getSkyLight(x, y, 0) - 1, value)) >= 14) ; + if (value > mask[j]) iChunk.setSkyLight(x, y, z, mask[j] = value); + } + } + } + } + + private class RelightSkyEntry implements Comparable { + public final int x; + public final int z; + public final byte[] mask; + public final byte[] fix; + public int bitmask; + public boolean smooth; + + public RelightSkyEntry(int x, int z, byte[] fix, int bitmask) { + this.x = x; + this.z = z; + byte[] array = new byte[256]; + Arrays.fill(array, (byte) 15); + this.mask = array; + this.bitmask = bitmask; + if (fix == null) { + this.fix = new byte[(maxY + 1) >> 4]; + Arrays.fill(this.fix, SkipReason.NONE); + } else { + this.fix = fix; + } + } + + @Override + public String toString() { + return x + "," + z; + } + + @Override + public int compareTo(Object o) { + RelightSkyEntry other = (RelightSkyEntry) o; + if (other.x < x) { + return 1; + } + if (other.x > x) { + return -1; + } + if (other.z < z) { + return 1; + } + if (other.z > z) { + return -1; + } + return 0; + } + } +} diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/lighting/NullRelighter.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/lighting/NullRelighter.java new file mode 100644 index 000000000..45de35d31 --- /dev/null +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/lighting/NullRelighter.java @@ -0,0 +1,49 @@ +package com.boydti.fawe.beta.implementation.lighting; + +public class NullRelighter implements Relighter { + + public static NullRelighter INSTANCE = new NullRelighter(); + + private NullRelighter() { + } + + @Override + public boolean addChunk(int cx, int cz, byte[] fix, int bitmask) { + return false; + } + + @Override + public void addLightUpdate(int x, int y, int z) { + + } + + @Override + public void fixLightingSafe(boolean sky) { + + } + + @Override + public void clear() { + + } + + @Override + public void removeLighting() { + + } + + @Override + public void fixBlockLighting() { + + } + + @Override + public void fixSkyLighting() { + + } + + @Override + public boolean isEmpty() { + return true; + } +} diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/lighting/Relighter.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/lighting/Relighter.java new file mode 100644 index 000000000..966d944b2 --- /dev/null +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/lighting/Relighter.java @@ -0,0 +1,74 @@ +package com.boydti.fawe.beta.implementation.lighting; + +public interface Relighter { + + /** + * Add a chunk to be relit when {@link Relighter#removeLighting} etc are called + * + * @param cx chunk x + * @param cz chunk z + * @param skipReason byte array of {@link SkipReason} for each chunksection in the chunk. Use case? No idea. + * @param bitmask Initial bitmask of the chunk (if being editited beforehand) + * @return Was the chunk added + */ + boolean addChunk(int cx, int cz, byte[] skipReason, int bitmask); + + /** + * Add a block to be relit + * + * @param x block x + * @param y block y + * @param z block z + */ + void addLightUpdate(int x, int y, int z); + + /** + * Safely? Fix block lighting + * + * @param sky whether to also relight sky light values + */ + void fixLightingSafe(boolean sky); + + /** + * Remove lighting and then relight safely + * + * @param sky whether to also relight sky light values + */ + default void removeAndRelight(boolean sky) { + removeLighting(); + fixLightingSafe(sky); + } + + /** + * Clear all chunks and blocks to be relit + */ + void clear(); + + /** + * Remove all block and sky light values (set to 0 light) in all chunks added to relighter + */ + void removeLighting(); + + /** + * Fix block light values in all chunks added to relighter + */ + void fixBlockLighting(); + + /** + * Fix sky light values in all chunks added to relighter + */ + void fixSkyLighting(); + + /** + * Are there any block or chunk added to be relit + * + * @return is the relight stuff to be relit empty + */ + boolean isEmpty(); + + public static class SkipReason { + public static final byte NONE = 0; + public static final byte AIR = 1; + public static final byte SOLID = 2; + } +} diff --git a/worldedit-core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAChunk.java b/worldedit-core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAChunk.java index e8717012e..57f16950a 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAChunk.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAChunk.java @@ -436,6 +436,24 @@ public class MCAChunk implements IChunk { return true; } + @Override public void setBlockLight(int x, int y, int z, int value) { + + } + + @Override public void setSkyLight(int x, int y, int z, int value) { + + } + + @Override public void removeSectionLighting(int layer, boolean sky) {} + + @Override public void setFullBright(int layer) {} + + @Override + public void setLightLayer(int layer, char[] toSet) {} + + @Override + public void setSkyLightLayer(int layer, char[] toSet) {} + @Override public void setEntity(CompoundTag entityTag) { setModified(); @@ -454,6 +472,14 @@ public class MCAChunk implements IChunk { return this.biomes; } + @Override public char[][] getLight() { + return new char[0][]; + } + + @Override public char[][] getSkyLight() { + return new char[0][]; + } + @Override public boolean setBiome(BlockVector2 pos, BiomeType biome) { return this.setBiome(pos.getX(), 0, pos.getZ(), biome); @@ -499,6 +525,15 @@ public class MCAChunk implements IChunk { return BlockState.getFromOrdinal(ordinal); } + //TODO implement lighting + @Override public int getSkyLight(int x, int y, int z) { + return 0; + } + + @Override public int getEmmittedLight(int x, int y, int z) { + return 0; + } + @Override public BaseBlock getFullBlock(int x, int y, int z) { BlockState block = getBlock(x, y, z); diff --git a/worldedit-core/src/main/java/com/boydti/fawe/object/RelightMode.java b/worldedit-core/src/main/java/com/boydti/fawe/object/RelightMode.java new file mode 100644 index 000000000..23b84c15e --- /dev/null +++ b/worldedit-core/src/main/java/com/boydti/fawe/object/RelightMode.java @@ -0,0 +1,7 @@ +package com.boydti.fawe.object; + +public enum RelightMode { + NONE, // no relighting + OPTIMAL, // relight changed light sources and changed blocks + ALL // relight every single block +} diff --git a/worldedit-core/src/main/java/com/boydti/fawe/util/EditSessionBuilder.java b/worldedit-core/src/main/java/com/boydti/fawe/util/EditSessionBuilder.java index d2a84c00b..1cc771d9d 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/util/EditSessionBuilder.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/util/EditSessionBuilder.java @@ -1,27 +1,26 @@ package com.boydti.fawe.util; -import static com.google.common.base.Preconditions.checkNotNull; - import com.boydti.fawe.Fawe; -import com.boydti.fawe.FaweAPI; import com.boydti.fawe.FaweCache; import com.boydti.fawe.beta.IBatchProcessor; import com.boydti.fawe.beta.IQueueChunk; import com.boydti.fawe.beta.IQueueExtent; +import com.boydti.fawe.beta.implementation.lighting.NMSRelighter; +import com.boydti.fawe.beta.implementation.lighting.NullRelighter; +import com.boydti.fawe.beta.implementation.lighting.Relighter; import com.boydti.fawe.beta.implementation.processors.LimitProcessor; import com.boydti.fawe.beta.implementation.queue.ParallelQueueExtent; -import com.sk89q.worldedit.util.Identifiable; -import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; import com.boydti.fawe.config.Settings; import com.boydti.fawe.logging.rollback.RollbackOptimizedHistory; import com.boydti.fawe.object.FaweLimit; import com.boydti.fawe.object.HistoryExtent; import com.boydti.fawe.object.NullChangeSet; import com.boydti.fawe.object.RegionWrapper; +import com.boydti.fawe.object.RelightMode; import com.boydti.fawe.object.brush.visualization.VirtualWorld; +import com.boydti.fawe.object.changeset.AbstractChangeSet; import com.boydti.fawe.object.changeset.BlockBagChangeSet; import com.boydti.fawe.object.changeset.DiskStorageHistory; -import com.boydti.fawe.object.changeset.AbstractChangeSet; import com.boydti.fawe.object.changeset.MemoryOptimizedHistory; import com.boydti.fawe.object.extent.FaweRegionExtent; import com.boydti.fawe.object.extent.MultiRegionExtent; @@ -37,12 +36,17 @@ import com.sk89q.worldedit.event.extent.EditSessionEvent; import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.extent.inventory.BlockBag; import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.util.Identifiable; import com.sk89q.worldedit.util.eventbus.EventBus; +import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; import com.sk89q.worldedit.world.World; -import java.util.UUID; -import javax.annotation.Nullable; import org.jetbrains.annotations.NotNull; +import javax.annotation.Nullable; +import java.util.UUID; + +import static com.google.common.base.Preconditions.checkNotNull; + public class EditSessionBuilder { @NotNull private World world; @@ -60,6 +64,7 @@ public class EditSessionBuilder { private boolean threaded = true; private EditSessionEvent event; private String command; + private RelightMode relightMode; /** * An EditSession builder
@@ -80,7 +85,7 @@ public class EditSessionBuilder { checkNotNull(world); this.world = world; } - + public EditSessionBuilder player(@Nullable Player player) { this.player = player; return setDirty(); @@ -172,6 +177,11 @@ public class EditSessionBuilder { return setDirty(); } + public EditSessionBuilder relightMode(@Nullable RelightMode relightMode) { + this.relightMode = relightMode; + return setDirty(); + } + public EditSessionBuilder checkMemory(@Nullable Boolean checkMemory) { this.checkMemory = checkMemory; return setDirty(); @@ -402,7 +412,7 @@ public class EditSessionBuilder { public World getWorld() { return world; } - + public Extent getExtent() { return extent != null ? extent : world; } @@ -410,7 +420,7 @@ public class EditSessionBuilder { public boolean isWrapped() { return wrapped; } - + public Extent getBypassHistory() { return bypassHistory; } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java index 3939babe2..9640adcc1 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java @@ -27,9 +27,11 @@ import static com.sk89q.worldedit.regions.Regions.asFlatRegion; import static com.sk89q.worldedit.regions.Regions.maximumBlockY; import static com.sk89q.worldedit.regions.Regions.minimumBlockY; +import com.boydti.fawe.FaweAPI; import com.boydti.fawe.FaweCache; import com.boydti.fawe.config.Caption; import com.boydti.fawe.object.FaweLimit; +import com.boydti.fawe.object.RelightMode; import com.sk89q.jnbt.CompoundTag; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.LocalSession; @@ -139,16 +141,15 @@ public class RegionCommands { ) @CommandPermissions("worldedit.light.fix") public void fixLighting(Player player) throws WorldEditException { - player.print(TextComponent.of("Temporarily not working")); -// final Location loc = player.getLocation(); -// Region selection = player.getSelection(); -// if (selection == null) { -// final int cx = loc.getBlockX() >> 4; -// final int cz = loc.getBlockZ() >> 4; -// selection = new CuboidRegion(BlockVector3.at(cx - 8, 0, cz - 8).multiply(16), BlockVector3.at(cx + 8, 0, cz + 8).multiply(16)); -// } -// int count = FaweAPI.fixLighting(player.getWorld(), selection,null); -// player.print(Caption.of("fawe.info.lighting.propagate.selection" , count)); + final Location loc = player.getLocation(); + Region selection = player.getSelection(); + if (selection == null) { + final int cx = loc.getBlockX() >> 4; + final int cz = loc.getBlockZ() >> 4; + selection = new CuboidRegion(BlockVector3.at(cx - 8, 0, cz - 8).multiply(16), BlockVector3.at(cx + 8, 0, cz + 8).multiply(16)); + } + int count = FaweAPI.fixLighting(player.getWorld(), selection,null, RelightMode.ALL); + player.print(Caption.of("fawe.info.lighting.propagate.selection" , count)); } // @Command( @@ -163,22 +164,21 @@ public class RegionCommands { // player.print(TextComponent.of("Light: " + block + " | " + sky)); // } -// @Command( -// name = "/removelighting", -// desc = "Removing lighting in a selection" -// ) -// @CommandPermissions("worldedit.light.remove") -// public void removeLighting(Player player) { -// player.print(TextComponent.of("Temporarily not working")); -// Region selection = player.getSelection(); -// if (selection == null) { -// final int cx = player.getLocation().getBlockX() >> 4; -// final int cz = player.getLocation().getBlockZ() >> 4; -// selection = new CuboidRegion(BlockVector3.at(cx - 8, 0, cz - 8).multiply(16), BlockVector3.at(cx + 8, 0, cz + 8).multiply(16)); -// } -// int count = FaweAPI.fixLighting(player.getWorld(), selection, null); -// player.print(Caption.of("fawe.info.updated.lighting.selection" , count)); -// } + @Command( + name = "/removelighting", + desc = "Removing lighting in a selection" + ) + @CommandPermissions("worldedit.light.remove") + public void removeLighting(Player player) { + Region selection = player.getSelection(); + if (selection == null) { + final int cx = player.getLocation().getBlockX() >> 4; + final int cz = player.getLocation().getBlockZ() >> 4; + selection = new CuboidRegion(BlockVector3.at(cx - 8, 0, cz - 8).multiply(16), BlockVector3.at(cx + 8, 0, cz + 8).multiply(16)); + } + int count = FaweAPI.fixLighting(player.getWorld(), selection, null, RelightMode.NONE); + player.print(Caption.of("fawe.info.updated.lighting.selection" , count)); + } @Command( name = "/nbtinfo", diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/AbstractDelegateExtent.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/AbstractDelegateExtent.java index 7e590be46..66acf6fb0 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/AbstractDelegateExtent.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/AbstractDelegateExtent.java @@ -205,6 +205,46 @@ public class AbstractDelegateExtent implements Extent { public boolean setBiome(BlockVector2 position, BiomeType biome) { return extent.setBiome(position.getX(), 0, position.getZ(), biome); } + + @Override + public boolean relight(int x, int y, int z) { + return extent.relight(x, y, z); + } + + @Override + public boolean relightBlock(int x, int y, int z) { + return extent.relightBlock(x, y, z); + } + + @Override + public boolean relightSky(int x, int y, int z) { + return extent.relightSky(x, y, z); + } + + @Override + public void setSkyLight(int x, int y, int z, int value) { + extent.setSkyLight(x, y, z, value); + } + + @Override + public void setBlockLight(int x, int y, int z, int value) { + extent.setSkyLight(x, y, z, value); + } + + @Override + public int getSkyLight(int x, int y, int z) { + return extent.getSkyLight(x, y, z); + } + + @Override + public int getEmmittedLight(int x, int y, int z) { + return extent.getEmmittedLight(x, y, z); + } + + @Override + public int getBrightness(int x, int y, int z) { + return extent.getBrightness(x, y, z); + } @Override public String toString() { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/Extent.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/Extent.java index 04c3e2953..b9c20c8f0 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/Extent.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/Extent.java @@ -651,6 +651,18 @@ public interface Extent extends InputExtent, OutputExtent { return count; } + default boolean relight(int x, int y, int z) { + return false; + } + + default boolean relightBlock(int x, int y, int z) { + return false; + } + + default boolean relightSky(int x, int y, int z) { + return false; + } + /** * Have an extent processed * - Either block (Extent) processing or chunk processing diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/InputExtent.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/InputExtent.java index 4d264c4e2..134e29f95 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/InputExtent.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/InputExtent.java @@ -85,4 +85,48 @@ public interface InputExtent { default BiomeType getBiomeType(int x, int y, int z) { return getBiome(MutableBlockVector2.get(x, z)); } + + /** + * Get the light level at the given location + * + * @param position location + * @return the light level at the location + */ + default int getEmmittedLight(MutableBlockVector3 position) { + return getEmmittedLight(position.getX(), position.getY(), position.getZ()); + } + + default int getEmmittedLight(int x, int y, int z) { + return 0; + } + + /** + * Get the sky light level at the given location + * + * @param position location + * @return the sky light level at the location + */ + default int getSkyLight(MutableBlockVector3 position) { + return getSkyLight(position.getX(), position.getY(), position.getZ()); + } + + default int getSkyLight(int x, int y, int z) { + return 0; + } + + default int getBrightness(MutableBlockVector3 position) { + return getBrightness(position.getX(), position.getY(), position.getZ()); + } + + default int getBrightness(int x, int y, int z) { + return getFullBlock(x, y, z).getMaterial().getLightValue(); + } + + default int getOpacity(MutableBlockVector3 position) { + return getOpacity(position.getX(), position.getY(), position.getZ()); + } + + default int getOpacity(int x, int y, int z) { + return getFullBlock(x, y, z).getMaterial().getLightOpacity(); + } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/OutputExtent.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/OutputExtent.java index 794ddefb2..04c275c4c 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/OutputExtent.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/OutputExtent.java @@ -82,6 +82,30 @@ public interface OutputExtent { return setBiome(MutableBlockVector2.get(x, z), biome); } + /** + * Set the light value + * + * @param position position of the block + * @param value light level to set + */ + default void setBlockLight(BlockVector3 position, int value) { + setBlockLight(position.getX(), position.getY(), position.getZ(), value); + } + + default void setBlockLight(int x, int y, int z, int value) {} + + /** + * Set the sky light value + * + * @param position position of the block + * @param value light level to set + */ + default void setSkyLight(BlockVector3 position, int value) { + setSkyLight(position.getX(), position.getY(), position.getZ(), value); + } + + default void setSkyLight(int x, int y, int z, int value) {} + /** * Return an {@link Operation} that should be called to tie up loose ends * (such as to commit changes in a buffer).