Plex-FAWE/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/lighting/NMSRelighter.java

1261 lines
56 KiB
Java

package com.boydti.fawe.beta.implementation.lighting;
import com.boydti.fawe.Fawe;
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.RelightMode;
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.internal.util.LogManagerCompat;
import com.sk89q.worldedit.math.MutableBlockVector3;
import com.sk89q.worldedit.registry.state.BooleanProperty;
import com.sk89q.worldedit.registry.state.DirectionalProperty;
import com.sk89q.worldedit.registry.state.EnumProperty;
import com.sk89q.worldedit.registry.state.Property;
import com.sk89q.worldedit.util.Direction;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockTypes;
import com.sk89q.worldedit.world.registry.BlockMaterial;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import org.apache.logging.log4j.Logger;
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.Locale;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
public class NMSRelighter implements Relighter {
private static final Logger LOGGER = LogManagerCompat.getLogger();
private static final int DISPATCH_SIZE = 64;
private static final DirectionalProperty stairDirection;
private static final EnumProperty stairHalf;
private static final EnumProperty stairShape;
private static final EnumProperty slabHalf;
private static final BooleanProperty waterLogged;
static {
stairDirection = (DirectionalProperty) (Property<?>) BlockTypes.SANDSTONE_STAIRS.getProperty("facing");
stairHalf = (EnumProperty) (Property<?>) BlockTypes.SANDSTONE_STAIRS.getProperty("half");
stairShape = (EnumProperty) (Property<?>) BlockTypes.SANDSTONE_STAIRS.getProperty("shape");
slabHalf = (EnumProperty) (Property<?>) BlockTypes.SANDSTONE_SLAB.getProperty("type");
waterLogged = (BooleanProperty) (Property<?>) BlockTypes.SANDSTONE_SLAB.getProperty("waterlogged");
}
public final MutableBlockVector3 mutableBlockPos = new MutableBlockVector3(0, 0, 0);
private final IQueueExtent<IQueueChunk> queue;
private final Map<Long, RelightSkyEntry> skyToRelight;
private final Object present = new Object();
private final Map<Long, Integer> chunksToSend;
private final Map<Long, Map<HeightMapType, int[]>> heightMaps;
private final ConcurrentLinkedQueue<RelightSkyEntry> extentdSkyToRelight = new ConcurrentLinkedQueue<>();
private final Map<Long, long[][][] /* z y x */> lightQueue;
private final AtomicBoolean lightLock = new AtomicBoolean(false);
private final ConcurrentHashMap<Long, long[][][]> concurrentLightQueue;
private final RelightMode relightMode;
private final int maxY;
private final boolean calculateHeightMaps;
private final ReentrantLock lightingLock;
private final AtomicBoolean finished = new AtomicBoolean(false);
private boolean removeFirst;
public NMSRelighter(IQueueExtent<IQueueChunk> queue, boolean calculateHeightMaps) {
this(queue, calculateHeightMaps, null);
}
public NMSRelighter(IQueueExtent<IQueueChunk> queue, boolean calculateHeightMaps, RelightMode relightMode) {
this.queue = queue;
this.skyToRelight = new Long2ObjectOpenHashMap<>(12);
this.lightQueue = new Long2ObjectOpenHashMap<>(12);
this.chunksToSend = new Long2ObjectOpenHashMap<>(12);
this.concurrentLightQueue = new ConcurrentHashMap<>(12);
this.heightMaps = new Long2ObjectOpenHashMap<>(12);
this.maxY = queue.getMaxY();
this.calculateHeightMaps = calculateHeightMaps;
this.relightMode = relightMode != null ? relightMode : RelightMode.valueOf(Settings.IMP.LIGHTING.MODE);
this.lightingLock = new ReentrantLock();
}
@Override public boolean isEmpty() {
return skyToRelight.isEmpty() && lightQueue.isEmpty() && extentdSkyToRelight.isEmpty() && concurrentLightQueue.isEmpty();
}
@Override
public ReentrantLock getLock() {
return lightingLock;
}
@Override
public boolean isFinished() {
return finished.get();
}
@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];
}
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();
heightMaps.clear();
lightQueue.clear();
}
public boolean addChunk(int cx, int cz, byte[] fix, int bitmask) {
RelightSkyEntry toPut = new RelightSkyEntry(cx, cz, fix, bitmask, calculateHeightMaps);
extentdSkyToRelight.add(toPut);
return true;
}
private synchronized Map<Long, RelightSkyEntry> 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<Map.Entry<Long, RelightSkyEntry>> iter = getSkyMap().entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<Long, RelightSkyEntry> 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<Long, long[][][]> map) {
int size = map.size();
if (size == 0) {
return;
}
Queue<MutableBlockVector3> lightPropagationQueue = new ArrayDeque<>(32);
Queue<Object[]> lightRemovalQueue = new ArrayDeque<>(32);
Map<MutableBlockVector3, Object> visited = new HashMap<>(32);
Map<MutableBlockVector3, Object> removalVisited = new HashMap<>(32);
// Make sure BlockTypes is initialised so we can check block characteristics later if needed
BlockTypes.STONE.getMaterial();
Iterator<Map.Entry<Long, long[][][]>> iter = map.entrySet().iterator();
while (iter.hasNext() && size-- > 0) {
Map.Entry<Long, long[][][]> 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);
BlockState state = this.queue.getBlock(node.getX(), node.getY(), node.getZ());
String id = state.getBlockType().getId().toLowerCase(Locale.ROOT);
if (lightLevel <= 1) {
continue;
}
if (id.contains("slab")) {
boolean top = state.getState(slabHalf).equalsIgnoreCase("top");
computeSlab(node.getX(), node.getY(), node.getZ(), lightLevel, lightPropagationQueue, visited, top);
} else if (id.contains("stair")) {
boolean top = state.getState(stairHalf).equalsIgnoreCase("top");
Direction direction = getStairDir(state);
String shape = getStairShape(state);
computeStair(node.getX(), node.getY(), node.getZ(), lightLevel, lightPropagationQueue, visited, top, direction, shape);
} else {
computeNormal(node.getX(), node.getY(), node.getZ(), lightLevel, lightPropagationQueue, visited);
}
}
}
private void computeStair(int x,
int y,
int z,
int currentLight,
Queue<MutableBlockVector3> queue,
Map<MutableBlockVector3, Object> visited,
boolean top,
Direction direction,
String shape) {
east:
{
// Block East
if (direction != Direction.WEST && !((direction == Direction.NORTH && !shape.equals("inner_left")) || (direction == Direction.SOUTH
&& !shape.equals("inner_right")) || (direction == Direction.EAST && shape.contains("outer")))) {
break east;
}
BlockState state = this.queue.getBlock(x + 1, y, z);
if (!(checkStairEast(state) && isStairOrTrueTop(state, top) && isSlabOrTrueValue(state, top ? "top" : "bottom"))) {
break east;
}
if (!state.getBlockType().getId().toLowerCase(Locale.ROOT).contains("stair")) {
this.computeSpreadBlockLight(x + 1, y, z, currentLight, queue, visited);
break east;
}
Direction otherDir = getStairDir(state);
String otherShape = getStairShape(state);
boolean b1 =
(otherDir == Direction.NORTH && !otherShape.equals("outer_right")) || (otherDir == Direction.EAST && otherShape.equals("inner_left"));
boolean b2 =
(otherDir == Direction.SOUTH && !otherShape.equals("outer_left")) || (otherDir == Direction.EAST && otherShape.equals("inner_right"));
switch (direction) {
case EAST:
if (shape.equals("outer_right") && b1) {
break east;
} else if (shape.equals("outer_left") && b2) {
break east;
}
break;
case WEST:
if (shape.equals("straight") || shape.contains("outer")) {
break;
} else if (shape.equals("inner_left") && b1) {
break east;
} else if (shape.equals("inner_right") && b2) {
break east;
}
break;
case SOUTH:
if (shape.equals("inner_left") || b1 || (otherDir == Direction.SOUTH && otherShape.equals("inner_right"))) {
break east;
}
break;
case NORTH:
if (shape.equals("inner_right") || b2 || (otherDir == Direction.NORTH && otherShape.equals("inner_left"))) {
break east;
}
break;
}
this.computeSpreadBlockLight(x + 1, y, z, currentLight, queue, visited);
}
west:
{
// Block West
if (direction != Direction.EAST && !((direction == Direction.SOUTH && !shape.equals("inner_left")) || (direction == Direction.NORTH
&& !shape.equals("inner_right")) || (direction == Direction.WEST && shape.contains("outer")))) {
break west;
}
BlockState state = this.queue.getBlock(x - 1, y, z);
if (!(checkStairWest(state) && isStairOrTrueTop(state, top) && isSlabOrTrueValue(state, top ? "top" : "bottom"))) {
break west;
}
if (!state.getBlockType().getId().toLowerCase(Locale.ROOT).contains("stair")) {
this.computeSpreadBlockLight(x - 1, y, z, currentLight, queue, visited);
break west;
}
Direction otherDir = getStairDir(state);
String otherShape = getStairShape(state);
boolean b1 =
(otherDir == Direction.SOUTH && !otherShape.equals("outer_right")) || (otherDir == Direction.WEST && otherShape.equals("inner_left"));
boolean b2 =
(otherDir == Direction.NORTH && !otherShape.equals("outer_left")) || (otherDir == Direction.WEST && otherShape.equals("inner_right"));
switch (direction) {
case WEST:
if (shape.equals("outer_right") && b1) {
break west;
} else if (shape.equals("outer_left") && b2) {
break west;
}
break;
case EAST:
if (shape.equals("straight") || shape.contains("outer")) {
break;
} else if (shape.equals("inner_left") && b1) {
break west;
} else if (shape.equals("inner_right") && b2) {
break west;
}
break;
case NORTH:
if (shape.equals("inner_left") || b1 || (otherDir == Direction.NORTH && otherShape.equals("inner_right"))) {
break west;
}
break;
case SOUTH:
if (shape.equals("inner_right") || b2 || (otherDir == Direction.SOUTH && otherShape.equals("inner_left"))) {
break west;
}
break;
}
this.computeSpreadBlockLight(x - 1, y, z, currentLight, queue, visited);
}
south:
{
// Block South
if (direction != Direction.NORTH && !((direction == Direction.WEST && !shape.equals("inner_left")) || (direction == Direction.EAST
&& !shape.equals("inner_right")) || (direction == Direction.SOUTH && shape.contains("outer")))) {
break south;
}
BlockState state = this.queue.getBlock(x, y, z + 1);
if (!(checkStairSouth(state) && isStairOrTrueTop(state, top) && isSlabOrTrueValue(state, top ? "top" : "bottom"))) {
break south;
}
if (!state.getBlockType().getId().toLowerCase(Locale.ROOT).contains("stair")) {
this.computeSpreadBlockLight(x, y, z + 1, currentLight, queue, visited);
break south;
}
Direction otherDir = getStairDir(state);
String otherShape = getStairShape(state);
boolean b1 =
(otherDir == Direction.EAST && !otherShape.equals("outer_right")) || (otherDir == Direction.SOUTH && otherShape.equals("inner_left"));
boolean b2 =
(otherDir == Direction.WEST && !otherShape.equals("outer_left")) || (otherDir == Direction.SOUTH && otherShape.equals("inner_right"));
switch (direction) {
case SOUTH:
if (shape.equals("outer_right") && b1) {
break south;
} else if (shape.equals("outer_left") && b2) {
break south;
}
break;
case NORTH:
if (shape.equals("straight") || shape.contains("outer")) {
break;
} else if (shape.equals("inner_left") && b1) {
break south;
} else if (shape.equals("inner_right") && b2) {
break south;
}
break;
case WEST:
if (shape.equals("inner_left") || b1 || (otherDir == Direction.WEST && otherShape.equals("inner_right"))) {
break south;
}
break;
case EAST:
if (shape.equals("inner_right") || b2 || (otherDir == Direction.EAST && otherShape.equals("inner_left"))) {
break south;
}
break;
}
this.computeSpreadBlockLight(x, y, z + 1, currentLight, queue, visited);
}
north:
{
// Block North
if (direction != Direction.SOUTH && !((direction == Direction.EAST && !shape.equals("inner_left")) || (direction == Direction.WEST
&& !shape.equals("inner_right")) || (direction == Direction.NORTH && shape.contains("outer")))) {
break north;
}
BlockState state = this.queue.getBlock(x, y, z - 1);
if (!(checkStairNorth(state) && isStairOrTrueTop(state, top) && isSlabOrTrueValue(state, top ? "top" : "bottom"))) {
break north;
}
if (!state.getBlockType().getId().toLowerCase(Locale.ROOT).contains("stair")) {
this.computeSpreadBlockLight(x, y, z - 1, currentLight, queue, visited);
break north;
}
Direction otherDir = getStairDir(state);
String otherShape = getStairShape(state);
boolean b1 =
(otherDir == Direction.WEST && !otherShape.equals("outer_right")) || (otherDir == Direction.NORTH && otherShape.equals("inner_left"));
boolean b2 =
(otherDir == Direction.EAST && !otherShape.equals("outer_left")) || (otherDir == Direction.NORTH && otherShape.equals("inner_right"));
switch (direction) {
case NORTH:
if (shape.equals("outer_right") && b1) {
break north;
} else if (shape.equals("outer_left") && b2) {
break north;
}
break;
case SOUTH:
if (shape.equals("straight") || shape.contains("outer")) {
break;
} else if (shape.equals("inner_left") && b1) {
break north;
} else if (shape.equals("inner_right") && b2) {
break north;
}
break;
case EAST:
if (shape.equals("inner_left") || b1 || (otherDir == Direction.EAST && otherShape.equals("inner_right"))) {
break north;
}
break;
case WEST:
if (shape.equals("inner_right") || b2 || (otherDir == Direction.WEST && otherShape.equals("inner_left"))) {
break north;
}
break;
}
this.computeSpreadBlockLight(x, y, z - 1, currentLight, queue, visited);
}
computeUpDown(x, y, z, currentLight, queue, visited, top);
}
private void computeSlab(int x,
int y,
int z,
int currentLight,
Queue<MutableBlockVector3> queue,
Map<MutableBlockVector3, Object> visited,
boolean top) {
{
// Block East
BlockState state = this.queue.getBlock(x + 1, y, z);
if (checkStairEast(state) && isStairOrTrueTop(state, top) && isSlabOrTrueValue(state, top ? "top" : "bottom")) {
this.computeSpreadBlockLight(x + 1, y, z, currentLight, queue, visited);
}
}
{
// Block West
BlockState state = this.queue.getBlock(x - 1, y, z);
if (checkStairWest(state) && isStairOrTrueTop(state, top) && isSlabOrTrueValue(state, top ? "top" : "bottom")) {
this.computeSpreadBlockLight(x - 1, y, z, currentLight, queue, visited);
}
}
{
// Block South
BlockState state = this.queue.getBlock(x, y, z + 1);
if (checkStairSouth(state) && isStairOrTrueTop(state, top) && isSlabOrTrueValue(state, top ? "top" : "bottom")) {
this.computeSpreadBlockLight(x, y, z + 1, currentLight, queue, visited);
}
}
{
// Block North
BlockState state = this.queue.getBlock(x, y, z - 1);
if (checkStairNorth(state) && isStairOrTrueTop(state, top) && isSlabOrTrueValue(state, top ? "top" : "bottom")) {
this.computeSpreadBlockLight(x, y, z - 1, currentLight, queue, visited);
}
}
computeUpDown(x, y, z, currentLight, queue, visited, top);
}
private void computeUpDown(int x,
int y,
int z,
int currentLight,
Queue<MutableBlockVector3> queue,
Map<MutableBlockVector3, Object> visited,
boolean top) {
BlockState state = this.queue.getBlock(x, y - 1, z);
if (y > 0 && top && isSlabOrTrueValue(state, "bottom") && isStairOrTrueTop(state, false)) {
this.computeSpreadBlockLight(x, y - 1, z, currentLight, queue, visited);
}
state = this.queue.getBlock(x, y + 1, z);
if (y < 255 && !top && isSlabOrTrueValue(state, "top") && isStairOrTrueTop(state, true)) {
this.computeSpreadBlockLight(x, y + 1, z, currentLight, queue, visited);
}
}
private void computeNormal(int x, int y, int z, int currentLight, Queue<MutableBlockVector3> queue, Map<MutableBlockVector3, Object> visited) {
{
// Block East
BlockState state = this.queue.getBlock(x + 1, y, z);
if (checkStairEast(state) && (isSlabOrTrueValue(state, "top") || isSlabOrTrueValue(state, "bottom"))) {
this.computeSpreadBlockLight(x + 1, y, z, currentLight, queue, visited);
}
}
{
// Block West
BlockState state = this.queue.getBlock(x - 1, y, z);
if (checkStairWest(state) && (isSlabOrTrueValue(state, "top") || isSlabOrTrueValue(state, "bottom"))) {
this.computeSpreadBlockLight(x - 1, y, z, currentLight, queue, visited);
}
}
{
// Block South
BlockState state = this.queue.getBlock(x, y, z + 1);
if (checkStairSouth(state) && (isSlabOrTrueValue(state, "top") || isSlabOrTrueValue(state, "bottom"))) {
this.computeSpreadBlockLight(x, y, z + 1, currentLight, queue, visited);
}
}
{
// Block North
BlockState state = this.queue.getBlock(x, y, z - 1);
if (checkStairNorth(state) && (isSlabOrTrueValue(state, "top") || isSlabOrTrueValue(state, "bottom"))) {
this.computeSpreadBlockLight(x, y, z - 1, currentLight, queue, visited);
}
}
BlockState state = this.queue.getBlock(x, y - 1, z);
if (y > 0 && isSlabOrTrueValue(state, "bottom") && isStairOrTrueTop(state, false)) {
this.computeSpreadBlockLight(x, y - 1, z, currentLight, queue, visited);
}
state = this.queue.getBlock(x, y + 1, z);
if (y < 255 && isSlabOrTrueValue(state, "top") && isStairOrTrueTop(state, false)) {
this.computeSpreadBlockLight(x, y + 1, z, currentLight, queue, visited);
}
}
private boolean checkStairNorth(BlockState state) {
if (!state.getBlockType().getId().toLowerCase(Locale.ROOT).contains("stair")) {
return true;
}
Direction direction = getStairDir(state);
String shape = getStairShape(state);
if (shape.contains("outer") || direction == Direction.NORTH) {
return true;
}
if (direction == Direction.SOUTH) {
return false;
}
if (direction == Direction.WEST) {
return !shape.equals("inner_left");
}
return direction != Direction.EAST || !shape.equals("inner_right");
}
private boolean checkStairSouth(BlockState state) {
if (!state.getBlockType().getId().toLowerCase(Locale.ROOT).contains("stair")) {
return true;
}
Direction direction = getStairDir(state);
String shape = getStairShape(state);
if (shape.contains("outer") || direction == Direction.SOUTH) {
return true;
}
if (direction == Direction.NORTH) {
return false;
}
if (direction == Direction.EAST) {
return !shape.equals("inner_left");
}
return direction != Direction.WEST || !shape.equals("inner_right");
}
private boolean checkStairEast(BlockState state) {
if (!state.getBlockType().getId().toLowerCase(Locale.ROOT).contains("stair")) {
return true;
}
Direction direction = getStairDir(state);
String shape = getStairShape(state);
if (shape.contains("outer") || direction == Direction.EAST) {
return true;
}
if (direction == Direction.WEST) {
return false;
}
if (direction == Direction.NORTH) {
return !shape.equals("inner_left");
}
return direction != Direction.SOUTH || !shape.equals("inner_right");
}
private boolean checkStairWest(BlockState state) {
if (!state.getBlockType().getId().toLowerCase(Locale.ROOT).contains("stair")) {
return true;
}
Direction direction = getStairDir(state);
String shape = getStairShape(state);
if (shape.contains("outer") || direction == Direction.WEST) {
return true;
}
if (direction == Direction.EAST) {
return false;
}
if (direction == Direction.SOUTH) {
return !shape.equals("inner_left");
}
return direction != Direction.NORTH || !shape.equals("inner_right");
}
private Direction getStairDir(BlockState state) {
return state.getState(stairDirection);
}
private String getStairShape(BlockState state) {
return state.getState(stairShape).toLowerCase(Locale.ROOT);
}
private boolean isStairOrTrueTop(BlockState state, boolean top) {
return !state.getBlockType().getId().contains("stair") || state.getState(stairHalf).equals("top") == top;
}
private boolean isSlabOrTrueValue(BlockState state, String value) {
return !state.getBlockType().getId().contains("slab") || state.getState(slabHalf).equals(value);
}
private void computeRemoveBlockLight(int x,
int y,
int z,
int currentLight,
Queue<Object[]> queue,
Queue<MutableBlockVector3> spreadQueue,
Map<MutableBlockVector3, Object> visited,
Map<MutableBlockVector3, Object> 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<MutableBlockVector3> queue,
Map<MutableBlockVector3, Object> visited) {
BlockMaterial material = this.queue.getBlock(x, y, z).getMaterial();
boolean solidNeedsLight = (!material.isSolid() || !material.isFullCube()) && material.getLightOpacity() > 0 && material.getLightValue() == 0;
currentLight = !solidNeedsLight ? currentLight - Math.max(1, material.getLightOpacity()) : currentLight - 1;
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 (currentLight > current) {
iChunk.setBlockLight(x & 15, y, z & 15, 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<Long, RelightSkyEntry> map = getSkyMap();
Iterator<Map.Entry<Long, RelightSkyEntry>> iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<Long, RelightSkyEntry> entry = iter.next();
chunksToSend.put(entry.getKey(), entry.getValue().bitmask);
iter.remove();
}
}
}
fixBlockLighting();
sendChunks();
} catch (Throwable e) {
e.printStackTrace();
}
}
public void fixBlockLighting() {
synchronized (lightQueue) {
while (!lightLock.compareAndSet(false, true)) {
try {
lightLock.wait(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
updateBlockLight(this.lightQueue);
} finally {
lightLock.set(false);
}
}
}
public synchronized void flush() {
Iterator<Map.Entry<Long, Integer>> iter = chunksToSend.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<Long, Integer> 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);
if (calculateHeightMaps && heightMaps != null) {
Map<HeightMapType, int[]> heightMapList = heightMaps.get(pair);
if (heightMapList != null) {
for (Map.Entry<HeightMapType, int[]> heightMapEntry : heightMapList.entrySet()) {
chunk.setHeightMap(heightMapEntry.getKey(), heightMapEntry.getValue());
}
}
}
iter.remove();
}
if (Settings.IMP.LIGHTING.ASYNC) {
queue.flush();
finished.set(true);
} else {
TaskManager.IMP.sync(new RunnableVal<Object>() {
@Override public void run(Object value) {
queue.flush();
finished.set(true);
}
});
}
}
public synchronized void sendChunks() {
RunnableVal<Object> runnable = new RunnableVal<Object>() {
@Override
public void run(Object value) {
Iterator<Map.Entry<Long, Integer>> iter = chunksToSend.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<Long, Integer> 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);
if (calculateHeightMaps && heightMaps != null) {
Map<HeightMapType, int[]> heightMapList = heightMaps.get(pair);
if (heightMapList != null) {
for (Map.Entry<HeightMapType, int[]> heightMapEntry : heightMapList.entrySet()) {
chunk.setHeightMap(heightMapEntry.getKey(), heightMapEntry.getValue());
}
}
}
chunk.flushLightToGet(true);
Fawe.imp().getPlatformAdapter().sendChunk(chunk.getOrCreateGet(), bitMask, true);
iter.remove();
}
finished.set(true);
}
};
if (Settings.IMP.LIGHTING.ASYNC) {
runnable.run();
} else {
TaskManager.IMP.sync(runnable);
}
}
public synchronized void fixSkyLighting() {
// Order chunks
Map<Long, RelightSkyEntry> map = getSkyMap();
ArrayList<RelightSkyEntry> chunksList = new ArrayList<>(map.size());
Iterator<Map.Entry<Long, RelightSkyEntry>> iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<Long, RelightSkyEntry> 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<RelightSkyEntry> 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<RelightSkyEntry> sorted) {
RelightSkyEntry[] chunks = sorted.toArray(new RelightSkyEntry[sorted.size()]);
boolean remove = this.removeFirst;
boolean heightMaps = this.calculateHeightMaps;
BlockVectorSet chunkSet = null;
if (remove || heightMaps) {
BlockVectorSet tmpSet = new BlockVectorSet();
if (remove) {
chunkSet = new BlockVectorSet();
for (RelightSkyEntry chunk : chunks) {
tmpSet.add(chunk.x, 0, chunk.z);
}
}
for (RelightSkyEntry chunk : chunks) {
if (remove) {
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);
}
}
if (heightMaps) {
long pair = MathMan.pairInt(chunk.x, chunk.z);
this.heightMaps.putIfAbsent(pair, new HashMap<>());
Map<HeightMapType, int[]> heightMapList = this.heightMaps.get(pair);
heightMapList.putIfAbsent(HeightMapType.WORLD_SURFACE, new int[256]);
heightMapList.putIfAbsent(HeightMapType.OCEAN_FLOOR, new int[256]);
heightMapList.putIfAbsent(HeightMapType.MOTION_BLOCKING, new int[256]);
heightMapList.putIfAbsent(HeightMapType.MOTION_BLOCKING_NO_LEAVES, new int[256]);
}
}
}
for (int y = 255; y > 0; y--) {
for (RelightSkyEntry chunk : chunks) { // Propagate skylight
int layer = y >> 4;
byte[] mask = chunk.mask;
if (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);
}
Map<HeightMapType, int[]> heightMapList = null;
if (heightMaps) {
long pair = MathMan.pairInt(chunk.x, chunk.z);
heightMapList = this.heightMaps.get(pair);
}
for (int j = 0; j < 256; j++) {
int x = j & 15;
int z = j >> 4;
byte value = mask[j];
BlockState state = iChunk.getBlock(x, y, z);
BlockMaterial material = state.getMaterial();
int opacity = material.getLightOpacity();
int brightness = material.getLightValue();
if (brightness > 0 && brightness != iChunk.getEmmittedLight(x, y, z)) {
addLightUpdate(bx + x, y, bz + z);
}
if (heightMaps) {
if (heightMapList.get(HeightMapType.WORLD_SURFACE)[j] == 0 && !material.isAir()) {
// MC Requires y+1
heightMapList.get(HeightMapType.WORLD_SURFACE)[j] = y + 1;
}
if (heightMapList.get(HeightMapType.OCEAN_FLOOR)[j] == 0 && material.isSolid()) {
heightMapList.get(HeightMapType.OCEAN_FLOOR)[j] = y + 1;
}
Map<Property<?>, Object> states = state.getStates();
try {
if (heightMapList.get(HeightMapType.MOTION_BLOCKING)[j] == 0 && (material.isSolid() || material.isLiquid() || (
states.containsKey(waterLogged) && state.getState(waterLogged)))) {
heightMapList.get(HeightMapType.MOTION_BLOCKING)[j] = y + 1;
}
} catch (Exception ignored) {
LOGGER.debug("Error calculating waterlogged state for BlockState: " + state.getBlockType().getId() + ". States:");
LOGGER.debug(states.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.joining(", ", "{", "}")));
}
try {
if (heightMapList.get(HeightMapType.MOTION_BLOCKING_NO_LEAVES)[j] == 0 && (material.isSolid() || material.isLiquid() || (
states.containsKey(waterLogged) && state.getState(waterLogged))) && !state.getBlockType().getId()
.toLowerCase().contains("leaves")) {
heightMapList.get(HeightMapType.MOTION_BLOCKING_NO_LEAVES)[j] = y + 1;
}
} catch (Exception ignored) {
LOGGER.debug("Error calculating waterlogged state for BlockState: " + state.getBlockType().getId() + ". States:");
LOGGER.debug(states.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.joining(", ", "{", "}")));
}
}
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;
if (!isStairOrTrueTop(state, true) || !(isSlabOrTrueValue(state, "top") || isSlabOrTrueValue(state, "double"))) {
iChunk.setSkyLight(x, y, z, value);
} else {
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 > 0) {
value -= opacity;
mask[j] = value;
}
if (!isStairOrTrueTop(state, true) || !(isSlabOrTrueValue(state, "top") || isSlabOrTrueValue(state, "double"))) {
iChunk.setSkyLight(x, y, z, value + opacity);
} else {
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<RelightSkyEntry> {
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, boolean heightmaps) {
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(RelightSkyEntry o) {
if (o.x < x) {
return 1;
}
if (o.x > x) {
return -1;
}
if (o.z < z) {
return 1;
}
if (o.z > z) {
return -1;
}
return 0;
}
}
}