diff --git a/polymer-core/src/main/java/eu/pb4/polymer/core/api/block/PolymerBlock.java b/polymer-core/src/main/java/eu/pb4/polymer/core/api/block/PolymerBlock.java index fcdc7c15..e8802800 100644 --- a/polymer-core/src/main/java/eu/pb4/polymer/core/api/block/PolymerBlock.java +++ b/polymer-core/src/main/java/eu/pb4/polymer/core/api/block/PolymerBlock.java @@ -48,6 +48,13 @@ default void onPolymerBlockSend(BlockState blockState, BlockPos.MutableBlockPos */ default boolean forceLightUpdates(BlockState blockState) { return false; } + /** + * You can override this method to force light to be estimated for the block position. This is useful for blocks + * using display entities, which sample their brightness from the light level at their position + * @param blockState + */ + default boolean forceLightInsideBlock(BlockState blockState) { return false; } + /** * Overrides breaking particle used by the block * @param state diff --git a/polymer-core/src/main/java/eu/pb4/polymer/core/api/block/PolymerBlockUtils.java b/polymer-core/src/main/java/eu/pb4/polymer/core/api/block/PolymerBlockUtils.java index cc76ca3e..a77237e5 100644 --- a/polymer-core/src/main/java/eu/pb4/polymer/core/api/block/PolymerBlockUtils.java +++ b/polymer-core/src/main/java/eu/pb4/polymer/core/api/block/PolymerBlockUtils.java @@ -115,12 +115,22 @@ public static boolean forceLightUpdates(BlockState blockState) { if (virtualBlock.forceLightUpdates(blockState)) { return true; } + if (virtualBlock.forceLightInsideBlock(blockState)) { + return true; + } return ((BlockStateExtra) blockState).polymer$isPolymerLightSource(); } return false; } + public static boolean forceLightInsideBlock(BlockState blockState) { + if (PolymerSyncedObject.getSyncedObject(BuiltInRegistries.BLOCK, blockState.getBlock()) instanceof PolymerBlock virtualBlock) { + return virtualBlock.forceLightInsideBlock(blockState); + } + return false; + } + /** * Gets BlockState used on client side * diff --git a/polymer-core/src/main/java/eu/pb4/polymer/core/impl/PolymerLightUpdateHelper.java b/polymer-core/src/main/java/eu/pb4/polymer/core/impl/PolymerLightUpdateHelper.java new file mode 100644 index 00000000..02dcf663 --- /dev/null +++ b/polymer-core/src/main/java/eu/pb4/polymer/core/impl/PolymerLightUpdateHelper.java @@ -0,0 +1,100 @@ +package eu.pb4.polymer.core.impl; + +import eu.pb4.polymer.core.impl.interfaces.PolymerChunkStorage; +import eu.pb4.polymer.core.impl.interfaces.PolymerChunkSectionStorage; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.SectionPos; +import net.minecraft.network.protocol.game.ClientboundLightUpdatePacketData; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.LightLayer; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.lighting.LayerLightEventListener; +import net.minecraft.world.level.lighting.LevelLightEngine; +import org.jspecify.annotations.Nullable; + +import java.util.BitSet; +import java.util.List; + +public final class PolymerLightUpdateHelper { + public static final ScopedValue CHUNK_CONTEXT = ScopedValue.newInstance(); + private static final Direction[] LIGHT_SAMPLE_DIRECTIONS = new Direction[] { + Direction.UP, Direction.DOWN, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST + }; + + private PolymerLightUpdateHelper() { + } + + public static void patchLightData(ClientboundLightUpdatePacketData data, ChunkPos chunkPos, LevelLightEngine lightEngine, + @Nullable BitSet skyChangedLightSectionFilter, @Nullable BitSet blockChangedLightSectionFilter) { + if (!CHUNK_CONTEXT.isBound()) return; + var chunk = CHUNK_CONTEXT.get(); + + if (chunk == null || !chunk.getPos().equals(chunkPos) || !((PolymerChunkStorage) chunk).polymer$hasAny()) { + return; + } + + patchLightLayer(data.getSkyYMask(), data.getSkyUpdates(), chunk, lightEngine, LightLayer.SKY, skyChangedLightSectionFilter); + patchLightLayer(data.getBlockYMask(), data.getBlockUpdates(), chunk, lightEngine, LightLayer.BLOCK, blockChangedLightSectionFilter); + } + + private static void patchLightLayer(BitSet mask, List updates, LevelChunk chunk, LevelLightEngine lightEngine, + LightLayer layer, @Nullable BitSet changedLightSectionFilter) { + var listener = lightEngine.getLayerListener(layer); + var sections = chunk.getSections(); + var mutable = new BlockPos.MutableBlockPos(); + + for (int sectionIndex = 0; sectionIndex < sections.length; sectionIndex++) { + LevelChunkSection section = sections[sectionIndex]; + if (section == null) { + continue; + } + + var storage = (PolymerChunkSectionStorage) section; + if (!storage.polymer$hasAny()) { + continue; + } + + int sectionY = chunk.getSectionYFromSectionIndex(sectionIndex); + int lightSectionIndex = sectionY - lightEngine.getMinLightSection(); + + if (changedLightSectionFilter != null && !changedLightSectionFilter.get(lightSectionIndex)) { + continue; + } + + if (!mask.get(lightSectionIndex)) { + continue; + } + byte[] update = updates.get(mask.get(0, lightSectionIndex).cardinality()); + + for (var iterator = storage.polymer$lightInsideIterator(SectionPos.of(chunk.getPos(), sectionY)); iterator.hasNext();) { + var pos = iterator.next(); + int value = getBestLightValue(listener, mutable, pos); + + setNibble(update, pos.getX() & 15, pos.getY() & 15, pos.getZ() & 15, value); + } + } + } + + private static int getBestLightValue(LayerLightEventListener listener, BlockPos.MutableBlockPos mutable, BlockPos pos) { + int value = listener.getLightValue(pos); + + for (var direction : LIGHT_SAMPLE_DIRECTIONS) { + mutable.setWithOffset(pos, direction); + value = Math.max(value, listener.getLightValue(mutable)); + } + + return value; + } + + private static void setNibble(byte[] data, int x, int y, int z, int val) { + // Matches DataLayer.set(int x, int y, int z, int val) + int index = y << 8 | z << 4 | x; + int position = index >> 1; + int nibble = index & 1; + int mask = ~(15 << 4 * nibble); + int valueToSet = (val & 0xF) << 4 * nibble; + data[position] = (byte)(data[position] & mask | valueToSet); + } +} diff --git a/polymer-core/src/main/java/eu/pb4/polymer/core/impl/interfaces/PolymerBlockPosStorage.java b/polymer-core/src/main/java/eu/pb4/polymer/core/impl/interfaces/PolymerChunkSectionStorage.java similarity index 73% rename from polymer-core/src/main/java/eu/pb4/polymer/core/impl/interfaces/PolymerBlockPosStorage.java rename to polymer-core/src/main/java/eu/pb4/polymer/core/impl/interfaces/PolymerChunkSectionStorage.java index 6a1a04a4..e489f1fc 100644 --- a/polymer-core/src/main/java/eu/pb4/polymer/core/impl/interfaces/PolymerBlockPosStorage.java +++ b/polymer-core/src/main/java/eu/pb4/polymer/core/impl/interfaces/PolymerChunkSectionStorage.java @@ -2,24 +2,20 @@ import it.unimi.dsi.fastutil.shorts.ShortSet; import org.jetbrains.annotations.ApiStatus; -import org.jspecify.annotations.Nullable; import java.util.Iterator; import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; @ApiStatus.Internal -public interface PolymerBlockPosStorage { - @Nullable +public interface PolymerChunkSectionStorage { ShortSet polymer$getBackendSet(); - @Nullable - Iterator polymer$iterator(SectionPos sectionPos); + Iterator polymer$blockIterator(SectionPos sectionPos); - @Nullable - Iterator polymer$iterator(); + Iterator polymer$lightInsideIterator(SectionPos sectionPos); - void polymer$setSynced(int x, int y, int z, boolean lightSource); + void polymer$setSynced(int x, int y, int z, boolean lightSource, boolean lightInside); void polymer$removeSynced(int x, int y, int z); boolean polymer$isSynced(int x, int y, int z); diff --git a/polymer-core/src/main/java/eu/pb4/polymer/core/impl/interfaces/PolymerChunkStorage.java b/polymer-core/src/main/java/eu/pb4/polymer/core/impl/interfaces/PolymerChunkStorage.java new file mode 100644 index 00000000..1c5d530c --- /dev/null +++ b/polymer-core/src/main/java/eu/pb4/polymer/core/impl/interfaces/PolymerChunkStorage.java @@ -0,0 +1,20 @@ +package eu.pb4.polymer.core.impl.interfaces; + +import net.minecraft.core.BlockPos; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.Nullable; + +import java.util.Iterator; + +@ApiStatus.Internal +public interface PolymerChunkStorage { + @Nullable + Iterator polymer$iterator(); + + void polymer$setSynced(int x, int y, int z, boolean lightSource, boolean lightInside); + void polymer$removeSynced(int x, int y, int z); + + boolean polymer$isSynced(int x, int y, int z); + + boolean polymer$hasAny(); +} diff --git a/polymer-core/src/main/java/eu/pb4/polymer/core/impl/networking/BlockPacketUtil.java b/polymer-core/src/main/java/eu/pb4/polymer/core/impl/networking/BlockPacketUtil.java index 27cddf46..71f011d0 100644 --- a/polymer-core/src/main/java/eu/pb4/polymer/core/impl/networking/BlockPacketUtil.java +++ b/polymer-core/src/main/java/eu/pb4/polymer/core/impl/networking/BlockPacketUtil.java @@ -4,7 +4,7 @@ import eu.pb4.polymer.core.api.utils.PolymerSyncedObject; import eu.pb4.polymer.core.impl.PolymerImplUtils; import eu.pb4.polymer.core.impl.interfaces.ChunkDataS2CPacketInterface; -import eu.pb4.polymer.core.impl.interfaces.PolymerBlockPosStorage; +import eu.pb4.polymer.core.impl.interfaces.PolymerChunkStorage; import eu.pb4.polymer.core.impl.interfaces.PolymerGamePacketListenerExtension; import eu.pb4.polymer.core.mixin.block.packet.ClientboundBlockUpdatePacketAccessor; import eu.pb4.polymer.core.mixin.block.packet.ClientboundSectionBlocksUpdatePacketAccessor; @@ -20,7 +20,6 @@ import net.minecraft.server.network.ServerGamePacketListenerImpl; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.LevelChunk; -import net.fabricmc.fabric.api.networking.v1.context.PacketContext; public class BlockPacketUtil { public static void sendFromPacket(Packet packet, ServerGamePacketListenerImpl handler) { @@ -31,7 +30,7 @@ public static void sendFromPacket(Packet packet, ServerGamePacketListenerImpl } } else if (packet instanceof ClientboundLevelChunkWithLightPacket) { LevelChunk wc = ((ChunkDataS2CPacketInterface) packet).polymer$getWorldChunk(); - PolymerBlockPosStorage wci = (PolymerBlockPosStorage) wc; + PolymerChunkStorage wci = (PolymerChunkStorage) wc; if (wc != null && wci.polymer$hasAny()) { PolymerServerProtocol.sendSectionUpdate(handler, wc); var iterator = wci.polymer$iterator(); diff --git a/polymer-core/src/main/java/eu/pb4/polymer/core/impl/networking/PolymerServerProtocol.java b/polymer-core/src/main/java/eu/pb4/polymer/core/impl/networking/PolymerServerProtocol.java index 040319f6..19277833 100644 --- a/polymer-core/src/main/java/eu/pb4/polymer/core/impl/networking/PolymerServerProtocol.java +++ b/polymer-core/src/main/java/eu/pb4/polymer/core/impl/networking/PolymerServerProtocol.java @@ -5,7 +5,8 @@ import eu.pb4.polymer.core.api.utils.PolymerSyncedObject; import eu.pb4.polymer.core.impl.PolymerImpl; import eu.pb4.polymer.core.impl.PolymerImplUtils; -import eu.pb4.polymer.core.impl.interfaces.PolymerBlockPosStorage; +import eu.pb4.polymer.core.impl.interfaces.PolymerChunkStorage; +import eu.pb4.polymer.core.impl.interfaces.PolymerChunkSectionStorage; import eu.pb4.polymer.core.impl.interfaces.PolymerIdMapper; import eu.pb4.polymer.core.impl.interfaces.RegistryExtension; import eu.pb4.polymer.core.impl.networking.entry.*; @@ -69,12 +70,12 @@ public static void sendSectionUpdate(ServerGamePacketListenerImpl player, LevelC var version = PolymerServerNetworking.getSupportedVersion(player, S2CPackets.WORLD_CHUNK_SECTION_UPDATE); if (version > -1) { - var wci = (PolymerBlockPosStorage) chunk; + var wci = (PolymerChunkStorage) chunk; if (wci.polymer$hasAny()) { var sections = chunk.getSections(); for (var i = 0; i < sections.length; i++) { var section = sections[i]; - var storage = (PolymerBlockPosStorage) section; + var storage = (PolymerChunkSectionStorage) section; if (section != null && storage.polymer$hasAny()) { var set = storage.polymer$getBackendSet(); diff --git a/polymer-core/src/main/java/eu/pb4/polymer/core/mixin/block/BlockStateMixin.java b/polymer-core/src/main/java/eu/pb4/polymer/core/mixin/block/BlockStateMixin.java index a53a0afa..352d1c94 100644 --- a/polymer-core/src/main/java/eu/pb4/polymer/core/mixin/block/BlockStateMixin.java +++ b/polymer-core/src/main/java/eu/pb4/polymer/core/mixin/block/BlockStateMixin.java @@ -37,7 +37,7 @@ public abstract class BlockStateMixin implements BlockStateExtra { } this.polymer$calculatedIsLight = true; - return false; + return polymer$isLight; } @ModifyExpressionValue(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/state/BlockState;codec(Lcom/mojang/serialization/Codec;Ljava/util/function/Function;Ljava/util/function/Function;)Lcom/mojang/serialization/Codec;")) diff --git a/polymer-core/src/main/java/eu/pb4/polymer/core/mixin/block/ChunkHolderMixin.java b/polymer-core/src/main/java/eu/pb4/polymer/core/mixin/block/ChunkHolderMixin.java new file mode 100644 index 00000000..70ac1e9f --- /dev/null +++ b/polymer-core/src/main/java/eu/pb4/polymer/core/mixin/block/ChunkHolderMixin.java @@ -0,0 +1,34 @@ +package eu.pb4.polymer.core.mixin.block; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import eu.pb4.polymer.core.impl.PolymerLightUpdateHelper; +import net.minecraft.network.protocol.game.ClientboundLightUpdatePacket; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.lighting.LevelLightEngine; +import org.jspecify.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import java.util.BitSet; + +@Mixin(ChunkHolder.class) +public class ChunkHolderMixin { + @WrapOperation( + method = "broadcastChanges", + at = @At( + value = "NEW", + target = "(Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/world/level/lighting/LevelLightEngine;Ljava/util/BitSet;Ljava/util/BitSet;)Lnet/minecraft/network/protocol/game/ClientboundLightUpdatePacket;" + ) + ) + private ClientboundLightUpdatePacket addPolymerLightContext( + ChunkPos pos, LevelLightEngine lightEngine, @Nullable BitSet skyChangedLightSectionFilter, + @Nullable BitSet blockChangedLightSectionFilter, Operation operation, + LevelChunk chunk + ) { + return ScopedValue.where(PolymerLightUpdateHelper.CHUNK_CONTEXT, chunk) + .call(() -> operation.call(pos, lightEngine, skyChangedLightSectionFilter, blockChangedLightSectionFilter)); + } +} diff --git a/polymer-core/src/main/java/eu/pb4/polymer/core/mixin/block/ServerChunkCacheMixin.java b/polymer-core/src/main/java/eu/pb4/polymer/core/mixin/block/ServerChunkCacheMixin.java index 9a8dfb77..15456eae 100644 --- a/polymer-core/src/main/java/eu/pb4/polymer/core/mixin/block/ServerChunkCacheMixin.java +++ b/polymer-core/src/main/java/eu/pb4/polymer/core/mixin/block/ServerChunkCacheMixin.java @@ -3,8 +3,9 @@ import eu.pb4.polymer.common.impl.CompatStatus; import eu.pb4.polymer.core.api.block.PolymerBlockUtils; import eu.pb4.polymer.core.impl.PolymerImpl; +import eu.pb4.polymer.core.impl.PolymerLightUpdateHelper; import eu.pb4.polymer.core.impl.compat.ImmersivePortalsUtils; -import eu.pb4.polymer.core.impl.interfaces.PolymerBlockPosStorage; +import eu.pb4.polymer.core.impl.interfaces.PolymerChunkSectionStorage; import it.unimi.dsi.fastutil.objects.Object2LongArrayMap; import it.unimi.dsi.fastutil.objects.Object2LongMap; import org.jspecify.annotations.Nullable; @@ -75,12 +76,12 @@ public abstract class ServerChunkCacheMixin { int sectionIndex = chunk.getSectionIndexFromSectionY(sectionPos.y()); // As there is an additional light section above and below the world, there might not even be a block section here if (sectionIndex >= 0 && sectionIndex < sections.length) { - if (sections[sectionIndex] instanceof PolymerBlockPosStorage section) { + if (sections[sectionIndex] instanceof PolymerChunkSectionStorage section) { section.polymer$setRequireLights(false); } } - polymer$broadcastBlockLightForSection(sectionPos); + polymer$broadcastBlockLightForSection(chunk, sectionPos); return true; }); @@ -96,14 +97,14 @@ private List getPlayersWatchingChunk(ChunkPos chunkPos) { } @Unique - private void polymer$broadcastBlockLightForSection(SectionPos pos) { + private void polymer$broadcastBlockLightForSection(LevelChunk chunk, SectionPos pos) { List players = getPlayersWatchingChunk(pos.chunk()); if (players.isEmpty()) { return; } BitSet dirtyBlockLightSections = new BitSet(); dirtyBlockLightSections.set(pos.y() - this.lightEngine.getMinLightSection()); - Packet packet = new ClientboundLightUpdatePacket(pos.chunk(), this.lightEngine, new BitSet(), dirtyBlockLightSections); + Packet packet = ScopedValue.where(PolymerLightUpdateHelper.CHUNK_CONTEXT, chunk).call(() -> new ClientboundLightUpdatePacket(pos.chunk(), this.lightEngine, new BitSet(), dirtyBlockLightSections)); for (ServerPlayer player : players) { player.connection.send(packet); } @@ -132,7 +133,7 @@ private List getPlayersWatchingChunk(ChunkPos chunkPos) { for (var i = Math.max(0, chunk.getSectionIndexFromSectionY(pos.y() - 1)); i <= max; i++) { var section = sections[i]; - if (section != null && !section.hasOnlyAir() && ((PolymerBlockPosStorage) section).polymer$requireLights()) { + if (section != null && !section.hasOnlyAir() && ((PolymerChunkSectionStorage) section).polymer$requireLights()) { return true; } } diff --git a/polymer-core/src/main/java/eu/pb4/polymer/core/mixin/block/packet/ClientboundLevelChunkWithLightPacketMixin.java b/polymer-core/src/main/java/eu/pb4/polymer/core/mixin/block/packet/ClientboundLevelChunkWithLightPacketMixin.java index 9937bf1c..b907b11f 100644 --- a/polymer-core/src/main/java/eu/pb4/polymer/core/mixin/block/packet/ClientboundLevelChunkWithLightPacketMixin.java +++ b/polymer-core/src/main/java/eu/pb4/polymer/core/mixin/block/packet/ClientboundLevelChunkWithLightPacketMixin.java @@ -1,6 +1,10 @@ package eu.pb4.polymer.core.mixin.block.packet; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import eu.pb4.polymer.core.impl.PolymerLightUpdateHelper; import eu.pb4.polymer.core.impl.interfaces.ChunkDataS2CPacketInterface; +import net.minecraft.network.protocol.game.ClientboundLightUpdatePacketData; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; @@ -8,9 +12,12 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import java.util.BitSet; + import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; +import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.lighting.LevelLightEngine; +import org.jspecify.annotations.Nullable; @Mixin(ClientboundLevelChunkWithLightPacket.class) public class ClientboundLevelChunkWithLightPacketMixin implements ChunkDataS2CPacketInterface { @@ -22,6 +29,20 @@ public class ClientboundLevelChunkWithLightPacketMixin implements ChunkDataS2CPa this.polymer$worldChunk = chunk; } + @WrapOperation( + method = "(Lnet/minecraft/world/level/chunk/LevelChunk;Lnet/minecraft/world/level/lighting/LevelLightEngine;Ljava/util/BitSet;Ljava/util/BitSet;)V", + at = @At( + value = "NEW", + target = "(Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/world/level/lighting/LevelLightEngine;Ljava/util/BitSet;Ljava/util/BitSet;)Lnet/minecraft/network/protocol/game/ClientboundLightUpdatePacketData;" + ) + ) + private ClientboundLightUpdatePacketData polymer$addPolymerLightContext( + ChunkPos chunkPos, LevelLightEngine lightEngine, @Nullable BitSet skyChangedLightSectionFilter, + @Nullable BitSet blockChangedLightSectionFilter, Operation operation, + LevelChunk chunk) { + return ScopedValue.where(PolymerLightUpdateHelper.CHUNK_CONTEXT, chunk).call(() -> operation.call(chunkPos, lightEngine, skyChangedLightSectionFilter, blockChangedLightSectionFilter)); + } + public LevelChunk polymer$getWorldChunk() { return this.polymer$worldChunk; } diff --git a/polymer-core/src/main/java/eu/pb4/polymer/core/mixin/block/packet/ClientboundLightUpdatePacketDataMixin.java b/polymer-core/src/main/java/eu/pb4/polymer/core/mixin/block/packet/ClientboundLightUpdatePacketDataMixin.java new file mode 100644 index 00000000..4a49c7c3 --- /dev/null +++ b/polymer-core/src/main/java/eu/pb4/polymer/core/mixin/block/packet/ClientboundLightUpdatePacketDataMixin.java @@ -0,0 +1,23 @@ +package eu.pb4.polymer.core.mixin.block.packet; + +import eu.pb4.polymer.core.impl.PolymerLightUpdateHelper; +import net.minecraft.network.protocol.game.ClientboundLightUpdatePacketData; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.lighting.LevelLightEngine; +import org.jspecify.annotations.Nullable; +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.CallbackInfo; + +import java.util.BitSet; + +@Mixin(ClientboundLightUpdatePacketData.class) +public class ClientboundLightUpdatePacketDataMixin { + @Inject(method = "(Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/world/level/lighting/LevelLightEngine;Ljava/util/BitSet;Ljava/util/BitSet;)V", at = @At("TAIL")) + private void polymer$patchPolymerBlockLight( + ChunkPos chunkPos, LevelLightEngine lightEngine, @Nullable BitSet skyChangedLightSectionFilter, + @Nullable BitSet blockChangedLightSectionFilter, CallbackInfo ci) { + PolymerLightUpdateHelper.patchLightData((ClientboundLightUpdatePacketData) (Object) this, chunkPos, lightEngine, skyChangedLightSectionFilter, blockChangedLightSectionFilter); + } +} diff --git a/polymer-core/src/main/java/eu/pb4/polymer/core/mixin/block/storage/LevelChunkMixin.java b/polymer-core/src/main/java/eu/pb4/polymer/core/mixin/block/storage/LevelChunkMixin.java index ce0c4293..e1505f4b 100644 --- a/polymer-core/src/main/java/eu/pb4/polymer/core/mixin/block/storage/LevelChunkMixin.java +++ b/polymer-core/src/main/java/eu/pb4/polymer/core/mixin/block/storage/LevelChunkMixin.java @@ -3,8 +3,8 @@ import com.google.common.collect.ForwardingIterator; import eu.pb4.polymer.core.api.block.PolymerBlockUtils; import eu.pb4.polymer.core.impl.PolymerImplUtils; -import eu.pb4.polymer.core.impl.interfaces.PolymerBlockPosStorage; -import it.unimi.dsi.fastutil.shorts.ShortSet; +import eu.pb4.polymer.core.impl.interfaces.PolymerChunkStorage; +import eu.pb4.polymer.core.impl.interfaces.PolymerChunkSectionStorage; import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; import net.minecraft.server.level.ServerLevel; @@ -31,7 +31,7 @@ import java.util.Iterator; @Mixin(LevelChunk.class) -public abstract class LevelChunkMixin extends ChunkAccess implements PolymerBlockPosStorage { +public abstract class LevelChunkMixin extends ChunkAccess implements PolymerChunkStorage { public LevelChunkMixin(ChunkPos pos, UpgradeData upgradeData, LevelHeightAccessor heightLimitView, PalettedContainerFactory palettesFactory, long inhabitedTime, @Nullable LevelChunkSection[] sectionArray, @Nullable BlendingData blendingData) { super(pos, upgradeData, heightLimitView, palettesFactory, inhabitedTime, sectionArray, blendingData); @@ -54,14 +54,14 @@ public LevelChunkMixin(ChunkPos pos, UpgradeData upgradeData, LevelHeightAccesso if (section != null && !section.hasOnlyAir()) { var container = section.getStates(); if (container.maybeHas(PolymerImplUtils.POLYMER_STATES::contains)) { - var storage = (PolymerBlockPosStorage) section; + var storage = (PolymerChunkSectionStorage) section; BlockState state; for (byte x = 0; x < 16; x++) { for (byte z = 0; z < 16; z++) { for (byte y = 0; y < 16; y++) { state = container.get(x, y, z); if (PolymerImplUtils.POLYMER_STATES.contains(state)) { - storage.polymer$setSynced(x, y, z, PolymerBlockUtils.forceLightUpdates(state)); + storage.polymer$setSynced(x, y, z, PolymerBlockUtils.forceLightUpdates(state), PolymerBlockUtils.forceLightInsideBlock(state)); } } } @@ -76,7 +76,7 @@ public LevelChunkMixin(ChunkPos pos, UpgradeData upgradeData, LevelHeightAccesso @Inject(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;", shift = At.Shift.AFTER)) private void polymer$addToList(BlockPos pos, BlockState state, int flags, CallbackInfoReturnable cir) { if (PolymerImplUtils.POLYMER_STATES.contains(state)) { - this.polymer$setSynced(pos.getX(), pos.getY(), pos.getZ(), PolymerBlockUtils.forceLightUpdates(state)); + this.polymer$setSynced(pos.getX(), pos.getY(), pos.getZ(), PolymerBlockUtils.forceLightUpdates(state), PolymerBlockUtils.forceLightInsideBlock(state)); } else { this.polymer$removeSynced(pos.getX(), pos.getY(), pos.getZ()); } @@ -95,9 +95,9 @@ protected Iterator delegate() { while (this.current < array.length) { var id = this.current++; var s = array[id]; - var si = (PolymerBlockPosStorage) s; + var si = (PolymerChunkSectionStorage) s; if (s != null && si.polymer$hasAny()) { - this.currentIterator = si.polymer$iterator(SectionPos.of(LevelChunkMixin.this.getPos(), LevelChunkMixin.this.getSectionYFromSectionIndex(id))); + this.currentIterator = si.polymer$blockIterator(SectionPos.of(LevelChunkMixin.this.getPos(), LevelChunkMixin.this.getSectionYFromSectionIndex(id))); break; } } @@ -109,8 +109,8 @@ protected Iterator delegate() { } @Override - public void polymer$setSynced(int x, int y, int z, boolean lightSource) { - this.polymer_getSectionStorage(y).polymer$setSynced(x, y, z, lightSource); + public void polymer$setSynced(int x, int y, int z, boolean lightSource, boolean lightInside) { + this.polymer_getSectionStorage(y).polymer$setSynced(x, y, z, lightSource, lightInside); } @Override @@ -126,24 +126,14 @@ protected Iterator delegate() { @Override public boolean polymer$hasAny() { for (var s : this.getSections()) { - if (s != null && ((PolymerBlockPosStorage) s).polymer$hasAny()) { + if (s != null && ((PolymerChunkSectionStorage) s).polymer$hasAny()) { return true; } } return false; } - @Override - public @Nullable ShortSet polymer$getBackendSet() { - return null; - } - - @Override - public @Nullable Iterator polymer$iterator(SectionPos sectionPos) { - return null; - } - - private PolymerBlockPosStorage polymer_getSectionStorage(int y) { - return (PolymerBlockPosStorage) this.getSection(this.getSectionIndex(y)); + private PolymerChunkSectionStorage polymer_getSectionStorage(int y) { + return (PolymerChunkSectionStorage) this.getSection(this.getSectionIndex(y)); } } diff --git a/polymer-core/src/main/java/eu/pb4/polymer/core/mixin/block/storage/LevelChunkSectionMixin.java b/polymer-core/src/main/java/eu/pb4/polymer/core/mixin/block/storage/LevelChunkSectionMixin.java index b86fcb0f..6c00d663 100644 --- a/polymer-core/src/main/java/eu/pb4/polymer/core/mixin/block/storage/LevelChunkSectionMixin.java +++ b/polymer-core/src/main/java/eu/pb4/polymer/core/mixin/block/storage/LevelChunkSectionMixin.java @@ -1,9 +1,9 @@ package eu.pb4.polymer.core.mixin.block.storage; -import eu.pb4.polymer.core.impl.interfaces.PolymerBlockPosStorage; +import eu.pb4.polymer.core.impl.interfaces.PolymerChunkSectionStorage; +import it.unimi.dsi.fastutil.shorts.ShortIterator; import it.unimi.dsi.fastutil.shorts.ShortOpenHashSet; import it.unimi.dsi.fastutil.shorts.ShortSet; -import org.jspecify.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; @@ -13,23 +13,24 @@ import net.minecraft.world.level.chunk.LevelChunkSection; @Mixin(LevelChunkSection.class) -public class LevelChunkSectionMixin implements PolymerBlockPosStorage { +public class LevelChunkSectionMixin implements PolymerChunkSectionStorage { @Unique private final ShortSet polymer$blocks = new ShortOpenHashSet(); @Unique private final ShortSet polymer$lights = new ShortOpenHashSet(); @Unique + private final ShortSet polymer$lightInsides = new ShortOpenHashSet(); + @Unique private boolean polymer$requireLightUpdate;; @Override - public @Nullable ShortSet polymer$getBackendSet() { + public ShortSet polymer$getBackendSet() { return this.polymer$blocks; } - @Override - public Iterator polymer$iterator(SectionPos sectionPos) { + @Unique + public Iterator polymer$iterator(ShortIterator iterator, SectionPos sectionPos) { var blockPos = new BlockPos.MutableBlockPos(); - var iterator = this.polymer$blocks.iterator(); return new Iterator<>() { @Override @@ -47,31 +48,39 @@ public BlockPos.MutableBlockPos next() { } @Override - public @Nullable Iterator polymer$iterator() { - return null; + public Iterator polymer$blockIterator(SectionPos sectionPos) { + return polymer$iterator(this.polymer$blocks.iterator(), sectionPos); } @Override - public void polymer$setSynced(int x, int y, int z, boolean lightSource) { - var i = PolymerBlockPosStorage.pack(x, y, z); + public Iterator polymer$lightInsideIterator(SectionPos sectionPos) { + return polymer$iterator(this.polymer$lightInsides.iterator(), sectionPos); + } + + @Override + public void polymer$setSynced(int x, int y, int z, boolean lightSource, boolean lightInside) { + var i = PolymerChunkSectionStorage.pack(x, y, z); this.polymer$blocks.add(i); if (lightSource) { this.polymer$lights.add(i); } + if (lightInside) { + this.polymer$lightInsides.add(i); + } } @Override public void polymer$removeSynced(int x, int y, int z) { - var i = PolymerBlockPosStorage.pack(x, y, z); + var i = PolymerChunkSectionStorage.pack(x, y, z); this.polymer$blocks.remove(i); - if (this.polymer$lights.remove(i)) { + if (this.polymer$lights.remove(i) || this.polymer$lightInsides.remove(i)) { this.polymer$requireLightUpdate = true; } } @Override public boolean polymer$isSynced(int x, int y, int z) { - return this.polymer$blocks.contains(PolymerBlockPosStorage.pack(x, y, z)); + return this.polymer$blocks.contains(PolymerChunkSectionStorage.pack(x, y, z)); } @Override @@ -86,7 +95,7 @@ public BlockPos.MutableBlockPos next() { @Override public boolean polymer$requireLights() { - return this.polymer$requireLightUpdate || polymer$hasLights(); + return this.polymer$requireLightUpdate || polymer$hasLights() || !this.polymer$lightInsides.isEmpty(); } @Override diff --git a/polymer-core/src/main/resources/polymer-core.mixins.json b/polymer-core/src/main/resources/polymer-core.mixins.json index c3dd25de..a87f2732 100644 --- a/polymer-core/src/main/resources/polymer-core.mixins.json +++ b/polymer-core/src/main/resources/polymer-core.mixins.json @@ -13,6 +13,7 @@ "block.BlockMixin", "block.BlockStateBaseMixin", "block.BlockStateMixin", + "block.ChunkHolderMixin", "block.ClientboundBlockEntityDataPacketAccessor", "block.PalettedContainerAccessor", "block.PlayerChunkSenderMixin", @@ -26,6 +27,7 @@ "block.packet.ClientboundBlockUpdatePacketAccessor", "block.packet.ClientboundLevelChunkPacketDataMixin", "block.packet.ClientboundLevelChunkWithLightPacketMixin", + "block.packet.ClientboundLightUpdatePacketDataMixin", "block.packet.ClientboundLevelEventPacketMixin", "block.packet.ClientboundSectionBlocksUpdatePacketAccessor", "block.packet.ClientboundSectionBlocksUpdatePacketMixin", diff --git a/polymer-core/src/testmod/java/eu/pb4/polymertest/HolderBarrierBlock.java b/polymer-core/src/testmod/java/eu/pb4/polymertest/HolderBarrierBlock.java new file mode 100644 index 00000000..c4073f98 --- /dev/null +++ b/polymer-core/src/testmod/java/eu/pb4/polymertest/HolderBarrierBlock.java @@ -0,0 +1,49 @@ +package eu.pb4.polymertest; + +import eu.pb4.polymer.core.api.block.PolymerBlock; +import eu.pb4.polymer.virtualentity.api.BlockWithElementHolder; +import eu.pb4.polymer.virtualentity.api.ElementHolder; +import eu.pb4.polymer.virtualentity.api.elements.ItemDisplayElement; +import net.fabricmc.fabric.api.networking.v1.context.PacketContext; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.item.ItemDisplayContext; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import org.jspecify.annotations.Nullable; + +public class HolderBarrierBlock extends Block implements PolymerBlock, BlockWithElementHolder { + private final Block block; + + public HolderBarrierBlock(Properties settings, Block block) { + super(settings); + this.block = block; + } + + @Override + public BlockState getPolymerBlockState(BlockState state, @Nullable PacketContext context) { + return Blocks.BARRIER.defaultBlockState(); + } + + @Override + public boolean forceLightInsideBlock(BlockState blockState) { + return true; + } + + @Override + public @Nullable ElementHolder createElementHolder(ServerLevel world, BlockPos pos, BlockState initialBlockState) { + return new CustomHolder(block); + } + + public static class CustomHolder extends ElementHolder { + + public CustomHolder(Block block) { + var element = new ItemDisplayElement(block.asItem()); + element.setItemDisplayContext(ItemDisplayContext.NONE); + element.setInvisible(true); + this.addElement(element); + } + + } +} diff --git a/polymer-core/src/testmod/java/eu/pb4/polymertest/TestMod.java b/polymer-core/src/testmod/java/eu/pb4/polymertest/TestMod.java index 319404ea..443cc1e1 100644 --- a/polymer-core/src/testmod/java/eu/pb4/polymertest/TestMod.java +++ b/polymer-core/src/testmod/java/eu/pb4/polymertest/TestMod.java @@ -307,7 +307,9 @@ public InteractionResult interactLivingEntity(ItemStack stack, Player player, Li } })); public static Block ANIMATED_BLOCK = registerBlock(Identifier.fromNamespaceAndPath("test", "animated"), s -> new AnimatedBlock(s.lightLevel((state) -> 15).strength(2f))); + public static Block HOLDER_DIRT = registerBlock(Identifier.fromNamespaceAndPath("test", "holder_dirt"), s -> new HolderBarrierBlock(s, Blocks.DIRT)); public static BlockItem ANIMATED_BLOCK_ITEM = registerItem(Identifier.fromNamespaceAndPath("test", "animated"), (s) -> new PolymerBlockItem(ANIMATED_BLOCK, s, Items.BEACON)); + public static BlockItem HOLDER_DIRT_ITEM = registerItem(Identifier.fromNamespaceAndPath("test", "holder_dirt"), (s) -> new PolymerBlockItem(HOLDER_DIRT, s, Items.DIRT)); public static Block END_GATEWAY = registerBlock(Identifier.fromNamespaceAndPath("test", "end_gateway"), s -> new FakeEndGatewayBlock(s.lightLevel((state) -> 15).strength(2f))); public static BlockEntityType END_GATEWAY_BE = register(BuiltInRegistries.BLOCK_ENTITY_TYPE, Identifier.fromNamespaceAndPath("test", "end_gateway"),