Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ public static ServerSubLevel assembleBlocks(final ServerLevel level, final Block
pipeline.teleport(subLevel, subLevel.logicalPose().position(), subLevel.logicalPose().orientation());
subLevel.updateLastPose();

// Trigger light rescan for the new sub-level so it picks up world light sources
dev.ryanhcode.sable.render.light_bridge.ServerSubLevelLightInjector.markNeedsFullRescan(subLevel.getUniqueId());
dev.ryanhcode.sable.render.light_bridge.ServerSubLevelWorldInjector.markNeedsFullRescan(subLevel.getUniqueId());

SubLevelAssemblyHelper.moveTrackingPoints(level, bounds, subLevel, transform);

return subLevel;
Expand All @@ -136,6 +140,10 @@ public static void kickFromContainingSubLevel(final ServerLevel level,
containingPose.transformPosition(subLevel.logicalPose().position());

subLevel.setSplitFrom((ServerSubLevel) containingSubLevel, originalPose);

// Trigger light rescan for the new split sub-level so it picks up world light sources
dev.ryanhcode.sable.render.light_bridge.ServerSubLevelLightInjector.markNeedsFullRescan(subLevel.getUniqueId());
dev.ryanhcode.sable.render.light_bridge.ServerSubLevelWorldInjector.markNeedsFullRescan(subLevel.getUniqueId());
}

/**
Expand Down Expand Up @@ -356,6 +364,7 @@ public static void moveBlocks(final ServerLevel level, final AssemblyTransform t
}

final LevelChunk chunk = resultingAccelerator.getChunk(SectionPos.blockToSectionCoord(newPos.getX()), SectionPos.blockToSectionCoord(newPos.getZ()));
if (chunk == null) continue;

chunk.setBlockState(newPos, subLevelState, true);
states.add(subLevelState);
Expand All @@ -381,6 +390,7 @@ public static void moveBlocks(final ServerLevel level, final AssemblyTransform t

try {
final LevelChunk levelchunk = resultingAccelerator.getChunk(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ()));
if (levelchunk == null) continue;
final BlockState subLevelState = states.get(i);
SubLevelAssemblyHelper.markAndNotifyBlock(resultingLevel, pos, levelchunk, Blocks.AIR.defaultBlockState(), subLevelState, 3, 512);
} catch (final Exception e) {
Expand All @@ -398,6 +408,7 @@ public static void moveBlocks(final ServerLevel level, final AssemblyTransform t
try {
final LevelChunk chunk = accelerator.getChunk(SectionPos.blockToSectionCoord(block.getX()),
SectionPos.blockToSectionCoord(block.getZ()));
if (chunk == null) continue;

chunk.setBlockState(block, subLevelState, true);
} catch (final Exception e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import dev.ryanhcode.sable.Sable;
import dev.ryanhcode.sable.companion.math.Pose3d;
import dev.ryanhcode.sable.render.light_bridge.ServerSubLevelWorldInjector;
import dev.ryanhcode.sable.sublevel.ServerSubLevel;
import dev.ryanhcode.sable.sublevel.SubLevel;
import dev.ryanhcode.sable.sublevel.storage.SubLevelOccupancySavedData;
Expand Down Expand Up @@ -71,6 +72,7 @@ public void initialize() {
@Override
public void tick() {
super.tick();
ServerSubLevelWorldInjector.tick(this.getLevel());
this.holdingChunkMap.processChanges();
}

Expand Down Expand Up @@ -120,6 +122,8 @@ public void removeSubLevel(final int x, final int z, final SubLevelRemovalReason
subLevel.deleteAllEntities();
}

ServerSubLevelWorldInjector.onSubLevelRemoved(this.getLevel(), subLevel.getUniqueId());

super.removeSubLevel(x, z, reason);

if (reason == SubLevelRemovalReason.REMOVED) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import dev.ryanhcode.sable.Sable;
import dev.ryanhcode.sable.SableCommonEvents;
import dev.ryanhcode.sable.api.SubLevelHelper;
import dev.ryanhcode.sable.render.light_bridge.ServerSubLevelLightInjector;
import dev.ryanhcode.sable.render.light_bridge.ServerSubLevelWorldInjector;
import dev.ryanhcode.sable.sublevel.ServerSubLevel;
import dev.ryanhcode.sable.sublevel.SubLevel;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
Expand Down Expand Up @@ -33,6 +35,13 @@ public class LevelChunkMixin {
@Unique
private BlockPos sable$blockSet = null;

/**
* Set during {@link #sable$setBlockState} when a block's light emission changes,
* read in {@link #sable$postSetBlockState} so we can poke the server-side light injector once.
*/
@Unique
private boolean sable$emissionDirty = false;

@Inject(method = "setBlockState", at = @At("HEAD"))
private void sable$preSetBlockState(final BlockPos pPos, final BlockState pState, final boolean pIsMoving,
final CallbackInfoReturnable<BlockState> cir) {
Expand All @@ -47,25 +56,40 @@ public class LevelChunkMixin {

if (subLevel != null) {
subLevel.getPlot().onBlockChange(this.sable$blockSet, pState);

// Any block change on a plot can affect opacity or emission — always notify world injector.
if (this.level instanceof final ServerLevel serverLevel
&& subLevel instanceof final ServerSubLevel serverSubLevel) {
ServerSubLevelWorldInjector.onPlotBlockChanged(serverSubLevel);
if (this.sable$emissionDirty) {
ServerSubLevelLightInjector.onPlotBlockLightChanged(serverLevel, serverSubLevel);
}
}
}
}

this.sable$blockSet = null;
this.sable$emissionDirty = false;
}

@WrapOperation(method = "setBlockState", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/chunk/LevelChunkSection;setBlockState(IIILnet/minecraft/world/level/block/state/BlockState;)Lnet/minecraft/world/level/block/state/BlockState;"))
private BlockState sable$setBlockState(final LevelChunkSection instance, int pX, int pY, int pZ, final BlockState newState, final Operation<BlockState> original) {
final BlockState oldState = original.call(instance, pX, pY, pZ, newState);

if (this.level instanceof final ServerLevel serverLevel && oldState != newState) {
pX = this.sable$blockSet.getX();
pY = this.sable$blockSet.getY();
pZ = this.sable$blockSet.getZ();
if (oldState != newState) {
if (oldState.getLightEmission() != newState.getLightEmission()) {
this.sable$emissionDirty = true;
}

SableCommonEvents.handleBlockChange(serverLevel, (LevelChunk) (Object) this, pX, pY, pZ, oldState, newState);
if (this.level instanceof final ServerLevel serverLevel) {
pX = this.sable$blockSet.getX();
pY = this.sable$blockSet.getY();
pZ = this.sable$blockSet.getZ();

SableCommonEvents.handleBlockChange(serverLevel, (LevelChunk) (Object) this, pX, pY, pZ, oldState, newState);
}
}

return oldState;
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package dev.ryanhcode.sable.mixin.plot.lighting.server;

import dev.ryanhcode.sable.render.light_bridge.ServerSubLevelLightInjector;
import dev.ryanhcode.sable.render.light_bridge.ServerSubLevelWorldInjector;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.lighting.LightEngine;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

@Mixin(LightEngine.class)
public abstract class LightEngineOpacityMixin {

@Inject(method = "getOpacity", at = @At("HEAD"), cancellable = true)
private void sable$blockLightAtOpaquePositions(final BlockState state, final BlockPos pos, final CallbackInfoReturnable<Integer> cir) {
final long packed = pos.asLong();
if (ServerSubLevelWorldInjector.isOpaqueAt(packed)) {
cir.setReturnValue(16);
return;
}
if (ServerSubLevelLightInjector.isWorldOpaqueInPlot(packed)) {
cir.setReturnValue(16);
}
}

@Inject(method = "shapeOccludes", at = @At("HEAD"), cancellable = true)
private void sable$shapeOccludesSubLevel(final long sourcePos, final BlockState sourceState, final long targetPos, final BlockState targetState, final Direction direction, final CallbackInfoReturnable<Boolean> cir) {
final byte targetMask = ServerSubLevelWorldInjector.getShapeOcclusion(targetPos);
if (targetMask != 0 && (targetMask & (1 << direction.getOpposite().ordinal())) != 0) {
cir.setReturnValue(true);
return;
}
final byte sourceMask = ServerSubLevelWorldInjector.getShapeOcclusion(sourcePos);
if (sourceMask != 0 && (sourceMask & (1 << direction.ordinal())) != 0) {
cir.setReturnValue(true);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package dev.ryanhcode.sable.mixin.plot.lighting.server;

import dev.ryanhcode.sable.render.light_bridge.ServerSubLevelLightInjector;
import net.minecraft.core.SectionPos;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.LightLayer;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

/**
* Forwards server-side block-light updates to the {@link ServerSubLevelLightInjector} so plots
* can be re-scanned and reinjected if the change occurred near a sub-level.
*/
@Mixin(ServerChunkCache.class)
public class ServerChunkCacheLightMixin {

@Shadow @Final private ServerLevel level;

@Inject(method = "onLightUpdate", at = @At("RETURN"))
private void sable$forwardBlockLightUpdate(final LightLayer layer, final SectionPos pos, final CallbackInfo ci) {
if (layer != LightLayer.BLOCK) {
return;
}

ServerSubLevelLightInjector.onServerLightUpdate(this.level, pos.getX(), pos.getY(), pos.getZ());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,7 @@ public void handleChunkSectionAddition(final LevelChunkSection section, final in
// if it's only air, all zeros will do. it'll default to empty neighborhood state and 0 (empty) collider ID
if (!section.hasOnlyAir()) {
final LevelChunk chunk = this.accelerator.getChunk(x, z);
if (chunk == null) return;

for (int bx = 0; bx < 16; bx++) {
for (int bz = 0; bz < 16; bz++) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package dev.ryanhcode.sable.render.light_bridge;

/**
* Cached plot-local block data for a sub-level's light-relevant blocks.
* Populated once on block change, re-projected to world space on movement.
*/
public final class PlotLocalLightData {

/** Plot-local packed positions of fully opaque blocks (canOcclude && !useShapeForLightOcclusion). */
public final long[] opaquePositions;

/** Plot-local packed positions of light-emitting blocks. */
public final long[] emitterPositions;

/** Emission level (1-15) parallel to emitterPositions. */
public final byte[] emitterLevels;

/** Plot-local packed positions of shape-occluding blocks (useShapeForLightOcclusion). */
public final long[] shapePositions;

/** Face occlusion mask (6 bits) parallel to shapePositions. */
public final byte[] shapeMasks;

public PlotLocalLightData(final long[] opaquePositions, final long[] emitterPositions, final byte[] emitterLevels, final long[] shapePositions, final byte[] shapeMasks) {
this.opaquePositions = opaquePositions;
this.emitterPositions = emitterPositions;
this.emitterLevels = emitterLevels;
this.shapePositions = shapePositions;
this.shapeMasks = shapeMasks;
}

public static final PlotLocalLightData EMPTY = new PlotLocalLightData(new long[0], new long[0], new byte[0], new long[0], new byte[0]);
}
Loading