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 i field_44745 ADD_SKY_SOURCE_ENTRY
|
||||
f Ljb$a; j field_44746 scratchPos
|
||||
m (IIII)V a method_51586 removeSourcesBelow
|
||||
m (JLjh;IZI)V a method_51587 propagateFromEmptySections
|
||||
p 0 blockPos
|
||||
p 1 direction
|
||||
p 2 lightLevel
|
||||
p 3 shouldIncrease
|
||||
p 4 emptySections
|
||||
p 1 blockPos
|
||||
p 3 direction
|
||||
p 4 lightLevel
|
||||
p 5 shouldIncrease
|
||||
p 6 emptySections
|
||||
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
|
||||
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
|
||||
v 3 5 -1 _ _ x
|
||||
v 4 11 -1 _ _ y
|
||||
|
||||
@@ -1,14 +1,47 @@
|
||||
package io.github.slimeistdev.crystalline_sky;
|
||||
|
||||
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 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.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 {
|
||||
@Override
|
||||
public void onInitializeClient() {
|
||||
BlockRenderLayerMap.putBlock(CrystallineBlocks.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;
|
||||
|
||||
import io.github.slimeistdev.crystalline_sky.registry.CrystallineBlocks;
|
||||
import io.github.slimeistdev.crystalline_sky.registry.CrystallineDataComponentTypes;
|
||||
import io.github.slimeistdev.crystalline_sky.registry.CrystallineItems;
|
||||
|
||||
public class ModSetup {
|
||||
public static void init() {
|
||||
CrystallineDataComponentTypes.init();
|
||||
CrystallineBlocks.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 {
|
||||
@WrapMethod(method = "needsLightUpdate")
|
||||
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;
|
||||
|
||||
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 net.minecraft.block.BlockState;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.ChunkPos;
|
||||
import net.minecraft.util.math.ChunkSectionPos;
|
||||
import net.minecraft.util.math.Direction;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
import net.minecraft.world.chunk.ChunkProvider;
|
||||
import net.minecraft.world.chunk.ChunkToNibbleArrayMap;
|
||||
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.LightSourceView;
|
||||
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.callback.CallbackInfo;
|
||||
|
||||
// todo replace all name = "..." locals with ordinal
|
||||
@Mixin(ChunkSkyLightProvider.class)
|
||||
public abstract class ChunkSkyLightProviderMixin<M extends ChunkToNibbleArrayMap<M>, S extends LightStorage<M>> extends ChunkLightProvider<M, S> {
|
||||
@Shadow
|
||||
@Final
|
||||
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) {
|
||||
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"))
|
||||
private void emitFromCrystallineSky(long blockPos, CallbackInfo ci) {
|
||||
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))) {
|
||||
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"))
|
||||
private void propagateCrystallineSkyLight(ChunkPos chunkPos, CallbackInfo ci) {
|
||||
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)));
|
||||
});
|
||||
}
|
||||
|
||||
@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 {
|
||||
@Invoker("isSectionInEnabledColumn")
|
||||
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
|
||||
&& client.player != null
|
||||
&& (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 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;
|
||||
}
|
||||
|
||||
public static boolean isWeepingSky(BlockState state) {
|
||||
return state.isOf(WEEPING_SKY);
|
||||
}
|
||||
|
||||
public static int getSkyLightLevel(BlockState state) {
|
||||
if (state.isOf(SKY_LIGHT)) {
|
||||
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 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;
|
||||
|
||||
import io.github.slimeistdev.crystalline_sky.items.WeepingSkyBlockItem;
|
||||
import net.minecraft.block.LightBlock;
|
||||
import net.minecraft.component.DataComponentTypes;
|
||||
import net.minecraft.component.type.BlockStateComponent;
|
||||
@@ -15,7 +16,7 @@ public class CrystallineItems {
|
||||
|
||||
public static final Item WEEPING_SKY = Items.register(
|
||||
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(
|
||||
|
||||
@@ -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.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",
|
||||
"mixins": [
|
||||
"ChunkLightProviderMixin",
|
||||
"ChunkSkyLightMixin",
|
||||
"ChunkSkyLightProviderMixin",
|
||||
"LightStorageAccessor"
|
||||
"LightStorageAccessor",
|
||||
"SkyLightStorageAccessor"
|
||||
],
|
||||
"client": [
|
||||
"client.BlockRenderLayerMixin",
|
||||
"client.ClientWorldMixin",
|
||||
"client.DebugRendererMixin",
|
||||
"client.DefaultFramebufferSetMixin",
|
||||
"client.FramebufferMixin",
|
||||
"client.WorldRendererAccessor",
|
||||
|
||||
Reference in New Issue
Block a user