mirror of
https://github.com/wisplite/crystalline-sky.git
synced 2026-10-30 13:32:18 -05:00
functional weeping sky
This commit is contained in:
+35
-6
@@ -26,15 +26,44 @@ c fae net/minecraft/class_3572 net/minecraft/world/chunk/light/ChunkSkyLightProv
|
|||||||
f J h field_44744 REMOVE_SKY_SOURCE_ENTRY
|
f J h field_44744 REMOVE_SKY_SOURCE_ENTRY
|
||||||
f J i field_44745 ADD_SKY_SOURCE_ENTRY
|
f J i field_44745 ADD_SKY_SOURCE_ENTRY
|
||||||
f Ljb$a; j field_44746 scratchPos
|
f Ljb$a; j field_44746 scratchPos
|
||||||
m (IIII)V a method_51586 removeSourcesBelow
|
|
||||||
m (JLjh;IZI)V a method_51587 propagateFromEmptySections
|
m (JLjh;IZI)V a method_51587 propagateFromEmptySections
|
||||||
p 0 blockPos
|
p 1 blockPos
|
||||||
p 1 direction
|
p 3 direction
|
||||||
p 2 lightLevel
|
p 4 lightLevel
|
||||||
p 3 shouldIncrease
|
p 5 shouldIncrease
|
||||||
p 4 emptySections
|
p 6 emptySections
|
||||||
m (III)V b method_51590 updateSourcesInColumn
|
m (III)V b method_51590 updateSourcesInColumn
|
||||||
|
p 1 x
|
||||||
|
p 2 z
|
||||||
|
p 3 lowestSourceY
|
||||||
|
v 4 15 -1 _ _ minY
|
||||||
|
m (IIII)V a method_51586 removeSourcesBelow
|
||||||
|
p 1 x
|
||||||
|
p 2 z
|
||||||
|
p 3 lowestSourceY
|
||||||
|
p 4 minY
|
||||||
|
v 5 13 -1 _ _ sectionX
|
||||||
|
v 6 19 -1 _ _ sectionZ
|
||||||
|
v 7 24 -1 _ _ topSolid
|
||||||
|
v 8 31 -1 _ _ sectionY
|
||||||
|
v 9 75 -1 _ _ minYInSection
|
||||||
|
v 10 82 -1 _ _ maxYInSection
|
||||||
|
v 11 91 -1 _ _ it_y
|
||||||
|
v 12 107 -1 _ _ it_pos
|
||||||
m (IIII)V b method_51591 addSourcesAbove
|
m (IIII)V b method_51591 addSourcesAbove
|
||||||
|
p 1 x
|
||||||
|
p 2 z
|
||||||
|
p 3 lowestSourceY
|
||||||
|
p 4 minY
|
||||||
|
v 5 6 -1 _ _ sectionX
|
||||||
|
v 6 12 -1 _ _ sectionZ
|
||||||
|
v 7 63 -1 _ _ maxAdjacentLowestSourceY
|
||||||
|
v 8 71 -1 _ _ lowestRelevantY
|
||||||
|
v 9 85 -1 _ _ it_packedSection
|
||||||
|
v 11 125 -1 _ _ minYInSection
|
||||||
|
v 12 132 -1 _ _ maxYInSection
|
||||||
|
v 13 141 -1 _ _ it_y
|
||||||
|
v 14 157 -1 _ _ it_pos
|
||||||
m (J)V a method_51529 checkNode
|
m (J)V a method_51529 checkNode
|
||||||
v 3 5 -1 _ _ x
|
v 3 5 -1 _ _ x
|
||||||
v 4 11 -1 _ _ y
|
v 4 11 -1 _ _ y
|
||||||
|
|||||||
@@ -1,14 +1,47 @@
|
|||||||
package io.github.slimeistdev.crystalline_sky;
|
package io.github.slimeistdev.crystalline_sky;
|
||||||
|
|
||||||
import io.github.slimeistdev.crystalline_sky.extenders_cove.BlockRenderLayerExt;
|
import io.github.slimeistdev.crystalline_sky.extenders_cove.BlockRenderLayerExt;
|
||||||
|
import io.github.slimeistdev.crystalline_sky.mixin_ducks.client.DebugRenderer_Duck;
|
||||||
import io.github.slimeistdev.crystalline_sky.registry.CrystallineBlocks;
|
import io.github.slimeistdev.crystalline_sky.registry.CrystallineBlocks;
|
||||||
import net.fabricmc.api.ClientModInitializer;
|
import net.fabricmc.api.ClientModInitializer;
|
||||||
|
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||||
|
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
|
||||||
import net.fabricmc.fabric.api.client.rendering.v1.BlockRenderLayerMap;
|
import net.fabricmc.fabric.api.client.rendering.v1.BlockRenderLayerMap;
|
||||||
|
import net.minecraft.client.option.KeyBinding;
|
||||||
|
import net.minecraft.client.util.InputUtil;
|
||||||
|
import net.minecraft.screen.ScreenTexts;
|
||||||
|
import net.minecraft.text.Text;
|
||||||
|
import net.minecraft.util.Formatting;
|
||||||
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
public class CrystallineSkyClient implements ClientModInitializer {
|
public class CrystallineSkyClient implements ClientModInitializer {
|
||||||
@Override
|
@Override
|
||||||
public void onInitializeClient() {
|
public void onInitializeClient() {
|
||||||
BlockRenderLayerMap.putBlock(CrystallineBlocks.SKY, BlockRenderLayerExt.CRYSTALLINE_SKY_SKY);
|
BlockRenderLayerMap.putBlock(CrystallineBlocks.SKY, BlockRenderLayerExt.CRYSTALLINE_SKY_SKY);
|
||||||
BlockRenderLayerMap.putBlock(CrystallineBlocks.WEEPING_SKY, BlockRenderLayerExt.CRYSTALLINE_SKY_SKY);
|
BlockRenderLayerMap.putBlock(CrystallineBlocks.WEEPING_SKY, BlockRenderLayerExt.CRYSTALLINE_SKY_SKY);
|
||||||
|
|
||||||
|
var weepingSkyKeybind = KeyBindingHelper.registerKeyBinding(new KeyBinding(
|
||||||
|
"crystalline_sky.key.toggle_weeping_sky_debug",
|
||||||
|
InputUtil.Type.KEYSYM,
|
||||||
|
GLFW.GLFW_KEY_UNKNOWN,
|
||||||
|
"crystalline_sky.category.crystalline_sky"
|
||||||
|
));
|
||||||
|
|
||||||
|
ClientTickEvents.END_CLIENT_TICK.register(client -> {
|
||||||
|
while (weepingSkyKeybind.wasPressed()) {
|
||||||
|
int mode = ((DebugRenderer_Duck) client.debugRenderer).crystalline_sky$toggleWeepingSky();
|
||||||
|
|
||||||
|
Text message = getDebugMessage(Formatting.YELLOW,
|
||||||
|
Text.translatable("message.crystalline_sky.weeping_sky_debug." + mode)
|
||||||
|
);
|
||||||
|
|
||||||
|
client.inGameHud.getChatHud().addMessage(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("SameParameterValue")
|
||||||
|
private static Text getDebugMessage(Formatting formatting, Text message) {
|
||||||
|
return Text.empty().append(Text.translatable("debug.prefix").formatted(formatting, Formatting.BOLD)).append(ScreenTexts.SPACE).append(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
package io.github.slimeistdev.crystalline_sky;
|
package io.github.slimeistdev.crystalline_sky;
|
||||||
|
|
||||||
import io.github.slimeistdev.crystalline_sky.registry.CrystallineBlocks;
|
import io.github.slimeistdev.crystalline_sky.registry.CrystallineBlocks;
|
||||||
|
import io.github.slimeistdev.crystalline_sky.registry.CrystallineDataComponentTypes;
|
||||||
import io.github.slimeistdev.crystalline_sky.registry.CrystallineItems;
|
import io.github.slimeistdev.crystalline_sky.registry.CrystallineItems;
|
||||||
|
|
||||||
public class ModSetup {
|
public class ModSetup {
|
||||||
public static void init() {
|
public static void init() {
|
||||||
|
CrystallineDataComponentTypes.init();
|
||||||
CrystallineBlocks.init();
|
CrystallineBlocks.init();
|
||||||
CrystallineItems.init();
|
CrystallineItems.init();
|
||||||
}
|
}
|
||||||
|
|||||||
+753
@@ -0,0 +1,753 @@
|
|||||||
|
package io.github.slimeistdev.crystalline_sky.infrastructure;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import io.github.slimeistdev.crystalline_sky.util.ArrayUtils;
|
||||||
|
import it.unimi.dsi.fastutil.shorts.ShortList;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public class IntWeepingStorage implements WeepingStorage {
|
||||||
|
private final int[] sizes;
|
||||||
|
|
||||||
|
// each long is (y_sky << 32 | y_bottom)
|
||||||
|
// y_sky is the position of the highest weeping sky block in a layer
|
||||||
|
// y_bottom is the lowest illuminated position (so 1 above a solid block) (less than y_sky) in a layer, or -1 if the layer is unbounded
|
||||||
|
// must be sorted top-to-bottom by y_sky
|
||||||
|
private final int[][] columns;
|
||||||
|
|
||||||
|
private final int[] tempRunSplitSizes;
|
||||||
|
private final int[][] tempRunSplits;
|
||||||
|
|
||||||
|
// only used for iterators
|
||||||
|
private final int minY;
|
||||||
|
|
||||||
|
public IntWeepingStorage(int minY) {
|
||||||
|
this.sizes = new int[16*16];
|
||||||
|
this.columns = new int[16*16][0];
|
||||||
|
|
||||||
|
this.tempRunSplitSizes = new int[16*16*2];
|
||||||
|
this.tempRunSplits = new int[16*16*2][0];
|
||||||
|
|
||||||
|
this.minY = minY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getIndex(int localX, int localZ) {
|
||||||
|
return localX + localZ * 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int pack(short ySky, short yBottom) {
|
||||||
|
return ((int)ySky << 16) | Short.toUnsignedInt(yBottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static short unpackSky(int packed) {
|
||||||
|
return (short)(packed >>> 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static short unpackBottom(int packed) {
|
||||||
|
return (short)(packed & 0xFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int calculateIncreasedLength(int currentLength) {
|
||||||
|
if (currentLength == 0)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
// calculate next power of two
|
||||||
|
return Integer.highestOneBit(currentLength) << 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeShrinkColumn(int index) {
|
||||||
|
int[] column = columns[index];
|
||||||
|
int size = sizes[index];
|
||||||
|
|
||||||
|
int newLength = calculateIncreasedLength(size);
|
||||||
|
if (column.length > newLength) {
|
||||||
|
int[] newColumn = new int[newLength];
|
||||||
|
System.arraycopy(column, 0, newColumn, 0, size);
|
||||||
|
columns[index] = newColumn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insertValue(int columnIndex, int i, int packed) {
|
||||||
|
int[] column = columns[columnIndex];
|
||||||
|
int size = sizes[columnIndex];
|
||||||
|
|
||||||
|
if (size + 1 > column.length) {
|
||||||
|
int newLength = calculateIncreasedLength(column.length);
|
||||||
|
int[] newColumn = new int[newLength];
|
||||||
|
|
||||||
|
System.arraycopy(column, 0, newColumn, 0, i);
|
||||||
|
newColumn[i] = packed;
|
||||||
|
System.arraycopy(column, i, newColumn, i + 1, size - i);
|
||||||
|
|
||||||
|
columns[columnIndex] = newColumn;
|
||||||
|
} else {
|
||||||
|
System.arraycopy(column, i, column, i + 1, size - i);
|
||||||
|
column[i] = packed;
|
||||||
|
}
|
||||||
|
sizes[columnIndex] = ++size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
unlit sections are iterated downward
|
||||||
|
lit sections are iterated upward
|
||||||
|
|
||||||
|
a newly-unlit section should call insertTempRunSplit at its topY
|
||||||
|
a newly-lit section should call insertTempRunSplit at its bottomY
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will split a run in subsequent iterators
|
||||||
|
* @param columnIndex column to split
|
||||||
|
* @param splitY the new (isLit ? bottomY : topY) of a run that intersects this one
|
||||||
|
* @param isLit whether this should be added to the splits for lit or unlit runs
|
||||||
|
*/
|
||||||
|
private void insertTempRunSplit(int columnIndex, int splitY, boolean isLit) {
|
||||||
|
int index = columnIndex * 2 + (isLit ? 1 : 0);
|
||||||
|
|
||||||
|
int size = tempRunSplitSizes[index];
|
||||||
|
int[] splits = tempRunSplits[index];
|
||||||
|
|
||||||
|
if (size == 0) {
|
||||||
|
if (splits.length == 0) {
|
||||||
|
tempRunSplits[index] = new int[] {splitY};
|
||||||
|
} else {
|
||||||
|
splits[0] = splitY;
|
||||||
|
}
|
||||||
|
tempRunSplitSizes[index] = ++size;
|
||||||
|
} else {
|
||||||
|
// binary search for insertion point
|
||||||
|
int insertIndex = ArrayUtils.binarySearchSortedInsertionPoint(splits, splitY, size);
|
||||||
|
if (insertIndex != -1) {
|
||||||
|
if (size + 1 > splits.length) {
|
||||||
|
int newLength = calculateIncreasedLength(splits.length);
|
||||||
|
int[] newSplits = new int[newLength];
|
||||||
|
|
||||||
|
System.arraycopy(splits, 0, newSplits, 0, insertIndex);
|
||||||
|
newSplits[insertIndex] = splitY;
|
||||||
|
System.arraycopy(splits, insertIndex, newSplits, insertIndex + 1, splits.length - insertIndex);
|
||||||
|
|
||||||
|
tempRunSplits[index] = newSplits;
|
||||||
|
} else {
|
||||||
|
System.arraycopy(splits, insertIndex, splits, insertIndex + 1, size - insertIndex);
|
||||||
|
splits[insertIndex] = splitY;
|
||||||
|
}
|
||||||
|
tempRunSplitSizes[index] = ++size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void litTempSplit(int columnIndex, int bottomY) {
|
||||||
|
insertTempRunSplit(columnIndex, bottomY, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unlitTempSplit(int columnIndex, int topY) {
|
||||||
|
insertTempRunSplit(columnIndex, topY, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void insertSky(int localX, int y, int localZ, WeepingScanner scanner) {
|
||||||
|
int index = getIndex(localX, localZ);
|
||||||
|
int size = sizes[index];
|
||||||
|
int[] column = columns[index];
|
||||||
|
short yShort = (short) y;
|
||||||
|
|
||||||
|
if (size == 0) {
|
||||||
|
int solidY = scanner.scan(localX, y, localZ);
|
||||||
|
short shortLowestLitY = (short) (solidY == -1 ? -1 : solidY + 1);
|
||||||
|
int packed = pack(yShort, shortLowestLitY);
|
||||||
|
if (column.length == 0) {
|
||||||
|
columns[index] = new int[] {packed};
|
||||||
|
} else {
|
||||||
|
column[0] = packed;
|
||||||
|
}
|
||||||
|
sizes[index] = ++size;
|
||||||
|
litTempSplit(index, shortLowestLitY);
|
||||||
|
} else {
|
||||||
|
// possible cases:
|
||||||
|
// 1. we are in a layer, and do nothing
|
||||||
|
// 2. we are below the lowest layer, and create a new layer
|
||||||
|
// 3. we are above a layer, and must scan to determine whether to join it or insert one above it
|
||||||
|
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
int packed = column[i];
|
||||||
|
short sky = unpackSky(packed);
|
||||||
|
short bottom = unpackBottom(packed);
|
||||||
|
|
||||||
|
if (yShort == sky)
|
||||||
|
return; // no change
|
||||||
|
|
||||||
|
if (bottom != -1 && yShort < sky && (yShort == bottom || yShort == bottom - 1)) {
|
||||||
|
// scan if layer can continue now, and extend it down if so. Else, fall through to in-or-below handling
|
||||||
|
if (!scanner.faceBlocksLight(localX, bottom, localZ)) {
|
||||||
|
int solidY = scanner.scan(localX, yShort, localZ);
|
||||||
|
short shortLowestLitY = (short) (solidY == -1 ? -1 : solidY + 1);
|
||||||
|
|
||||||
|
if (i + 1 < size) { // maybe join layer below
|
||||||
|
int packedBelow = column[i + 1];
|
||||||
|
short skyBelow = unpackSky(packedBelow);
|
||||||
|
short bottomBelow = unpackBottom(packedBelow);
|
||||||
|
|
||||||
|
if (solidY == -1 || solidY > skyBelow) { // join layer[i] and layer[i + 1]
|
||||||
|
litTempSplit(index, skyBelow + 1); // the newly-lit region is [skyBelow + 1, bottom - 1]
|
||||||
|
|
||||||
|
column[i] = pack(sky, bottomBelow);
|
||||||
|
System.arraycopy(column, i + 2, column, i + 1, size - (i + 2));
|
||||||
|
sizes[index] = --size;
|
||||||
|
|
||||||
|
maybeShrinkColumn(index);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// extend layer[i] down
|
||||||
|
column[i] = pack(sky, shortLowestLitY);
|
||||||
|
litTempSplit(index, shortLowestLitY);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yShort > sky) { // above layer
|
||||||
|
int solidY = scanner.scan(localX, y, localZ);
|
||||||
|
if (solidY == -1 || solidY < sky) { // join layer
|
||||||
|
column[i] = pack(yShort, bottom);
|
||||||
|
litTempSplit(index, sky + 1); // the newly-lit region is [sky + 1, yShort]
|
||||||
|
} else { // insert new layer above (at index i)
|
||||||
|
insertValue(index, i, pack(yShort, (short) (solidY + 1)));
|
||||||
|
litTempSplit(index, solidY + 1);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else { // (yShort < sky) in or below
|
||||||
|
if (bottom == -1 || yShort >= bottom) { // in layer, do nothing
|
||||||
|
return;
|
||||||
|
} else if (i == size - 1) { // below layer, add if last layer
|
||||||
|
int solidY = scanner.scan(localX, y, localZ);
|
||||||
|
short shortLowestLitY = (short) (solidY == -1 ? -1 : solidY + 1);
|
||||||
|
|
||||||
|
insertValue(index, size, pack(yShort, shortLowestLitY));
|
||||||
|
litTempSplit(index, shortLowestLitY);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void insertSolid(int localX, int y, int localZ, WeepingScanner scanner) {
|
||||||
|
int index = getIndex(localX, localZ);
|
||||||
|
int size = sizes[index];
|
||||||
|
int[] column = columns[index];
|
||||||
|
short yShort = (short) y;
|
||||||
|
|
||||||
|
// if we're inside a layer, limit it
|
||||||
|
|
||||||
|
boolean lightBlockedHere;
|
||||||
|
boolean lightBlockedAbove;
|
||||||
|
int lastLitY = -1;
|
||||||
|
boolean blockingCalculated = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
int packed = column[i];
|
||||||
|
short sky = unpackSky(packed);
|
||||||
|
short bottom = unpackBottom(packed);
|
||||||
|
|
||||||
|
if (yShort > sky) { // we're above all remaining layers, so nothing to do
|
||||||
|
return;
|
||||||
|
} else if (yShort == sky) { // either remove layer or lower the start (scan for sky)
|
||||||
|
int nextSky = scanner.scanForWeepingSky(localX, y, localZ);
|
||||||
|
|
||||||
|
if (nextSky == -1 || nextSky < bottom) { // remove layer
|
||||||
|
System.arraycopy(column, i + 1, column, i, size - (i + 1));
|
||||||
|
sizes[index] = --size;
|
||||||
|
maybeShrinkColumn(index);
|
||||||
|
} else { // lower start of layer
|
||||||
|
column[i] = pack((short) nextSky, bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
unlitTempSplit(index, sky);
|
||||||
|
return;
|
||||||
|
} else { // (yShort < sky) limit layer and potentially split layer
|
||||||
|
if (!blockingCalculated) {
|
||||||
|
lightBlockedHere = scanner.faceBlocksLight(localX, y, localZ);
|
||||||
|
lightBlockedAbove = scanner.faceBlocksLight(localX, y + 1, localZ);
|
||||||
|
|
||||||
|
if (lightBlockedAbove) {
|
||||||
|
lastLitY = y + 1;
|
||||||
|
} else if (lightBlockedHere) {
|
||||||
|
lastLitY = y;
|
||||||
|
} else { // no effect
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockingCalculated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastLitY <= bottom) // no effect
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int nextSky = scanner.scanForWeepingSky(localX, y, localZ);
|
||||||
|
|
||||||
|
// limit
|
||||||
|
column[i] = pack(sky, (short) lastLitY);
|
||||||
|
|
||||||
|
unlitTempSplit(index, lastLitY - 1);
|
||||||
|
|
||||||
|
if (nextSky != -1 && nextSky >= bottom) { // split
|
||||||
|
insertValue(index, i + 1, pack((short) nextSky, bottom));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void insertAir(int localX, int y, int localZ, WeepingScanner scanner) {
|
||||||
|
int index = getIndex(localX, localZ);
|
||||||
|
int size = sizes[index];
|
||||||
|
int[] column = columns[index];
|
||||||
|
short yShort = (short) y;
|
||||||
|
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
int packed = column[i];
|
||||||
|
short sky = unpackSky(packed);
|
||||||
|
short bottom = unpackBottom(packed);
|
||||||
|
|
||||||
|
if (yShort > sky) { // above all remaining layers, nothing to do
|
||||||
|
return;
|
||||||
|
} else if (yShort == sky) { // rescan for sky
|
||||||
|
int nextSky = scanner.scanForWeepingSky(localX, y, localZ);
|
||||||
|
|
||||||
|
if (nextSky == -1 || nextSky < bottom) { // remove layer
|
||||||
|
System.arraycopy(column, i + 1, column, i, size - (i + 1));
|
||||||
|
sizes[index] = --size;
|
||||||
|
maybeShrinkColumn(index);
|
||||||
|
|
||||||
|
unlitTempSplit(index, sky);
|
||||||
|
} else { // lower start of layer
|
||||||
|
column[i] = pack((short) nextSky, bottom);
|
||||||
|
|
||||||
|
unlitTempSplit(index, sky);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else { // (yShort < sky) maybe extend layer down
|
||||||
|
if (bottom != -1 && (yShort == bottom || yShort == bottom - 1)) { // we may have freed things up
|
||||||
|
if (!scanner.faceBlocksLight(localX, bottom, localZ)) { // extend layer down or join with layer below
|
||||||
|
int solidY = scanner.scan(localX, yShort, localZ);
|
||||||
|
short shortLowestLitY = (short) (solidY == -1 ? -1 : solidY + 1);
|
||||||
|
|
||||||
|
if (i + 1 < size) { // maybe join layer below
|
||||||
|
int packedBelow = column[i + 1];
|
||||||
|
short skyBelow = unpackSky(packedBelow);
|
||||||
|
short bottomBelow = unpackBottom(packedBelow);
|
||||||
|
|
||||||
|
if (solidY == -1 || solidY < skyBelow) { // join layer[i] and layer[i + 1]
|
||||||
|
column[i] = pack(sky, bottomBelow);
|
||||||
|
System.arraycopy(column, i + 2, column, i + 1, size - (i + 2));
|
||||||
|
sizes[index] = --size;
|
||||||
|
|
||||||
|
litTempSplit(index, skyBelow + 1);
|
||||||
|
|
||||||
|
maybeShrinkColumn(index);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// extend layer[i] down
|
||||||
|
column[i] = pack(sky, shortLowestLitY);
|
||||||
|
litTempSplit(index, shortLowestLitY);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
int[] empty = new int[0];
|
||||||
|
for (int i = 0; i < 16*16; i++) {
|
||||||
|
sizes[i] = 0;
|
||||||
|
columns[i] = empty;
|
||||||
|
|
||||||
|
tempRunSplitSizes[i] = 0;
|
||||||
|
tempRunSplits[i] = empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(int localX, int localZ, ShortList layers) {
|
||||||
|
Preconditions.checkArgument(layers.size() % 2 == 0, "Expected even number of shorts in layers");
|
||||||
|
|
||||||
|
int index = getIndex(localX, localZ);
|
||||||
|
int size = layers.size() / 2;
|
||||||
|
sizes[index] = size;
|
||||||
|
int[] column = columns[index];
|
||||||
|
|
||||||
|
if (column.length < size) {
|
||||||
|
column = new int[calculateIncreasedLength(size)];
|
||||||
|
columns[index] = column;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
short sky = layers.getShort(i * 2);
|
||||||
|
short bottom = layers.getShort(i * 2 + 1);
|
||||||
|
column[i] = pack(sky, bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
maybeShrinkColumn(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLit(int localX, int y, int localZ) {
|
||||||
|
int index = getIndex(localX, localZ);
|
||||||
|
int size = sizes[index];
|
||||||
|
int[] column = columns[index];
|
||||||
|
|
||||||
|
if (size == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
short yShort = (short) (y - minY);
|
||||||
|
|
||||||
|
// binary search
|
||||||
|
int left = 0;
|
||||||
|
int right = size - 1;
|
||||||
|
|
||||||
|
while (left <= right) {
|
||||||
|
int mid = (left + right) / 2;
|
||||||
|
int packed = column[mid];
|
||||||
|
|
||||||
|
short sky = unpackSky(packed);
|
||||||
|
short bottom = unpackBottom(packed);
|
||||||
|
|
||||||
|
if (yShort > sky) { // we're higher
|
||||||
|
right = mid - 1;
|
||||||
|
} else if (yShort < sky) { // we're lower
|
||||||
|
if (bottom == -1 || yShort >= bottom) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
left = mid + 1;
|
||||||
|
}
|
||||||
|
} else { // yShort == sky
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOTE: unlike all other methods in this class, this uses global y coordinates.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Iterable<Run> iterateLitRuns(int localX, int localZ, int lowestSourceY) {
|
||||||
|
int index = getIndex(localX, localZ);
|
||||||
|
int size = sizes[index];
|
||||||
|
int[] column = columns[index];
|
||||||
|
|
||||||
|
int splitsColumnIndex = index * 2 + 1;
|
||||||
|
int splitsSize = tempRunSplitSizes[splitsColumnIndex];
|
||||||
|
int[] splits = tempRunSplits[splitsColumnIndex];
|
||||||
|
|
||||||
|
List<Run> runs = new ArrayList<>(size);
|
||||||
|
runs.add(new Run(lowestSourceY, Integer.MAX_VALUE));
|
||||||
|
|
||||||
|
if (lowestSourceY == Integer.MIN_VALUE) {
|
||||||
|
return wrapUnlitRuns(splitsSize, splits, runs);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
int packed = column[i];
|
||||||
|
short sky = unpackSky(packed);
|
||||||
|
short bottom = unpackBottom(packed);
|
||||||
|
|
||||||
|
int globalSky = sky + minY;
|
||||||
|
int globalBottom = bottom == -1 ? Integer.MIN_VALUE : bottom + minY;
|
||||||
|
|
||||||
|
// if we're above lowestSourceY, don't bother
|
||||||
|
|
||||||
|
if (globalBottom >= lowestSourceY)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// if we intersect with default sky, merge
|
||||||
|
if (globalSky >= lowestSourceY - 1) {
|
||||||
|
runs.set(0, new Run(globalBottom, Integer.MAX_VALUE));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise, add a new run
|
||||||
|
runs.add(new Run(globalBottom, globalSky));
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapLitRuns(splitsSize, splits, runs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOTE: unlike all other methods in this class, this uses global y coordinates.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Iterable<Run> iterateUnlitRuns(int localX, int localZ, int lowestSourceY) {
|
||||||
|
int index = getIndex(localX, localZ);
|
||||||
|
int size = sizes[index];
|
||||||
|
int[] column = columns[index];
|
||||||
|
|
||||||
|
int splitsColumnIndex = index * 2;
|
||||||
|
int splitsSize = tempRunSplitSizes[splitsColumnIndex];
|
||||||
|
int[] splits = tempRunSplits[splitsColumnIndex];
|
||||||
|
|
||||||
|
// fill with all gaps between layers, which are below lowestSourceY
|
||||||
|
List<Run> runs = new ArrayList<>(size);
|
||||||
|
|
||||||
|
if (lowestSourceY == Integer.MIN_VALUE) {
|
||||||
|
return wrapUnlitRuns(splitsSize, splits, runs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size == 0) {
|
||||||
|
runs.add(new Run(Integer.MIN_VALUE, lowestSourceY - 1));
|
||||||
|
} else {
|
||||||
|
int lastLowestLit = lowestSourceY;
|
||||||
|
|
||||||
|
// each iteration adds the gap above layer[i]
|
||||||
|
for (int i = 0; i < size && lastLowestLit > Integer.MIN_VALUE; i++) {
|
||||||
|
int packed = column[i];
|
||||||
|
short sky = unpackSky(packed);
|
||||||
|
short bottom = unpackBottom(packed);
|
||||||
|
|
||||||
|
int globalSky = sky + minY;
|
||||||
|
int globalBottom = bottom == -1 ? Integer.MIN_VALUE : bottom + minY;
|
||||||
|
|
||||||
|
if (globalBottom >= lowestSourceY) // completely lit, what's the point?
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (globalSky < lastLowestLit - 1) // gap
|
||||||
|
runs.add(new Run(globalSky + 1, lastLowestLit - 1));
|
||||||
|
|
||||||
|
lastLowestLit = globalBottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the gap below the lowest layer
|
||||||
|
if (lastLowestLit > Integer.MIN_VALUE && lastLowestLit != lowestSourceY) {
|
||||||
|
runs.add(new Run(Integer.MIN_VALUE, lastLowestLit - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapUnlitRuns(splitsSize, splits, runs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearTempRunSplits(int localX, int localZ) {
|
||||||
|
int[] empty = new int[0];
|
||||||
|
|
||||||
|
int baseIndex = getIndex(localX, localZ) * 2;
|
||||||
|
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
int index = baseIndex + i;
|
||||||
|
|
||||||
|
tempRunSplitSizes[index] = 0;
|
||||||
|
tempRunSplits[index] = empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isColumnEmpty(int localX, int localZ) {
|
||||||
|
return sizes[getIndex(localX, localZ)] <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNoUnlitSplits(int localX, int localZ) {
|
||||||
|
return tempRunSplitSizes[getIndex(localX, localZ) * 2] <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void debugState(int localX, int localZ, Consumer<String> stringConsumer) {
|
||||||
|
int index = getIndex(localX, localZ);
|
||||||
|
int size = sizes[index];
|
||||||
|
int[] column = columns[index];
|
||||||
|
|
||||||
|
if (size == 0) {
|
||||||
|
stringConsumer.accept(" <empty>");
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
int packed = column[i];
|
||||||
|
short sky = unpackSky(packed);
|
||||||
|
short bottom = unpackBottom(packed);
|
||||||
|
|
||||||
|
stringConsumer.accept(" layer " + i + ": sky=" + (sky + minY) + ", bottom=" + (bottom == -1 ? "-1" : (bottom + minY)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** wrapped iterable must be sorted top-to-bottom, splits must be sorted bottom-to-top */
|
||||||
|
public Iterable<Run> wrapLitRuns(int splitsSize, int[] splits, Iterable<Run> iterable) {
|
||||||
|
if (splitsSize <= 0) return iterable;
|
||||||
|
return () -> new LitSplittingIterator(splitsSize, splits, iterable.iterator());
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LitSplittingIterator implements Iterator<Run> {
|
||||||
|
private final int[] splits;
|
||||||
|
private final Iterator<Run> iterator;
|
||||||
|
|
||||||
|
private final Deque<Run> remainingSplits = new ArrayDeque<>();
|
||||||
|
|
||||||
|
private int currentSplitIndex;
|
||||||
|
|
||||||
|
private LitSplittingIterator(int splitsSize, int[] splits, Iterator<Run> iterator) {
|
||||||
|
this.splits = splits;
|
||||||
|
this.iterator = iterator;
|
||||||
|
|
||||||
|
this.currentSplitIndex = splitsSize - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return !remainingSplits.isEmpty() || iterator.hasNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Run next() {
|
||||||
|
if (!remainingSplits.isEmpty()) {
|
||||||
|
return remainingSplits.removeFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
Run run = iterator.next();
|
||||||
|
while (currentSplitIndex >= 0) {
|
||||||
|
int splitY = splits[currentSplitIndex] + minY; // becomes the new bottomY of a run
|
||||||
|
if (splitY < run.bottomY()) { // split is below run, not relevant (yet)
|
||||||
|
return run;
|
||||||
|
} else if (splitY > run.topY()) { // split is above run, will never be relevant
|
||||||
|
currentSplitIndex--;
|
||||||
|
} else if (splitY == run.bottomY()) { // run already fulfills the split
|
||||||
|
currentSplitIndex--;
|
||||||
|
return run;
|
||||||
|
} else { // split is inside run, split it
|
||||||
|
Run upper = new Run(splitY, run.topY());
|
||||||
|
|
||||||
|
run = new Run(run.bottomY(), splitY - 1);
|
||||||
|
remainingSplits.addFirst(upper);
|
||||||
|
currentSplitIndex--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return run;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** wrapped iterable must be sorted top-to-bottom, splits must be sorted bottom-to-top */
|
||||||
|
private Iterable<Run> wrapUnlitRuns(int splitsSize, int[] splits, Iterable<Run> iterable) {
|
||||||
|
if (splitsSize <= 0) return iterable;
|
||||||
|
return () -> new UnlitSplittingIterator(splitsSize, splits, iterable.iterator());
|
||||||
|
}
|
||||||
|
|
||||||
|
private class UnlitSplittingIterator implements Iterator<Run> {
|
||||||
|
private final int[] splits;
|
||||||
|
private final Iterator<Run> iterator;
|
||||||
|
|
||||||
|
private final Deque<Run> remainingSplits = new ArrayDeque<>();
|
||||||
|
|
||||||
|
private int currentSplitIndex;
|
||||||
|
|
||||||
|
private UnlitSplittingIterator(int splitsSize, int[] splits, Iterator<Run> iterator) {
|
||||||
|
this.splits = splits;
|
||||||
|
this.iterator = iterator;
|
||||||
|
|
||||||
|
this.currentSplitIndex = splitsSize - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return !remainingSplits.isEmpty() || iterator.hasNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Run next() {
|
||||||
|
if (!remainingSplits.isEmpty()) {
|
||||||
|
return remainingSplits.removeFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
Run run = iterator.next();
|
||||||
|
while (currentSplitIndex >= 0) {
|
||||||
|
int splitY = splits[currentSplitIndex] + minY; // becomes the new topY of a run
|
||||||
|
if (splitY < run.bottomY()) { // split is below run, not relevant (yet)
|
||||||
|
return run;
|
||||||
|
} else if (splitY > run.topY()) { // split is above run, will never be relevant
|
||||||
|
currentSplitIndex--;
|
||||||
|
return run;
|
||||||
|
} else if (splitY == run.topY()) { // run already fulfills the split
|
||||||
|
currentSplitIndex--;
|
||||||
|
return run;
|
||||||
|
} else { // split is inside run, split it
|
||||||
|
Run lower = new Run(run.bottomY(), splitY);
|
||||||
|
|
||||||
|
run = new Run(splitY + 1, run.topY());
|
||||||
|
remainingSplits.addFirst(lower);
|
||||||
|
currentSplitIndex--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return run;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*private class DownwardIterator implements IntIterator {
|
||||||
|
private final int[] column;
|
||||||
|
private final int size;
|
||||||
|
private int i;
|
||||||
|
private int currentY;
|
||||||
|
|
||||||
|
public DownwardIterator(int localX, int localZ, int yMax) {
|
||||||
|
int index = getIndex(localX, localZ);
|
||||||
|
|
||||||
|
int actualYMax = yMax - minY;
|
||||||
|
|
||||||
|
this.column = columns[index];
|
||||||
|
this.size = sizes[index];
|
||||||
|
|
||||||
|
this.i = -1;
|
||||||
|
|
||||||
|
// advance to first layer
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
int packed = column[i];
|
||||||
|
short sky = unpackSky(packed);
|
||||||
|
short bottom = unpackBottom(packed);
|
||||||
|
|
||||||
|
if (bottom != -1 && bottom > actualYMax)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
this.i = i;
|
||||||
|
currentY = Math.min(currentY, sky);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int nextInt() {
|
||||||
|
if (i == -1) {
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
}
|
||||||
|
|
||||||
|
int ret = currentY;
|
||||||
|
|
||||||
|
int packed = column[i];
|
||||||
|
short bottom = unpackBottom(packed);
|
||||||
|
|
||||||
|
if (bottom != -1 && ret == bottom) {
|
||||||
|
i++;
|
||||||
|
if (i == size) {
|
||||||
|
i = -1;
|
||||||
|
} else {
|
||||||
|
packed = column[i];
|
||||||
|
currentY = unpackSky(packed);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
currentY--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret + minY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return i != -1;
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}
|
||||||
+122
@@ -0,0 +1,122 @@
|
|||||||
|
package io.github.slimeistdev.crystalline_sky.infrastructure;
|
||||||
|
|
||||||
|
import io.github.slimeistdev.crystalline_sky.registry.CrystallineBlocks;
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import net.minecraft.util.math.Direction;
|
||||||
|
import net.minecraft.util.shape.VoxelShape;
|
||||||
|
import net.minecraft.util.shape.VoxelShapes;
|
||||||
|
import net.minecraft.world.BlockView;
|
||||||
|
import net.minecraft.world.chunk.light.ChunkLightProvider;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
public class MutableWeepingScanner implements WeepingStorage.WeepingScanner {
|
||||||
|
private @Nullable BlockView blockView;
|
||||||
|
private final int minY;
|
||||||
|
|
||||||
|
private final BlockPos.Mutable reusablePos1 = new BlockPos.Mutable();
|
||||||
|
private final BlockPos.Mutable reusablePos2 = new BlockPos.Mutable();
|
||||||
|
|
||||||
|
public MutableWeepingScanner(int minY) {
|
||||||
|
this.minY = minY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBlockView(@Nullable BlockView blockView) {
|
||||||
|
this.blockView = blockView;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int transformInputY(int y) {
|
||||||
|
return y + minY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int transformOutputY(int y) {
|
||||||
|
return y == Integer.MIN_VALUE ? -1 : y - minY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private @NotNull BlockView checkBlockView() {
|
||||||
|
if (blockView == null) {
|
||||||
|
throw new IllegalStateException("blockView is not set");
|
||||||
|
} else {
|
||||||
|
return blockView;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int scan(int localX, int yMax, int localZ) {
|
||||||
|
BlockView blockView = checkBlockView();
|
||||||
|
|
||||||
|
int actualYMax = transformInputY(yMax);
|
||||||
|
|
||||||
|
BlockPos.Mutable topPos = reusablePos1.set(localX, actualYMax, localZ);
|
||||||
|
BlockPos.Mutable bottomPos = reusablePos2.set(localX, actualYMax - 1, localZ);
|
||||||
|
BlockState topState = blockView.getBlockState(topPos);
|
||||||
|
|
||||||
|
while (bottomPos.getY() >= minY) {
|
||||||
|
BlockState bottomState = blockView.getBlockState(bottomPos);
|
||||||
|
if (faceBlocksLight(topState, bottomState)) {
|
||||||
|
return transformOutputY(bottomPos.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
topState = bottomState;
|
||||||
|
topPos.set(bottomPos);
|
||||||
|
bottomPos.move(Direction.DOWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int scanForWeepingSky(int localX, int yMax, int localZ) {
|
||||||
|
BlockView blockView = checkBlockView();
|
||||||
|
|
||||||
|
int actualYMax = transformInputY(yMax);
|
||||||
|
|
||||||
|
BlockPos.Mutable topPos = reusablePos1.set(localX, actualYMax, localZ);
|
||||||
|
BlockPos.Mutable bottomPos = reusablePos2.set(localX, actualYMax - 1, localZ);
|
||||||
|
BlockState topState = blockView.getBlockState(topPos);
|
||||||
|
|
||||||
|
if (CrystallineBlocks.isWeepingSky(topState))
|
||||||
|
return yMax;
|
||||||
|
|
||||||
|
while (bottomPos.getY() >= minY) {
|
||||||
|
BlockState bottomState = blockView.getBlockState(bottomPos);
|
||||||
|
if (CrystallineBlocks.isWeepingSky(bottomState)) {
|
||||||
|
return transformOutputY(bottomPos.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (faceBlocksLight(topState, bottomState)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
topState = bottomState;
|
||||||
|
topPos.set(bottomPos);
|
||||||
|
bottomPos.move(Direction.DOWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean faceBlocksLight(int localX, int upperY, int localZ) {
|
||||||
|
BlockView blockView = checkBlockView();
|
||||||
|
|
||||||
|
int actualY = transformInputY(upperY);
|
||||||
|
reusablePos1.set(localX, actualY, localZ);
|
||||||
|
reusablePos2.set(localX, actualY - 1, localZ);
|
||||||
|
BlockState upper = blockView.getBlockState(reusablePos1);
|
||||||
|
BlockState lower = blockView.getBlockState(reusablePos2);
|
||||||
|
|
||||||
|
return faceBlocksLight(upper, lower);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean faceBlocksLight(BlockState upper, BlockState lower) {
|
||||||
|
if (lower.getOpacity() != 0) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
VoxelShape voxelShape = ChunkLightProvider.getOpaqueShape(upper, Direction.DOWN);
|
||||||
|
VoxelShape voxelShape2 = ChunkLightProvider.getOpaqueShape(lower, Direction.UP);
|
||||||
|
return VoxelShapes.unionCoversFullCube(voxelShape, voxelShape2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
package io.github.slimeistdev.crystalline_sky.infrastructure;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntIterator;
|
||||||
|
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
|
public class PeekableIntIterator implements IntIterator {
|
||||||
|
private final IntIterator wrapped;
|
||||||
|
|
||||||
|
private boolean hasPeeked = false;
|
||||||
|
private int peekedValue;
|
||||||
|
|
||||||
|
public PeekableIntIterator(IntIterator wrapped) {
|
||||||
|
this.wrapped = wrapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int peek() {
|
||||||
|
if (!hasPeeked) {
|
||||||
|
if (!wrapped.hasNext()) {
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
}
|
||||||
|
peekedValue = wrapped.nextInt();
|
||||||
|
hasPeeked = true;
|
||||||
|
}
|
||||||
|
return peekedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int nextInt() {
|
||||||
|
if (hasPeeked) {
|
||||||
|
hasPeeked = false;
|
||||||
|
return peekedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapped.nextInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return hasPeeked || wrapped.hasNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
+53
@@ -0,0 +1,53 @@
|
|||||||
|
package io.github.slimeistdev.crystalline_sky.infrastructure;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: y coordinates are [0, ...)
|
||||||
|
*/
|
||||||
|
public interface WeepingStorage {
|
||||||
|
void insertSky(int localX, int y, int localZ, WeepingScanner scanner);
|
||||||
|
|
||||||
|
void insertSolid(int localX, int y, int localZ, WeepingScanner scanner);
|
||||||
|
|
||||||
|
void insertAir(int localX, int y, int localZ, WeepingScanner scanner);
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
boolean isLit(int localX, int y, int localZ);
|
||||||
|
|
||||||
|
Iterable<Run> iterateLitRuns(int localX, int localZ, int lowestSourceY);
|
||||||
|
|
||||||
|
Iterable<Run> iterateUnlitRuns(int localX, int localZ, int lowestSourceY);
|
||||||
|
|
||||||
|
void clearTempRunSplits(int localX, int localZ);
|
||||||
|
|
||||||
|
boolean isColumnEmpty(int localX, int localZ);
|
||||||
|
|
||||||
|
boolean hasNoUnlitSplits(int localX, int localZ);
|
||||||
|
|
||||||
|
void debugState(int localX, int localZ, Consumer<String> stringConsumer);
|
||||||
|
|
||||||
|
/** A continuous run of positions [bottomY, topY] */
|
||||||
|
record Run(int bottomY, int topY) {
|
||||||
|
public Run withBottomY(int newBottomY) {
|
||||||
|
return new Run(newBottomY, this.topY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Run withTopY(int newTopY) {
|
||||||
|
return new Run(this.bottomY, newTopY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WeepingScanner {
|
||||||
|
/** Scans downwards from yMax (exclusive) to find the first solid block.
|
||||||
|
* Returns the y coordinate of the first solid block, or -1 if none was found.
|
||||||
|
* The returned value is guaranteed to be less than yMax.
|
||||||
|
*/
|
||||||
|
int scan(int localX, int yMax, int localZ);
|
||||||
|
|
||||||
|
int scanForWeepingSky(int localX, int yMax, int localZ);
|
||||||
|
|
||||||
|
boolean faceBlocksLight(int localX, int upperY, int localZ);
|
||||||
|
}
|
||||||
|
}
|
||||||
+143
@@ -0,0 +1,143 @@
|
|||||||
|
package io.github.slimeistdev.crystalline_sky.infrastructure.client;
|
||||||
|
|
||||||
|
import io.github.slimeistdev.crystalline_sky.infrastructure.WeepingStorage;
|
||||||
|
import io.github.slimeistdev.crystalline_sky.mixin_ducks.ChunkSkyLight_Duck;
|
||||||
|
import net.fabricmc.api.EnvType;
|
||||||
|
import net.fabricmc.api.Environment;
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.client.render.RenderLayer;
|
||||||
|
import net.minecraft.client.render.VertexConsumer;
|
||||||
|
import net.minecraft.client.render.VertexConsumerProvider;
|
||||||
|
import net.minecraft.client.render.debug.DebugRenderer;
|
||||||
|
import net.minecraft.client.util.math.MatrixStack;
|
||||||
|
import net.minecraft.util.math.ChunkPos;
|
||||||
|
import net.minecraft.util.math.ColumnPos;
|
||||||
|
import net.minecraft.world.chunk.light.ChunkSkyLight;
|
||||||
|
import net.minecraft.world.chunk.light.LightSourceView;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.joml.Matrix4f;
|
||||||
|
|
||||||
|
@Environment(EnvType.CLIENT)
|
||||||
|
public class WeepingSkyDebugRenderer implements DebugRenderer.Renderer {
|
||||||
|
private final MinecraftClient client;
|
||||||
|
private @Nullable ColumnPos column = null;
|
||||||
|
private boolean litMode = true;
|
||||||
|
|
||||||
|
private static final int[] COLORS = {
|
||||||
|
0xFFFF0000,
|
||||||
|
0xFFFF7F00,
|
||||||
|
0xFFFFFF00,
|
||||||
|
0xFF00FF00,
|
||||||
|
0xFF00FFFF,
|
||||||
|
0xFF007FFF,
|
||||||
|
0xFF0000FF,
|
||||||
|
0xFF7F00FF,
|
||||||
|
0xFFFF00FF,
|
||||||
|
0xFFFF007F,
|
||||||
|
};
|
||||||
|
|
||||||
|
public WeepingSkyDebugRenderer(MinecraftClient client) {
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColumn(@Nullable ColumnPos column) {
|
||||||
|
this.column = column;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLitMode(boolean lit) {
|
||||||
|
this.litMode = lit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, double cameraX, double cameraY, double cameraZ) {
|
||||||
|
if (column == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (client.world == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ChunkPos chunkPos = column.toChunkPos();
|
||||||
|
LightSourceView chunk = client.world.getChunkManager().getChunk(chunkPos.x, chunkPos.z);
|
||||||
|
if (chunk == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ChunkSkyLight chunkSkyLight = chunk.getChunkSkyLight();
|
||||||
|
WeepingStorage weepingStorage = ((ChunkSkyLight_Duck) chunkSkyLight).crystalline_sky$getWeepingStorage();
|
||||||
|
if (weepingStorage == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int localX = column.x() & 15;
|
||||||
|
int localZ = column.z() & 15;
|
||||||
|
|
||||||
|
int lowestSourceY = chunkSkyLight.get(localX, localZ);
|
||||||
|
|
||||||
|
Matrix4f matrix4f = matrices.peek().getPositionMatrix();
|
||||||
|
|
||||||
|
float x1 = (float) (column.x() - cameraX);
|
||||||
|
float x2 = x1 + 1;
|
||||||
|
float z1 = (float) (column.z() - cameraZ);
|
||||||
|
float z2 = z1 + 1;
|
||||||
|
|
||||||
|
int colorIndex = 0;
|
||||||
|
|
||||||
|
Iterable<WeepingStorage.Run> runs = this.litMode
|
||||||
|
? weepingStorage.iterateLitRuns(localX, localZ, lowestSourceY)
|
||||||
|
: weepingStorage.iterateUnlitRuns(localX, localZ, lowestSourceY);
|
||||||
|
for (WeepingStorage.Run run : runs) {
|
||||||
|
int color = COLORS[colorIndex++ % COLORS.length];
|
||||||
|
|
||||||
|
// limit things to a renderable range
|
||||||
|
int bottomY = Math.max(-0xFFFF, run.bottomY());
|
||||||
|
int topY = Math.min(run.topY(), 0xFFFF) + 1;
|
||||||
|
|
||||||
|
float y1 = (float) (bottomY - cameraY);
|
||||||
|
float y2 = (float) (topY - cameraY);
|
||||||
|
|
||||||
|
// draw outlined box
|
||||||
|
renderOutlinedBox(
|
||||||
|
vertexConsumers, matrix4f,
|
||||||
|
x1, x2,
|
||||||
|
y1, y2,
|
||||||
|
z1, z2,
|
||||||
|
color
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void renderOutlinedBox(VertexConsumerProvider vertexConsumers, Matrix4f matrix, float x1, float x2, float y1, float y2, float z1, float z2, int color) {
|
||||||
|
VertexConsumer vertexConsumer = vertexConsumers.getBuffer(RenderLayer.getDebugLineStrip(2.0));
|
||||||
|
|
||||||
|
// render top face
|
||||||
|
renderHorizontalSquare(matrix, x1, x2, y1, z1, z2, color, vertexConsumer);
|
||||||
|
|
||||||
|
// render bottom face
|
||||||
|
renderHorizontalSquare(matrix, x1, x2, y2, z1, z2, color, vertexConsumer);
|
||||||
|
|
||||||
|
// the (x1, z1) vertical edge has been drawn, draw the other 3
|
||||||
|
|
||||||
|
// (x2, z1)
|
||||||
|
vertexConsumer.vertex(matrix, x2, y2, z1).color(color);
|
||||||
|
vertexConsumer.vertex(matrix, x2, y1, z1).color(color);
|
||||||
|
|
||||||
|
// (x2, z2)
|
||||||
|
vertexConsumer.vertex(matrix, x2, y1, z2).color(color);
|
||||||
|
vertexConsumer.vertex(matrix, x2, y2, z2).color(color);
|
||||||
|
|
||||||
|
// (x1, z2)
|
||||||
|
vertexConsumer.vertex(matrix, x1, y2, z2).color(color);
|
||||||
|
vertexConsumer.vertex(matrix, x1, y1, z2).color(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void renderHorizontalSquare(Matrix4f matrix, float x1, float x2, float y, float z1, float z2, int color, VertexConsumer vertexConsumer) {
|
||||||
|
vertexConsumer.vertex(matrix, x1, y, z1).color(color);
|
||||||
|
vertexConsumer.vertex(matrix, x2, y, z1).color(color);
|
||||||
|
vertexConsumer.vertex(matrix, x2, y, z2).color(color);
|
||||||
|
vertexConsumer.vertex(matrix, x1, y, z2).color(color);
|
||||||
|
vertexConsumer.vertex(matrix, x1, y, z1).color(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
this.column = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package io.github.slimeistdev.crystalline_sky.items;
|
||||||
|
|
||||||
|
import io.github.slimeistdev.crystalline_sky.registry.CrystallineDataComponentTypes;
|
||||||
|
import net.minecraft.block.Block;
|
||||||
|
import net.minecraft.entity.player.PlayerEntity;
|
||||||
|
import net.minecraft.item.BlockItem;
|
||||||
|
import net.minecraft.item.ItemStack;
|
||||||
|
import net.minecraft.item.ItemUsageContext;
|
||||||
|
import net.minecraft.util.ActionResult;
|
||||||
|
import net.minecraft.util.Hand;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import net.minecraft.util.math.ColumnPos;
|
||||||
|
import net.minecraft.world.World;
|
||||||
|
|
||||||
|
public class WeepingSkyBlockItem extends BlockItem {
|
||||||
|
public WeepingSkyBlockItem(Block block, Settings settings) {
|
||||||
|
super(block, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ActionResult useOnBlock(ItemUsageContext context) {
|
||||||
|
ItemStack stack = context.getStack();
|
||||||
|
if (stack.get(CrystallineDataComponentTypes.WEEPING_SKY_DEBUG_COLUMN_TOOL) != null) {
|
||||||
|
if (context.getWorld().isClient) {
|
||||||
|
BlockPos pos = context.getBlockPos();
|
||||||
|
WeepingSkyBlockItemClient.setSelectedColumn(new ColumnPos(pos.getX(), pos.getZ()));
|
||||||
|
}
|
||||||
|
return ActionResult.SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.useOnBlock(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ActionResult use(World world, PlayerEntity user, Hand hand) {
|
||||||
|
ItemStack stack = user.getStackInHand(hand);
|
||||||
|
if (stack.get(CrystallineDataComponentTypes.WEEPING_SKY_DEBUG_COLUMN_TOOL) != null) {
|
||||||
|
if (world.isClient) {
|
||||||
|
WeepingSkyBlockItemClient.setSelectedColumn(null);
|
||||||
|
}
|
||||||
|
return ActionResult.SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.use(world, user, hand);
|
||||||
|
}
|
||||||
|
}
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
package io.github.slimeistdev.crystalline_sky.items;
|
||||||
|
|
||||||
|
import io.github.slimeistdev.crystalline_sky.infrastructure.client.WeepingSkyDebugRenderer;
|
||||||
|
import io.github.slimeistdev.crystalline_sky.mixin_ducks.client.DebugRenderer_Duck;
|
||||||
|
import net.fabricmc.api.EnvType;
|
||||||
|
import net.fabricmc.api.Environment;
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.text.Text;
|
||||||
|
import net.minecraft.util.math.ColumnPos;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
public class WeepingSkyBlockItemClient {
|
||||||
|
// todo check if this is server-safe
|
||||||
|
@Environment(EnvType.CLIENT)
|
||||||
|
public static void setSelectedColumn(@Nullable ColumnPos column) {
|
||||||
|
MinecraftClient mc = MinecraftClient.getInstance();
|
||||||
|
WeepingSkyDebugRenderer weepingSkyDebugRenderer = ((DebugRenderer_Duck) mc.debugRenderer).crystalline_sky$getWeepingSkyDebugRenderer();
|
||||||
|
weepingSkyDebugRenderer.setColumn(column);
|
||||||
|
|
||||||
|
Text text = column == null
|
||||||
|
? Text.translatable("message.crystalline_sky.weeping_sky_debug.column.cleared")
|
||||||
|
: Text.translatable("message.crystalline_sky.weeping_sky_debug.column.set", column.x(), column.z());
|
||||||
|
mc.inGameHud.setOverlayMessage(text, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-1
@@ -11,6 +11,8 @@ import org.spongepowered.asm.mixin.Mixin;
|
|||||||
public class ChunkLightProviderMixin {
|
public class ChunkLightProviderMixin {
|
||||||
@WrapMethod(method = "needsLightUpdate")
|
@WrapMethod(method = "needsLightUpdate")
|
||||||
private static boolean updateSky(BlockState oldState, BlockState newState, Operation<Boolean> original) {
|
private static boolean updateSky(BlockState oldState, BlockState newState, Operation<Boolean> original) {
|
||||||
return original.call(oldState, newState) || (CrystallineBlocks.getSkyLightLevel(oldState) != CrystallineBlocks.getSkyLightLevel(newState));
|
return original.call(oldState, newState)
|
||||||
|
|| (CrystallineBlocks.getSkyLightLevel(oldState) != CrystallineBlocks.getSkyLightLevel(newState))
|
||||||
|
|| (CrystallineBlocks.isWeepingSky(oldState) != CrystallineBlocks.isWeepingSky(newState));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
package io.github.slimeistdev.crystalline_sky.mixin;
|
||||||
|
|
||||||
|
import io.github.slimeistdev.crystalline_sky.CrystallineSky;
|
||||||
|
import io.github.slimeistdev.crystalline_sky.infrastructure.IntWeepingStorage;
|
||||||
|
import io.github.slimeistdev.crystalline_sky.infrastructure.MutableWeepingScanner;
|
||||||
|
import io.github.slimeistdev.crystalline_sky.infrastructure.WeepingStorage;
|
||||||
|
import io.github.slimeistdev.crystalline_sky.mixin_ducks.ChunkSkyLight_Duck;
|
||||||
|
import io.github.slimeistdev.crystalline_sky.registry.CrystallineBlocks;
|
||||||
|
import it.unimi.dsi.fastutil.shorts.ShortArrayList;
|
||||||
|
import it.unimi.dsi.fastutil.shorts.ShortList;
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.block.Blocks;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import net.minecraft.util.math.ChunkSectionPos;
|
||||||
|
import net.minecraft.util.math.Direction;
|
||||||
|
import net.minecraft.world.BlockView;
|
||||||
|
import net.minecraft.world.HeightLimitView;
|
||||||
|
import net.minecraft.world.chunk.Chunk;
|
||||||
|
import net.minecraft.world.chunk.ChunkSection;
|
||||||
|
import net.minecraft.world.chunk.light.ChunkSkyLight;
|
||||||
|
import org.spongepowered.asm.mixin.*;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
@Mixin(ChunkSkyLight.class)
|
||||||
|
public abstract class ChunkSkyLightMixin implements ChunkSkyLight_Duck {
|
||||||
|
@Shadow
|
||||||
|
@Final
|
||||||
|
private int minY;
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
@Final
|
||||||
|
private BlockPos.Mutable reusableBlockPos1;
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
private static boolean faceBlocksLight(BlockState upper, BlockState lower) {
|
||||||
|
throw new RuntimeException("Mixin failed to apply");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
@Final
|
||||||
|
private BlockPos.Mutable reusableBlockPos2;
|
||||||
|
@Unique
|
||||||
|
@Final
|
||||||
|
@Mutable
|
||||||
|
private IntWeepingStorage crystalline_sky$weepingStorage;
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
@Final
|
||||||
|
@Mutable
|
||||||
|
private MutableWeepingScanner crystalline_sky$weepingScanner;
|
||||||
|
|
||||||
|
@Inject(method = "<init>", at = @At("RETURN"))
|
||||||
|
private void init(HeightLimitView heightLimitView, CallbackInfo ci) {
|
||||||
|
crystalline_sky$weepingStorage = new IntWeepingStorage(minY + 1);
|
||||||
|
crystalline_sky$weepingScanner = new MutableWeepingScanner(minY + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WeepingStorage crystalline_sky$getWeepingStorage() {
|
||||||
|
return crystalline_sky$weepingStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "refreshSurfaceY", at = @At("HEAD"))
|
||||||
|
private void refreshWeepingSurfaceY(Chunk chunk, CallbackInfo ci) {
|
||||||
|
int highestNonEmptySectionIndex = chunk.getHighestNonEmptySection();
|
||||||
|
if (highestNonEmptySectionIndex == -1) {
|
||||||
|
crystalline_sky$weepingStorage.clear();
|
||||||
|
} else {
|
||||||
|
int topY = ChunkSectionPos.getBlockCoord(chunk.sectionIndexToCoord(highestNonEmptySectionIndex) + 1);
|
||||||
|
int weepingMinY = minY + 1;
|
||||||
|
|
||||||
|
ShortList data = new ShortArrayList();
|
||||||
|
|
||||||
|
for (int z = 0; z < 16; z++) {
|
||||||
|
for (int x = 0; x < 16; x++) {
|
||||||
|
data.clear();
|
||||||
|
|
||||||
|
BlockPos.Mutable topPos = reusableBlockPos1.set(x, topY, z);
|
||||||
|
BlockPos.Mutable bottomPos = reusableBlockPos2.set(x, topY - 1, z);
|
||||||
|
BlockState topState = Blocks.AIR.getDefaultState();
|
||||||
|
|
||||||
|
short foundSky = -1;
|
||||||
|
|
||||||
|
for (int sectionIndex = highestNonEmptySectionIndex; sectionIndex >= 0; sectionIndex--) {
|
||||||
|
ChunkSection section = chunk.getSection(sectionIndex);
|
||||||
|
|
||||||
|
if (section.isEmpty()) {
|
||||||
|
topState = Blocks.AIR.getDefaultState();
|
||||||
|
topPos.setY(ChunkSectionPos.getBlockCoord(chunk.sectionIndexToCoord(sectionIndex)));
|
||||||
|
bottomPos.setY(topPos.getY() - 1);
|
||||||
|
} else {
|
||||||
|
for (int y = 15; y >= 0; y--) {
|
||||||
|
BlockState bottomState = section.getBlockState(x, y, z);
|
||||||
|
|
||||||
|
if (CrystallineBlocks.isWeepingSky(bottomState)) {
|
||||||
|
if (foundSky == -1) {
|
||||||
|
foundSky = (short) (bottomPos.getY() - weepingMinY);
|
||||||
|
}
|
||||||
|
} else if (faceBlocksLight(topState, bottomState)) {
|
||||||
|
if (foundSky != -1) {
|
||||||
|
short lastLitY = (short) (bottomPos.getY() - weepingMinY + 1);
|
||||||
|
data.add(foundSky);
|
||||||
|
data.add(lastLitY);
|
||||||
|
foundSky = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
topState = bottomState;
|
||||||
|
topPos.set(bottomPos);
|
||||||
|
bottomPos.move(Direction.DOWN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundSky != -1) {
|
||||||
|
data.add(foundSky);
|
||||||
|
data.add((short) -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
crystalline_sky$weepingStorage.set(x, z, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "isSkyLightAccessible(Lnet/minecraft/world/BlockView;III)Z", at = @At("HEAD"))
|
||||||
|
private void updateWeepingSkyState(BlockView blockView, int localX, int y, int localZ, CallbackInfoReturnable<Boolean> cir) {
|
||||||
|
BlockPos pos = reusableBlockPos1.set(localX, y, localZ);
|
||||||
|
BlockState state = blockView.getBlockState(pos);
|
||||||
|
|
||||||
|
crystalline_sky$weepingScanner.setBlockView(blockView);
|
||||||
|
|
||||||
|
int weepingMinY = minY + 1;
|
||||||
|
|
||||||
|
if (state.isAir()) {
|
||||||
|
crystalline_sky$weepingStorage.insertAir(localX, y - weepingMinY, localZ, crystalline_sky$weepingScanner);
|
||||||
|
} else if (CrystallineBlocks.isWeepingSky(state)) {
|
||||||
|
crystalline_sky$weepingStorage.insertSky(localX, y - weepingMinY, localZ, crystalline_sky$weepingScanner);
|
||||||
|
} else if (state.isOpaque()) {
|
||||||
|
crystalline_sky$weepingStorage.insertSolid(localX, y - weepingMinY, localZ, crystalline_sky$weepingScanner);
|
||||||
|
}
|
||||||
|
|
||||||
|
CrystallineSky.LOG.info("Weeping sky ranges for local column [{}, {}] in {}:", localX, localZ, blockView);
|
||||||
|
crystalline_sky$weepingStorage.debugState(localX, localZ, CrystallineSky.LOG::info);
|
||||||
|
|
||||||
|
crystalline_sky$weepingScanner.setBlockView(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
+186
-1
@@ -1,14 +1,23 @@
|
|||||||
package io.github.slimeistdev.crystalline_sky.mixin;
|
package io.github.slimeistdev.crystalline_sky.mixin;
|
||||||
|
|
||||||
|
import com.llamalad7.mixinextras.expression.Definition;
|
||||||
|
import com.llamalad7.mixinextras.expression.Expression;
|
||||||
|
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||||
|
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||||
|
import com.llamalad7.mixinextras.sugar.Local;
|
||||||
|
import io.github.slimeistdev.crystalline_sky.infrastructure.WeepingStorage;
|
||||||
|
import io.github.slimeistdev.crystalline_sky.mixin_ducks.ChunkSkyLight_Duck;
|
||||||
import io.github.slimeistdev.crystalline_sky.registry.CrystallineBlocks;
|
import io.github.slimeistdev.crystalline_sky.registry.CrystallineBlocks;
|
||||||
import net.minecraft.block.BlockState;
|
import net.minecraft.block.BlockState;
|
||||||
import net.minecraft.util.math.BlockPos;
|
import net.minecraft.util.math.BlockPos;
|
||||||
import net.minecraft.util.math.ChunkPos;
|
import net.minecraft.util.math.ChunkPos;
|
||||||
import net.minecraft.util.math.ChunkSectionPos;
|
import net.minecraft.util.math.ChunkSectionPos;
|
||||||
|
import net.minecraft.util.math.Direction;
|
||||||
import net.minecraft.world.chunk.Chunk;
|
import net.minecraft.world.chunk.Chunk;
|
||||||
import net.minecraft.world.chunk.ChunkProvider;
|
import net.minecraft.world.chunk.ChunkProvider;
|
||||||
import net.minecraft.world.chunk.ChunkToNibbleArrayMap;
|
import net.minecraft.world.chunk.ChunkToNibbleArrayMap;
|
||||||
import net.minecraft.world.chunk.light.ChunkLightProvider;
|
import net.minecraft.world.chunk.light.ChunkLightProvider;
|
||||||
|
import net.minecraft.world.chunk.light.ChunkSkyLight;
|
||||||
import net.minecraft.world.chunk.light.ChunkSkyLightProvider;
|
import net.minecraft.world.chunk.light.ChunkSkyLightProvider;
|
||||||
import net.minecraft.world.chunk.light.LightSourceView;
|
import net.minecraft.world.chunk.light.LightSourceView;
|
||||||
import net.minecraft.world.chunk.light.LightStorage;
|
import net.minecraft.world.chunk.light.LightStorage;
|
||||||
@@ -19,12 +28,30 @@ import org.spongepowered.asm.mixin.injection.At;
|
|||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
// todo replace all name = "..." locals with ordinal
|
||||||
@Mixin(ChunkSkyLightProvider.class)
|
@Mixin(ChunkSkyLightProvider.class)
|
||||||
public abstract class ChunkSkyLightProviderMixin<M extends ChunkToNibbleArrayMap<M>, S extends LightStorage<M>> extends ChunkLightProvider<M, S> {
|
public abstract class ChunkSkyLightProviderMixin<M extends ChunkToNibbleArrayMap<M>, S extends LightStorage<M>> extends ChunkLightProvider<M, S> {
|
||||||
@Shadow
|
@Shadow
|
||||||
@Final
|
@Final
|
||||||
private BlockPos.Mutable scratchPos;
|
private BlockPos.Mutable scratchPos;
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
private static boolean isMaxLightLevel(int lightLevel) {
|
||||||
|
throw new RuntimeException("Mixin failed to apply");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
@Final
|
||||||
|
private static long REMOVE_TOP_SKY_SOURCE_ENTRY;
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
@Final
|
||||||
|
private static long REMOVE_SKY_SOURCE_ENTRY;
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
@Final
|
||||||
|
private static long ADD_SKY_SOURCE_ENTRY;
|
||||||
|
|
||||||
protected ChunkSkyLightProviderMixin(ChunkProvider chunkProvider, S lightStorage) {
|
protected ChunkSkyLightProviderMixin(ChunkProvider chunkProvider, S lightStorage) {
|
||||||
super(chunkProvider, lightStorage);
|
super(chunkProvider, lightStorage);
|
||||||
}
|
}
|
||||||
@@ -32,12 +59,38 @@ public abstract class ChunkSkyLightProviderMixin<M extends ChunkToNibbleArrayMap
|
|||||||
@Inject(method = "checkNode", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/chunk/light/SkyLightStorage;get(J)I"))
|
@Inject(method = "checkNode", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/chunk/light/SkyLightStorage;get(J)I"))
|
||||||
private void emitFromCrystallineSky(long blockPos, CallbackInfo ci) {
|
private void emitFromCrystallineSky(long blockPos, CallbackInfo ci) {
|
||||||
BlockState state = getStateForLighting(scratchPos.set(blockPos));
|
BlockState state = getStateForLighting(scratchPos.set(blockPos));
|
||||||
int level = CrystallineBlocks.getSkyLightLevel(state);
|
int level = CrystallineBlocks.isWeepingSky(state)
|
||||||
|
? 15
|
||||||
|
: CrystallineBlocks.getSkyLightLevel(state);
|
||||||
if (level > 0 && ((LightStorageAccessor) lightStorage).crystalline_sky$callIsSectionInEnabledColumn(ChunkSectionPos.fromBlockPos(blockPos))) {
|
if (level > 0 && ((LightStorageAccessor) lightStorage).crystalline_sky$callIsSectionInEnabledColumn(ChunkSectionPos.fromBlockPos(blockPos))) {
|
||||||
enqueueIncrease(blockPos, QueueEntry.increaseLightFromEmission(level, isTrivialForLighting(state)));
|
enqueueIncrease(blockPos, QueueEntry.increaseLightFromEmission(level, isTrivialForLighting(state)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Definition(id = "y", local = @Local(type = int.class, name = "y"))
|
||||||
|
@Definition(id = "lowestSourceY", local = @Local(type = int.class, name = "lowestSourceY"))
|
||||||
|
@Expression("y >= lowestSourceY")
|
||||||
|
@WrapOperation(method = "checkNode", at = @At("MIXINEXTRAS:EXPRESSION"))
|
||||||
|
private boolean weepBelowTheLowest(int y, int lowestSourceY, Operation<Boolean> original, @Local(name = "x") int x, @Local(name = "z") int z) {
|
||||||
|
if (original.call(y, lowestSourceY)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
LightSourceView chunk = chunkProvider.getChunk(ChunkSectionPos.getSectionCoord(x), ChunkSectionPos.getSectionCoord(z));
|
||||||
|
if (chunk == null) return false;
|
||||||
|
|
||||||
|
ChunkSkyLight chunkSkyLight = chunk.getChunkSkyLight();
|
||||||
|
if (chunkSkyLight == null) return false;
|
||||||
|
|
||||||
|
int localX = ChunkSectionPos.getLocalCoord(x);
|
||||||
|
int localZ = ChunkSectionPos.getLocalCoord(z);
|
||||||
|
|
||||||
|
WeepingStorage weepingStorage = ((ChunkSkyLight_Duck) chunkSkyLight).crystalline_sky$getWeepingStorage();
|
||||||
|
if (weepingStorage.isColumnEmpty(localX, localZ)) return false;
|
||||||
|
|
||||||
|
return weepingStorage.isLit(localX, y, localZ);
|
||||||
|
}
|
||||||
|
|
||||||
@Inject(method = "propagateLight", at = @At("RETURN"))
|
@Inject(method = "propagateLight", at = @At("RETURN"))
|
||||||
private void propagateCrystallineSkyLight(ChunkPos chunkPos, CallbackInfo ci) {
|
private void propagateCrystallineSkyLight(ChunkPos chunkPos, CallbackInfo ci) {
|
||||||
LightSourceView lightSourceView = this.chunkProvider.getChunk(chunkPos.x, chunkPos.z);
|
LightSourceView lightSourceView = this.chunkProvider.getChunk(chunkPos.x, chunkPos.z);
|
||||||
@@ -47,4 +100,136 @@ public abstract class ChunkSkyLightProviderMixin<M extends ChunkToNibbleArrayMap
|
|||||||
enqueueIncrease(pos.asLong(), QueueEntry.increaseLightFromEmission(CrystallineBlocks.getSkyLightLevel(state), isTrivialForLighting(state)));
|
enqueueIncrease(pos.asLong(), QueueEntry.increaseLightFromEmission(CrystallineBlocks.getSkyLightLevel(state), isTrivialForLighting(state)));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Inject(method = "removeSourcesBelow", at = @At("HEAD"), cancellable = true)
|
||||||
|
private void removeWeepingSourcesBelow(int x, int z, int lowestSourceY, int minY, CallbackInfo ci) {
|
||||||
|
LightSourceView chunk = chunkProvider.getChunk(ChunkSectionPos.getSectionCoord(x), ChunkSectionPos.getSectionCoord(z));
|
||||||
|
if (chunk == null) return;
|
||||||
|
|
||||||
|
ChunkSkyLight chunkSkyLight = chunk.getChunkSkyLight();
|
||||||
|
if (chunkSkyLight == null) return;
|
||||||
|
|
||||||
|
int localX = ChunkSectionPos.getLocalCoord(x);
|
||||||
|
int localZ = ChunkSectionPos.getLocalCoord(z);
|
||||||
|
|
||||||
|
WeepingStorage weepingStorage = ((ChunkSkyLight_Duck) chunkSkyLight).crystalline_sky$getWeepingStorage();
|
||||||
|
if (weepingStorage.isColumnEmpty(localX, localZ) && weepingStorage.hasNoUnlitSplits(localX, localZ)) return;
|
||||||
|
|
||||||
|
// there's little chance of accidental mixin compatibility anyway, so just cancel
|
||||||
|
ci.cancel();
|
||||||
|
|
||||||
|
int sectionX = ChunkSectionPos.getSectionCoord(x);
|
||||||
|
int sectionZ = ChunkSectionPos.getSectionCoord(z);
|
||||||
|
int effectiveLowestSourceY = lowestSourceY <= minY ? Integer.MIN_VALUE : lowestSourceY;
|
||||||
|
|
||||||
|
SkyLightStorageAccessor lightStorageAccessor = (SkyLightStorageAccessor) this.lightStorage;
|
||||||
|
|
||||||
|
RunIter: for (WeepingStorage.Run run : weepingStorage.iterateUnlitRuns(localX, localZ, effectiveLowestSourceY)) {
|
||||||
|
// check lighting state of run
|
||||||
|
int topUnlit = run.topY();
|
||||||
|
int bottomUnlit = run.bottomY();
|
||||||
|
|
||||||
|
for (int sectionY = ChunkSectionPos.getSectionCoord(topUnlit); lightStorageAccessor.crystalline_sky$isAboveMinHeight(sectionY); sectionY--) {
|
||||||
|
if (lightStorageAccessor.crystalline_sky$hasSection(ChunkSectionPos.asLong(sectionX, sectionY, sectionZ))) {
|
||||||
|
int minYInSection = ChunkSectionPos.getBlockCoord(sectionY);
|
||||||
|
int maxYInSection = minYInSection + 15;
|
||||||
|
|
||||||
|
if (maxYInSection < bottomUnlit) { // todone break outer early if we're below bottomUnlit
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int it_y = Math.min(maxYInSection, topUnlit); it_y >= Math.max(minYInSection, bottomUnlit); it_y--) {
|
||||||
|
long it_pos = BlockPos.asLong(x, it_y, z);
|
||||||
|
|
||||||
|
if (CrystallineBlocks.isCrystallineSky(chunk.getBlockState(scratchPos.set(it_pos)))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isMaxLightLevel(lightStorageAccessor.crystalline_sky$get(it_pos))) {
|
||||||
|
continue RunIter;
|
||||||
|
}
|
||||||
|
|
||||||
|
lightStorageAccessor.crystalline_sky$set(it_pos, 0);
|
||||||
|
// todone which of these is correct?
|
||||||
|
enqueueDecrease(it_pos, it_y == topUnlit ? REMOVE_TOP_SKY_SOURCE_ENTRY : REMOVE_SKY_SOURCE_ENTRY);
|
||||||
|
//enqueueDecrease(it_pos, REMOVE_SKY_SOURCE_ENTRY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "addSourcesAbove", at = @At("RETURN"))
|
||||||
|
private void addAdditionalWeepingSourcesAbove(int x, int z, int lowestSourceY, int minY, CallbackInfo ci,
|
||||||
|
@Local(name = "maxAdjacentLowestSourceY") int maxAdjacentLowestSourceY) {
|
||||||
|
LightSourceView chunk = chunkProvider.getChunk(ChunkSectionPos.getSectionCoord(x), ChunkSectionPos.getSectionCoord(z));
|
||||||
|
if (chunk == null) return;
|
||||||
|
|
||||||
|
ChunkSkyLight chunkSkyLight = chunk.getChunkSkyLight();
|
||||||
|
if (chunkSkyLight == null) return;
|
||||||
|
|
||||||
|
int localX = ChunkSectionPos.getLocalCoord(x);
|
||||||
|
int localZ = ChunkSectionPos.getLocalCoord(z);
|
||||||
|
|
||||||
|
WeepingStorage weepingStorage = ((ChunkSkyLight_Duck) chunkSkyLight).crystalline_sky$getWeepingStorage();
|
||||||
|
if (weepingStorage.isColumnEmpty(localX, localZ)) {
|
||||||
|
weepingStorage.clearTempRunSplits(localX, localZ);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sectionX = ChunkSectionPos.getSectionCoord(x);
|
||||||
|
int sectionZ = ChunkSectionPos.getSectionCoord(z);
|
||||||
|
int effectiveLowestSourceY = lowestSourceY <= minY ? Integer.MIN_VALUE : lowestSourceY;
|
||||||
|
|
||||||
|
if (effectiveLowestSourceY == Integer.MIN_VALUE) {
|
||||||
|
weepingStorage.clearTempRunSplits(localX, localZ);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SkyLightStorageAccessor lightStorageAccessor = (SkyLightStorageAccessor) this.lightStorage;
|
||||||
|
|
||||||
|
RunIter: for (WeepingStorage.Run run : weepingStorage.iterateLitRuns(localX, localZ, effectiveLowestSourceY)) {
|
||||||
|
if (run.topY() == Integer.MAX_VALUE) {
|
||||||
|
if (run.bottomY() == effectiveLowestSourceY) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
run = run.withTopY(effectiveLowestSourceY - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int lowestRelevantY = Math.max(run.bottomY(), minY);
|
||||||
|
for (long it_packedSection = ChunkSectionPos.asLong(sectionX, ChunkSectionPos.getSectionCoord(lowestRelevantY), sectionZ);
|
||||||
|
!lightStorageAccessor.crystalline_sky$isAtOrAboveTopmostSection(it_packedSection);
|
||||||
|
it_packedSection = ChunkSectionPos.offset(it_packedSection, Direction.UP)
|
||||||
|
) {
|
||||||
|
if (lightStorageAccessor.crystalline_sky$hasSection(it_packedSection)) {
|
||||||
|
int minYInSection = ChunkSectionPos.getBlockCoord(ChunkSectionPos.unpackY(it_packedSection));
|
||||||
|
int maxYInSection = minYInSection + 15;
|
||||||
|
|
||||||
|
if (minYInSection > run.topY()) { // todone break outer early if we're above run.topY()
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int it_y = Math.max(minYInSection, lowestRelevantY); it_y <= Math.min(maxYInSection, run.topY()); it_y++) {
|
||||||
|
long it_pos = BlockPos.asLong(x, it_y, z);
|
||||||
|
|
||||||
|
if (CrystallineBlocks.isCrystallineSky(chunk.getBlockState(scratchPos.set(it_pos)))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isMaxLightLevel(lightStorageAccessor.crystalline_sky$get(it_pos))) {
|
||||||
|
continue RunIter;
|
||||||
|
}
|
||||||
|
|
||||||
|
lightStorageAccessor.crystalline_sky$set(it_pos, 15);
|
||||||
|
if (it_y < maxAdjacentLowestSourceY || it_y == lowestRelevantY) {
|
||||||
|
enqueueIncrease(it_pos, ADD_SKY_SOURCE_ENTRY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
weepingStorage.clearTempRunSplits(localX, localZ);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,4 +8,13 @@ import org.spongepowered.asm.mixin.gen.Invoker;
|
|||||||
public interface LightStorageAccessor {
|
public interface LightStorageAccessor {
|
||||||
@Invoker("isSectionInEnabledColumn")
|
@Invoker("isSectionInEnabledColumn")
|
||||||
boolean crystalline_sky$callIsSectionInEnabledColumn(long sectionPos);
|
boolean crystalline_sky$callIsSectionInEnabledColumn(long sectionPos);
|
||||||
|
|
||||||
|
@Invoker("hasSection")
|
||||||
|
boolean crystalline_sky$hasSection(long sectionPos);
|
||||||
|
|
||||||
|
@Invoker("get")
|
||||||
|
int crystalline_sky$get(long blockPos);
|
||||||
|
|
||||||
|
@Invoker("set")
|
||||||
|
void crystalline_sky$set(long blockPos, int lightLevel);
|
||||||
}
|
}
|
||||||
|
|||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
package io.github.slimeistdev.crystalline_sky.mixin;
|
||||||
|
|
||||||
|
import net.minecraft.world.chunk.light.SkyLightStorage;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||||
|
|
||||||
|
@Mixin(SkyLightStorage.class)
|
||||||
|
public interface SkyLightStorageAccessor extends LightStorageAccessor {
|
||||||
|
@Invoker("isAboveMinHeight")
|
||||||
|
boolean crystalline_sky$isAboveMinHeight(int sectionY);
|
||||||
|
|
||||||
|
@Invoker("isAtOrAboveTopmostSection")
|
||||||
|
boolean crystalline_sky$isAtOrAboveTopmostSection(long sectionPos);
|
||||||
|
}
|
||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
package io.github.slimeistdev.crystalline_sky.mixin.client;
|
||||||
|
|
||||||
|
import io.github.slimeistdev.crystalline_sky.infrastructure.client.WeepingSkyDebugRenderer;
|
||||||
|
import io.github.slimeistdev.crystalline_sky.mixin_ducks.client.DebugRenderer_Duck;
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.client.render.Frustum;
|
||||||
|
import net.minecraft.client.render.VertexConsumerProvider;
|
||||||
|
import net.minecraft.client.render.debug.DebugRenderer;
|
||||||
|
import net.minecraft.client.util.math.MatrixStack;
|
||||||
|
import org.spongepowered.asm.mixin.Final;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Mutable;
|
||||||
|
import org.spongepowered.asm.mixin.Unique;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
@Mixin(DebugRenderer.class)
|
||||||
|
public class DebugRendererMixin implements DebugRenderer_Duck {
|
||||||
|
@Unique
|
||||||
|
@Mutable
|
||||||
|
@Final
|
||||||
|
private WeepingSkyDebugRenderer crystalline_sky$weepingSkyDebugRenderer;
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private int crystalline_sky$weepingSkyMode = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WeepingSkyDebugRenderer crystalline_sky$getWeepingSkyDebugRenderer() {
|
||||||
|
return crystalline_sky$weepingSkyDebugRenderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int crystalline_sky$toggleWeepingSky() {
|
||||||
|
crystalline_sky$weepingSkyMode = (crystalline_sky$weepingSkyMode + 1) % 3;
|
||||||
|
return crystalline_sky$weepingSkyMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "<init>", at = @At("RETURN"))
|
||||||
|
private void init(MinecraftClient client, CallbackInfo ci) {
|
||||||
|
this.crystalline_sky$weepingSkyDebugRenderer = new WeepingSkyDebugRenderer(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "render", at = @At("RETURN"))
|
||||||
|
private void render(MatrixStack matrices, Frustum frustum, VertexConsumerProvider.Immediate vertexConsumers, double cameraX, double cameraY, double cameraZ, CallbackInfo ci) {
|
||||||
|
if (crystalline_sky$weepingSkyMode != 0) {
|
||||||
|
crystalline_sky$weepingSkyDebugRenderer.setLitMode(crystalline_sky$weepingSkyMode == 1);
|
||||||
|
crystalline_sky$weepingSkyDebugRenderer.render(matrices, vertexConsumers, cameraX, cameraY, cameraZ);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "reset", at = @At("RETURN"))
|
||||||
|
private void reset(CallbackInfo ci) {
|
||||||
|
crystalline_sky$weepingSkyDebugRenderer.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-1
@@ -158,7 +158,9 @@ public abstract class WorldRendererMixin {
|
|||||||
if (blockRenderLayer == BlockRenderLayerExt.CRYSTALLINE_SKY_SKY
|
if (blockRenderLayer == BlockRenderLayerExt.CRYSTALLINE_SKY_SKY
|
||||||
&& client.player != null
|
&& client.player != null
|
||||||
&& (client.player.getMainHandStack().isOf(CrystallineItems.SKY)
|
&& (client.player.getMainHandStack().isOf(CrystallineItems.SKY)
|
||||||
|| client.player.getOffHandStack().isOf(CrystallineItems.SKY))) {
|
|| client.player.getOffHandStack().isOf(CrystallineItems.SKY)
|
||||||
|
|| client.player.getMainHandStack().isOf(CrystallineItems.WEEPING_SKY)
|
||||||
|
|| client.player.getOffHandStack().isOf(CrystallineItems.WEEPING_SKY))) {
|
||||||
|
|
||||||
float f = ticks + client.getRenderTickCounter().getTickProgress(true);
|
float f = ticks + client.getRenderTickCounter().getTickProgress(true);
|
||||||
float alpha = (MathHelper.sin(f / 10.0f) + 1.0f) / 2.0f;
|
float alpha = (MathHelper.sin(f / 10.0f) + 1.0f) / 2.0f;
|
||||||
|
|||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
package io.github.slimeistdev.crystalline_sky.mixin_ducks;
|
||||||
|
|
||||||
|
import io.github.slimeistdev.crystalline_sky.infrastructure.WeepingStorage;
|
||||||
|
|
||||||
|
public interface ChunkSkyLight_Duck {
|
||||||
|
WeepingStorage crystalline_sky$getWeepingStorage();
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
package io.github.slimeistdev.crystalline_sky.mixin_ducks.client;
|
||||||
|
|
||||||
|
import io.github.slimeistdev.crystalline_sky.infrastructure.client.WeepingSkyDebugRenderer;
|
||||||
|
import net.fabricmc.api.EnvType;
|
||||||
|
import net.fabricmc.api.Environment;
|
||||||
|
|
||||||
|
@Environment(EnvType.CLIENT)
|
||||||
|
public interface DebugRenderer_Duck {
|
||||||
|
WeepingSkyDebugRenderer crystalline_sky$getWeepingSkyDebugRenderer();
|
||||||
|
|
||||||
|
int crystalline_sky$toggleWeepingSky();
|
||||||
|
}
|
||||||
@@ -48,17 +48,17 @@ public class CrystallineBlocks {
|
|||||||
return getSkyLightLevel(state) > 0;
|
return getSkyLightLevel(state) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isWeepingSky(BlockState state) {
|
|
||||||
return state.isOf(WEEPING_SKY);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getSkyLightLevel(BlockState state) {
|
public static int getSkyLightLevel(BlockState state) {
|
||||||
if (state.isOf(SKY_LIGHT)) {
|
if (state.isOf(SKY_LIGHT)) {
|
||||||
return state.get(LightBlock.LEVEL_15);
|
return state.get(LightBlock.LEVEL_15);
|
||||||
} else if (state.isOf(SKY) || state.isOf(WEEPING_SKY)) {
|
} else if (state.isOf(SKY)) { // weeping sky explicitly excluded, that's handled differently
|
||||||
return 15;
|
return 15;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isWeepingSky(BlockState state) {
|
||||||
|
return state.isOf(WEEPING_SKY);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
package io.github.slimeistdev.crystalline_sky.registry;
|
||||||
|
|
||||||
|
import io.github.slimeistdev.crystalline_sky.CrystallineSky;
|
||||||
|
import net.minecraft.component.ComponentType;
|
||||||
|
import net.minecraft.registry.Registries;
|
||||||
|
import net.minecraft.registry.Registry;
|
||||||
|
import net.minecraft.util.Unit;
|
||||||
|
|
||||||
|
import java.util.function.UnaryOperator;
|
||||||
|
|
||||||
|
public class CrystallineDataComponentTypes {
|
||||||
|
public static final ComponentType<Unit> WEEPING_SKY_DEBUG_COLUMN_TOOL = register(
|
||||||
|
"weeping_sky_debug_column_tool",
|
||||||
|
b -> b
|
||||||
|
.packetCodec(Unit.PACKET_CODEC)
|
||||||
|
.codec(Unit.CODEC)
|
||||||
|
);
|
||||||
|
|
||||||
|
@SuppressWarnings("SameParameterValue")
|
||||||
|
private static <T> ComponentType<T> register(String id, UnaryOperator<ComponentType.Builder<T>> builderOperator) {
|
||||||
|
return Registry.register(Registries.DATA_COMPONENT_TYPE, CrystallineSky.id(id), builderOperator.apply(ComponentType.builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void init() {}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package io.github.slimeistdev.crystalline_sky.registry;
|
package io.github.slimeistdev.crystalline_sky.registry;
|
||||||
|
|
||||||
|
import io.github.slimeistdev.crystalline_sky.items.WeepingSkyBlockItem;
|
||||||
import net.minecraft.block.LightBlock;
|
import net.minecraft.block.LightBlock;
|
||||||
import net.minecraft.component.DataComponentTypes;
|
import net.minecraft.component.DataComponentTypes;
|
||||||
import net.minecraft.component.type.BlockStateComponent;
|
import net.minecraft.component.type.BlockStateComponent;
|
||||||
@@ -15,7 +16,7 @@ public class CrystallineItems {
|
|||||||
|
|
||||||
public static final Item WEEPING_SKY = Items.register(
|
public static final Item WEEPING_SKY = Items.register(
|
||||||
CrystallineBlocks.WEEPING_SKY,
|
CrystallineBlocks.WEEPING_SKY,
|
||||||
settings -> settings.rarity(Rarity.EPIC)
|
(block, settings) -> new WeepingSkyBlockItem(block, settings.rarity(Rarity.EPIC))
|
||||||
);
|
);
|
||||||
|
|
||||||
public static final Item SKY_LIGHT = Items.register(
|
public static final Item SKY_LIGHT = Items.register(
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package io.github.slimeistdev.crystalline_sky.util;
|
||||||
|
|
||||||
|
public class ArrayUtils {
|
||||||
|
/**
|
||||||
|
* Binary search for the first index i in the sorted array where array[i] < value and array[i + 1] > value.
|
||||||
|
* @param array must be sorted in ascending order
|
||||||
|
* @param value the value to find a place for
|
||||||
|
* @return the index to insert at, or -1 if no such index exists
|
||||||
|
*/
|
||||||
|
public static int binarySearchSortedInsertionPoint(int[] array, int value) {
|
||||||
|
return binarySearchSortedInsertionPoint(array, value, array.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binary search for the first index i in the sorted array where array[i] < value and array[i + 1] > value.
|
||||||
|
* @param array must be sorted in ascending order
|
||||||
|
* @param value the value to find a place for
|
||||||
|
* @param length limits the search to indexes [0, length)
|
||||||
|
* @return the index to insert at, or -1 if no such index exists
|
||||||
|
*/
|
||||||
|
public static int binarySearchSortedInsertionPoint(int[] array, int value, int length) {
|
||||||
|
if (length == 0) return 0;
|
||||||
|
if (value < array[0]) return 0;
|
||||||
|
if (value > array[length - 1]) return length;
|
||||||
|
|
||||||
|
int left = 0;
|
||||||
|
int right = length - 1;
|
||||||
|
|
||||||
|
while (left < right) {
|
||||||
|
int mid = (left + right) / 2;
|
||||||
|
int midValue = array[mid];
|
||||||
|
|
||||||
|
if (midValue == value)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (midValue < value) {
|
||||||
|
left = mid + 1;
|
||||||
|
} else { // midValue > value
|
||||||
|
right = mid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (array[left] == value) return -1;
|
||||||
|
if (array[right] == value) return -1;
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,15 @@
|
|||||||
{
|
{
|
||||||
"block.crystalline_sky.sky": "Sky",
|
"block.crystalline_sky.sky": "Sky",
|
||||||
"block.crystalline_sky.weeping_sky": "Weeping Sky",
|
"block.crystalline_sky.weeping_sky": "Weeping Sky",
|
||||||
"block.crystalline_sky.sky_light": "Sky Light"
|
"block.crystalline_sky.sky_light": "Sky Light",
|
||||||
|
|
||||||
|
"crystalline_sky.key.toggle_weeping_sky_debug": "Toggle Weeping Sky Debug",
|
||||||
|
"crystalline_sky.category.crystalline_sky": "Crystalline Sky",
|
||||||
|
|
||||||
|
"message.crystalline_sky.weeping_sky_debug.column.cleared": "Cleared Weeping Sky debug column.",
|
||||||
|
"message.crystalline_sky.weeping_sky_debug.column.set": "Set Weeping Sky debug column to [%s, %s].",
|
||||||
|
|
||||||
|
"message.crystalline_sky.weeping_sky_debug.0": "Weeping Sky debug: off",
|
||||||
|
"message.crystalline_sky.weeping_sky_debug.1": "Weeping Sky debug: lit runs",
|
||||||
|
"message.crystalline_sky.weeping_sky_debug.2": "Weeping Sky debug: unlit runs"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,15 @@
|
|||||||
"compatibilityLevel": "JAVA_21",
|
"compatibilityLevel": "JAVA_21",
|
||||||
"mixins": [
|
"mixins": [
|
||||||
"ChunkLightProviderMixin",
|
"ChunkLightProviderMixin",
|
||||||
|
"ChunkSkyLightMixin",
|
||||||
"ChunkSkyLightProviderMixin",
|
"ChunkSkyLightProviderMixin",
|
||||||
"LightStorageAccessor"
|
"LightStorageAccessor",
|
||||||
|
"SkyLightStorageAccessor"
|
||||||
],
|
],
|
||||||
"client": [
|
"client": [
|
||||||
"client.BlockRenderLayerMixin",
|
"client.BlockRenderLayerMixin",
|
||||||
"client.ClientWorldMixin",
|
"client.ClientWorldMixin",
|
||||||
|
"client.DebugRendererMixin",
|
||||||
"client.DefaultFramebufferSetMixin",
|
"client.DefaultFramebufferSetMixin",
|
||||||
"client.FramebufferMixin",
|
"client.FramebufferMixin",
|
||||||
"client.WorldRendererAccessor",
|
"client.WorldRendererAccessor",
|
||||||
|
|||||||
Reference in New Issue
Block a user