From 5f9aadb1509663aa1105a287d1ddd523af85a7fc Mon Sep 17 00:00:00 2001 From: p1k0chu Date: Mon, 12 May 2025 18:01:25 +0300 Subject: [PATCH 1/8] add the most primitive light "engine" the world ever seen --- .../clientbound/ClientboundChunkDataPacket.kt | 20 ++---- .../ClientboundUpdateLightPacket.kt | 17 +++++ .../packets/registry/ClientPacketRegistry.kt | 2 +- .../io/github/dockyardmc/world/Light.kt | 54 +++++++++++++++ .../io/github/dockyardmc/world/LightEngine.kt | 67 +++++++++++++++++++ .../io/github/dockyardmc/world/Lights.kt | 12 ---- .../io/github/dockyardmc/world/chunk/Chunk.kt | 26 +++++-- 7 files changed, 165 insertions(+), 33 deletions(-) create mode 100644 src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundUpdateLightPacket.kt create mode 100644 src/main/kotlin/io/github/dockyardmc/world/Light.kt create mode 100644 src/main/kotlin/io/github/dockyardmc/world/LightEngine.kt delete mode 100644 src/main/kotlin/io/github/dockyardmc/world/Lights.kt diff --git a/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundChunkDataPacket.kt b/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundChunkDataPacket.kt index 6824b55c2..fc443165d 100644 --- a/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundChunkDataPacket.kt +++ b/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundChunkDataPacket.kt @@ -1,20 +1,20 @@ package io.github.dockyardmc.protocol.packets.play.clientbound -import io.github.dockyardmc.extentions.* +import io.github.dockyardmc.extentions.toByteArraySafe +import io.github.dockyardmc.extentions.writeByteArray +import io.github.dockyardmc.extentions.writeNBT +import io.github.dockyardmc.extentions.writeVarInt import io.github.dockyardmc.protocol.packets.ClientboundPacket -import io.github.dockyardmc.protocol.writeList -import io.github.dockyardmc.world.chunk.ChunkUtils import io.github.dockyardmc.utils.writeMSNBT import io.github.dockyardmc.world.Light import io.github.dockyardmc.world.block.BlockEntity import io.github.dockyardmc.world.chunk.ChunkSection -import io.netty.buffer.ByteBuf +import io.github.dockyardmc.world.chunk.ChunkUtils import io.netty.buffer.Unpooled import it.unimi.dsi.fastutil.objects.ObjectCollection import org.jglrxavpok.hephaistos.nbt.NBTCompound class ClientboundChunkDataPacket(x: Int, z: Int, heightMap: NBTCompound, sections: MutableList, blockEntities: ObjectCollection, light: Light) : ClientboundPacket() { - init { //X Z buffer.writeInt(x) @@ -42,14 +42,6 @@ class ClientboundChunkDataPacket(x: Int, z: Int, heightMap: NBTCompound, section buffer.writeNBT(blockEntity.data) } - // Light stuff - buffer.writeLongArray(light.skyMask.toLongArray()) - buffer.writeLongArray(light.blockMask.toLongArray()) - - buffer.writeLongArray(light.emptySkyMask.toLongArray()) - buffer.writeLongArray(light.emptyBlockMask.toLongArray()) - - buffer.writeList(light.skyLight, ByteBuf::writeByteArray) - buffer.writeList(light.blockLight, ByteBuf::writeByteArray) + light.write(buffer) } } \ No newline at end of file diff --git a/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundUpdateLightPacket.kt b/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundUpdateLightPacket.kt new file mode 100644 index 000000000..f8504887e --- /dev/null +++ b/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundUpdateLightPacket.kt @@ -0,0 +1,17 @@ +package io.github.dockyardmc.protocol.packets.play.clientbound + +import io.github.dockyardmc.extentions.writeVarInt +import io.github.dockyardmc.protocol.packets.ClientboundPacket +import io.github.dockyardmc.world.Light + +class ClientboundUpdateLightPacket( + chunkX: Int, + chunkZ: Int, + light: Light +) : ClientboundPacket() { + init { + buffer.writeVarInt(chunkX) + buffer.writeVarInt(chunkZ) + light.write(buffer) + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/github/dockyardmc/protocol/packets/registry/ClientPacketRegistry.kt b/src/main/kotlin/io/github/dockyardmc/protocol/packets/registry/ClientPacketRegistry.kt index bcbc6fc36..926c72240 100644 --- a/src/main/kotlin/io/github/dockyardmc/protocol/packets/registry/ClientPacketRegistry.kt +++ b/src/main/kotlin/io/github/dockyardmc/protocol/packets/registry/ClientPacketRegistry.kt @@ -84,7 +84,7 @@ object ClientPacketRegistry : PacketRegistry() { addPlay(ClientboundChunkDataPacket::class) addPlay(ClientboundWorldEventPacket::class) addPlay(ClientboundSendParticlePacket::class) - skipPlay("update light") + addPlay(ClientboundUpdateLightPacket::class) addPlay(ClientboundLoginPacket::class) skipPlay("map data") skipPlay("trade list") diff --git a/src/main/kotlin/io/github/dockyardmc/world/Light.kt b/src/main/kotlin/io/github/dockyardmc/world/Light.kt new file mode 100644 index 000000000..8cf4a95d0 --- /dev/null +++ b/src/main/kotlin/io/github/dockyardmc/world/Light.kt @@ -0,0 +1,54 @@ +package io.github.dockyardmc.world + +import io.github.dockyardmc.extentions.writeByteArray +import io.github.dockyardmc.extentions.writeLongArray +import io.github.dockyardmc.protocol.NetworkWritable +import io.github.dockyardmc.protocol.writeList +import io.netty.buffer.ByteBuf +import java.util.* + +class Light( + lightEngine: LightEngine +) : NetworkWritable { + val skyMask: BitSet = BitSet() + val blockMask: BitSet = BitSet() + val emptySkyMask: BitSet = BitSet() + val emptyBlockMask: BitSet = BitSet() + val skyLight: MutableList = mutableListOf() + val blockLight: MutableList = mutableListOf() + + init { + // first section is below the world. how awesome. we love minecraft + emptySkyMask.set(0) + emptyBlockMask.set(0) + + // last section is one section above the world. why. + emptySkyMask.set(skyLight.size + 1) + emptyBlockMask.set(skyLight.size + 1) + + lightEngine.skyLight.indices.forEach { i -> + if(lightEngine.hasNonZeroData(lightEngine.skyLight[i])) { + skyMask.set(i + 1) + skyLight.add(lightEngine.skyLight[i]) + } else { + emptySkyMask.set(i + 1) + } + + if(lightEngine.hasNonZeroData(lightEngine.blockLight[i])) { + blockMask.set(i + 1) + blockLight.add(lightEngine.blockLight[i]) + } else { + emptyBlockMask.set(i + 1) + } + } + } + + override fun write(buffer: ByteBuf) { + buffer.writeLongArray(skyMask.toLongArray()) + buffer.writeLongArray(blockMask.toLongArray()) + buffer.writeLongArray(emptySkyMask.toLongArray()) + buffer.writeLongArray(emptyBlockMask.toLongArray()) + buffer.writeList(skyLight, ByteBuf::writeByteArray) + buffer.writeList(blockLight, ByteBuf::writeByteArray) + } +} diff --git a/src/main/kotlin/io/github/dockyardmc/world/LightEngine.kt b/src/main/kotlin/io/github/dockyardmc/world/LightEngine.kt new file mode 100644 index 000000000..568bdd053 --- /dev/null +++ b/src/main/kotlin/io/github/dockyardmc/world/LightEngine.kt @@ -0,0 +1,67 @@ +package io.github.dockyardmc.world + +import io.github.dockyardmc.world.chunk.Chunk +import io.github.dockyardmc.world.chunk.ChunkSection + +class LightEngine( + val chunk: Chunk +) { + companion object { + const val ARRAY_SIZE = 2048 + } + + lateinit var recalcArray: ByteArray + + val skyLight: Array = Array(chunk.maxSection - chunk.minSection) { ByteArray(0) } + val blockLight: Array = Array(chunk.maxSection - chunk.minSection) { ByteArray(0) } + + fun recalculateChunk() { + chunk.sections.forEachIndexed { i, section -> + recalculateSection(section, i) + } + } + + fun recalculateSection(section: ChunkSection, sectionIndex: Int) { + recalcArray = ByteArray(ARRAY_SIZE) + + for (x in 0..15) { + for (z in 0..15) { + var foundSolid = false + for (y in 15 downTo 0) { + var light = 15 + + foundSolid = foundSolid || section.getBlock(x, y, z) != 0 + + if (foundSolid) { + light = 0 + } + + set(x, y, z, light) + //if(light == 0) { + // log("Writing light 0 for $x, $y, $z (section index $sectionIndex)") + // log("${recalcArray[getCoordIndex(x, y, z) ushr 1]}") + //} + } + } + } + + skyLight[sectionIndex] = recalcArray + } + + operator fun set(x: Int, y: Int, z: Int, value: Int) { + this[x or (z shl 4) or (y shl 8)] = value + } + + // https://github.com/PaperMC/Starlight/blob/6503621c6fe1b798328a69f1bca784c6f3ffcee3/src/main/java/ca/spottedleaf/starlight/common/light/SWMRNibbleArray.java#L410 + // operation type: updating + operator fun set(index: Int, value: Int) { + val shift = index and 1 shl 2 + val i = index ushr 1 + recalcArray[i] = (recalcArray[i].toInt() and (0xF0 ushr shift) or (value shl shift)).toByte() + } + + fun hasNonZeroData(array: ByteArray?): Boolean { + if (array == null) return false + return array.isNotEmpty() && array.any { it != 0.toByte() } + } +} diff --git a/src/main/kotlin/io/github/dockyardmc/world/Lights.kt b/src/main/kotlin/io/github/dockyardmc/world/Lights.kt deleted file mode 100644 index 23cdfc514..000000000 --- a/src/main/kotlin/io/github/dockyardmc/world/Lights.kt +++ /dev/null @@ -1,12 +0,0 @@ -package io.github.dockyardmc.world - -import java.util.* - -data class Light( - var skyMask: BitSet = BitSet(), - var blockMask: BitSet = BitSet(), - var emptySkyMask: BitSet = BitSet(), - var emptyBlockMask: BitSet = BitSet(), - var skyLight: List = emptyList(), - var blockLight: List = emptyList() -) diff --git a/src/main/kotlin/io/github/dockyardmc/world/chunk/Chunk.kt b/src/main/kotlin/io/github/dockyardmc/world/chunk/Chunk.kt index fbc9435f1..873eb6aea 100644 --- a/src/main/kotlin/io/github/dockyardmc/world/chunk/Chunk.kt +++ b/src/main/kotlin/io/github/dockyardmc/world/chunk/Chunk.kt @@ -6,9 +6,11 @@ import io.github.dockyardmc.location.Location import io.github.dockyardmc.player.Player import io.github.dockyardmc.protocol.packets.play.clientbound.ClientboundChunkDataPacket import io.github.dockyardmc.protocol.packets.play.clientbound.ClientboundUnloadChunkPacket +import io.github.dockyardmc.protocol.packets.play.clientbound.ClientboundUpdateLightPacket import io.github.dockyardmc.registry.registries.Biome import io.github.dockyardmc.utils.Viewable import io.github.dockyardmc.world.Light +import io.github.dockyardmc.world.LightEngine import io.github.dockyardmc.world.World import io.github.dockyardmc.world.block.Block import io.github.dockyardmc.world.block.BlockEntity @@ -28,6 +30,7 @@ class Chunk(val chunkX: Int, val chunkZ: Int, val world: World) : Viewable() { val minSection = world.dimensionType.minY / 16 val maxSection = world.dimensionType.height / 16 private lateinit var cachedPacket: ClientboundChunkDataPacket + private lateinit var cachedLightPacket: ClientboundUpdateLightPacket override var autoViewable: Boolean = true val heightmaps = EnumMap<_, ChunkHeightmap>(ChunkHeightmap.Type::class.java) @@ -38,13 +41,18 @@ class Chunk(val chunkX: Int, val chunkZ: Int, val world: World) : Viewable() { val sections: MutableList = mutableListOf() val blockEntities: Int2ObjectOpenHashMap = Int2ObjectOpenHashMap(0) - val light: Light = Light() + val lightEngine = LightEngine(this) val packet: ClientboundChunkDataPacket get() { if (!this::cachedPacket.isInitialized) updateCache() return cachedPacket } + val lightPacket: ClientboundUpdateLightPacket + get() { + if(!this::cachedLightPacket.isInitialized) updateLight() + return cachedLightPacket + } fun updateCache() { val heightmapNbt = NBT.Compound { builder -> @@ -54,8 +62,13 @@ class Chunk(val chunkX: Int, val chunkZ: Int, val world: World) : Viewable() { } } } + updateLight() + cachedPacket = ClientboundChunkDataPacket(chunkX, chunkZ, heightmapNbt, sections, blockEntities.values, Light(lightEngine)) + } - cachedPacket = ClientboundChunkDataPacket(chunkX, chunkZ, heightmapNbt, sections, blockEntities.values, light) + fun updateLight() { + lightEngine.recalculateChunk() + cachedLightPacket = ClientboundUpdateLightPacket(chunkX, chunkZ, Light(lightEngine)) } init { @@ -67,7 +80,6 @@ class Chunk(val chunkX: Int, val chunkZ: Int, val world: World) : Viewable() { getOrCreateHeightmap(type) ChunkHeightmap.generate(this, setOf(type)) } - updateCache() } fun setBlockRaw(x: Int, y: Int, z: Int, blockStateId: Int, shouldCache: Boolean = true) { @@ -181,16 +193,18 @@ class Chunk(val chunkX: Int, val chunkZ: Int, val world: World) : Viewable() { fun getOrCreateHeightmap(type: ChunkHeightmap.Type): ChunkHeightmap = heightmaps.computeIfAbsent(type) { ChunkHeightmap(this, type) } fun sendUpdateToViewers() { - viewers.sendPacket(cachedPacket) + viewers.sendPacket(packet) + viewers.sendPacket(lightPacket) } override fun addViewer(player: Player) { viewers.add(player) - player.sendPacket(cachedPacket) + player.sendPacket(packet) + player.sendPacket(lightPacket) } override fun removeViewer(player: Player) { viewers.remove(player) player.sendPacket(ClientboundUnloadChunkPacket(this.chunkPos)) } -} \ No newline at end of file +} From f21686a7d44f0c288d6534b808d6f1b9ab69328f Mon Sep 17 00:00:00 2001 From: LukynkaCZE Date: Sat, 19 Jul 2025 01:21:53 +0200 Subject: [PATCH 2/8] fix stuff --- .../io/github/dockyardmc/entity/Entity.kt | 10 ++----- .../clientbound/ClientboundChunkDataPacket.kt | 5 +--- .../io/github/dockyardmc/world/Light.kt | 16 +++++------ .../dockyardmc/world/PlayerChunkViewSystem.kt | 4 +-- .../io/github/dockyardmc/world/chunk/Chunk.kt | 27 ++++++++++++------- 5 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/main/kotlin/io/github/dockyardmc/entity/Entity.kt b/src/main/kotlin/io/github/dockyardmc/entity/Entity.kt index 68a3b8ba5..a3b2a0561 100644 --- a/src/main/kotlin/io/github/dockyardmc/entity/Entity.kt +++ b/src/main/kotlin/io/github/dockyardmc/entity/Entity.kt @@ -93,6 +93,8 @@ abstract class Entity(open var location: Location, open var world: World) : Disp val potionEffectsHandler = EntityPotionEffectsHandler(this) val itemPickupHandler = EntityItemPickupHandler(this) + val chunk: Chunk get() = world.chunks[ChunkPos.fromLocation(location).pack()] + var isDead: Boolean = false override var autoViewable: Boolean = true @@ -125,14 +127,6 @@ abstract class Entity(open var location: Location, open var world: World) : Disp } } - fun getCurrentChunk(): Chunk? { - return world.chunks[getCurrentChunkPos().pack()] - } - - fun getCurrentChunkPos(): ChunkPos { - return ChunkPos.fromLocation(location) - } - fun updateEntity(player: Player, respawn: Boolean = false) { sendMetadataPacketToViewers() } diff --git a/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundChunkDataPacket.kt b/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundChunkDataPacket.kt index fd6fe20e7..80a9e5451 100644 --- a/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundChunkDataPacket.kt +++ b/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundChunkDataPacket.kt @@ -1,9 +1,6 @@ package io.github.dockyardmc.protocol.packets.play.clientbound -import io.github.dockyardmc.extentions.toByteArraySafe -import io.github.dockyardmc.extentions.writeByteArray -import io.github.dockyardmc.extentions.writeNBT -import io.github.dockyardmc.extentions.writeVarInt +import io.github.dockyardmc.extentions.* import io.github.dockyardmc.protocol.packets.ClientboundPacket import io.github.dockyardmc.protocol.types.writeMap import io.github.dockyardmc.world.Light diff --git a/src/main/kotlin/io/github/dockyardmc/world/Light.kt b/src/main/kotlin/io/github/dockyardmc/world/Light.kt index 8cf4a95d0..029e922f3 100644 --- a/src/main/kotlin/io/github/dockyardmc/world/Light.kt +++ b/src/main/kotlin/io/github/dockyardmc/world/Light.kt @@ -3,12 +3,12 @@ package io.github.dockyardmc.world import io.github.dockyardmc.extentions.writeByteArray import io.github.dockyardmc.extentions.writeLongArray import io.github.dockyardmc.protocol.NetworkWritable -import io.github.dockyardmc.protocol.writeList +import io.github.dockyardmc.protocol.types.writeList import io.netty.buffer.ByteBuf import java.util.* class Light( - lightEngine: LightEngine + val lightEngine: LightEngine ) : NetworkWritable { val skyMask: BitSet = BitSet() val blockMask: BitSet = BitSet() @@ -27,14 +27,14 @@ class Light( emptyBlockMask.set(skyLight.size + 1) lightEngine.skyLight.indices.forEach { i -> - if(lightEngine.hasNonZeroData(lightEngine.skyLight[i])) { + if (lightEngine.hasNonZeroData(lightEngine.skyLight[i])) { skyMask.set(i + 1) skyLight.add(lightEngine.skyLight[i]) } else { emptySkyMask.set(i + 1) } - if(lightEngine.hasNonZeroData(lightEngine.blockLight[i])) { + if (lightEngine.hasNonZeroData(lightEngine.blockLight[i])) { blockMask.set(i + 1) blockLight.add(lightEngine.blockLight[i]) } else { @@ -44,10 +44,10 @@ class Light( } override fun write(buffer: ByteBuf) { - buffer.writeLongArray(skyMask.toLongArray()) - buffer.writeLongArray(blockMask.toLongArray()) - buffer.writeLongArray(emptySkyMask.toLongArray()) - buffer.writeLongArray(emptyBlockMask.toLongArray()) + buffer.writeLongArray(skyMask.toLongArray().toList()) + buffer.writeLongArray(blockMask.toLongArray().toList()) + buffer.writeLongArray(emptySkyMask.toLongArray().toList()) + buffer.writeLongArray(emptyBlockMask.toLongArray().toList()) buffer.writeList(skyLight, ByteBuf::writeByteArray) buffer.writeList(blockLight, ByteBuf::writeByteArray) } diff --git a/src/main/kotlin/io/github/dockyardmc/world/PlayerChunkViewSystem.kt b/src/main/kotlin/io/github/dockyardmc/world/PlayerChunkViewSystem.kt index 0219a67cc..2670fd930 100644 --- a/src/main/kotlin/io/github/dockyardmc/world/PlayerChunkViewSystem.kt +++ b/src/main/kotlin/io/github/dockyardmc/world/PlayerChunkViewSystem.kt @@ -27,7 +27,7 @@ class PlayerChunkViewSystem(val player: Player) { world.scheduler.runAsync { - val currentChunkPos = player.getCurrentChunkPos() + val currentChunkPos = player.chunk.chunkPos val currentChunkIndex = currentChunkPos.pack() val (currentChunkX, currentChunkZ) = ChunkPos.unpack(currentChunkIndex) @@ -60,7 +60,7 @@ class PlayerChunkViewSystem(val player: Player) { fun resendChunks() { player.world.scheduler.runAsync { - getChunksInRange(player.getCurrentChunkPos()).forEach { + getChunksInRange(player.chunk.chunkPos).forEach { loadChunk(ChunkPos.fromIndex(it)) } } diff --git a/src/main/kotlin/io/github/dockyardmc/world/chunk/Chunk.kt b/src/main/kotlin/io/github/dockyardmc/world/chunk/Chunk.kt index 37fec1bfd..82a90c560 100644 --- a/src/main/kotlin/io/github/dockyardmc/world/chunk/Chunk.kt +++ b/src/main/kotlin/io/github/dockyardmc/world/chunk/Chunk.kt @@ -6,9 +6,11 @@ import io.github.dockyardmc.nbt.nbt import io.github.dockyardmc.player.Player import io.github.dockyardmc.protocol.packets.play.clientbound.ClientboundChunkDataPacket import io.github.dockyardmc.protocol.packets.play.clientbound.ClientboundUnloadChunkPacket +import io.github.dockyardmc.protocol.packets.play.clientbound.ClientboundUpdateLightPacket import io.github.dockyardmc.registry.registries.Biome import io.github.dockyardmc.utils.viewable.Viewable import io.github.dockyardmc.world.Light +import io.github.dockyardmc.world.LightEngine import io.github.dockyardmc.world.World import io.github.dockyardmc.world.block.Block import io.github.dockyardmc.world.block.BlockEntity @@ -26,9 +28,11 @@ class Chunk(val chunkX: Int, val chunkZ: Int, val world: World) : Viewable() { val id: UUID = UUID.randomUUID() val minSection = world.dimensionType.minY / 16 val maxSection = world.dimensionType.height / 16 - private lateinit var cachedPacket: ClientboundChunkDataPacket override var autoViewable: Boolean = true + private lateinit var cachedLightPacket: ClientboundUpdateLightPacket + private lateinit var cachedPacket: ClientboundChunkDataPacket + val heightmaps = EnumMap<_, ChunkHeightmap>(ChunkHeightmap.Type::class.java) val motionBlocking: LongArray = LongArray(37) { 0 } @@ -39,7 +43,7 @@ class Chunk(val chunkX: Int, val chunkZ: Int, val world: World) : Viewable() { val sections: MutableList = mutableListOf() val blockEntities: Int2ObjectOpenHashMap = Int2ObjectOpenHashMap(0) - val light: Light = Light() + val lightEngine = LightEngine(this) val packet: ClientboundChunkDataPacket get() { @@ -47,6 +51,12 @@ class Chunk(val chunkX: Int, val chunkZ: Int, val world: World) : Viewable() { return cachedPacket } + val lightPacket: ClientboundUpdateLightPacket + get() { + if(!this::cachedLightPacket.isInitialized) updateLight() + return cachedLightPacket + } + init { val sectionsAmount = maxSection - minSection repeat(sectionsAmount) { @@ -74,15 +84,12 @@ class Chunk(val chunkX: Int, val chunkZ: Int, val world: World) : Viewable() { } } } - cachedPacket = ClientboundChunkDataPacket(chunkX, chunkZ, heightmapData, sections, blockEntities.values, light) + cachedPacket = ClientboundChunkDataPacket(chunkX, chunkZ, heightmapData, sections, blockEntities.values, Light(lightEngine)) } - init { - val sectionsAmount = maxSection - minSection - repeat(sectionsAmount) { - sections.add(ChunkSection.empty()) - } - updateCache() + fun updateLight() { + lightEngine.recalculateChunk() + cachedLightPacket = ClientboundUpdateLightPacket(chunkX, chunkZ, Light(lightEngine)) } fun setBlockRaw(x: Int, y: Int, z: Int, blockStateId: Int, shouldCache: Boolean = true) { @@ -197,11 +204,13 @@ class Chunk(val chunkX: Int, val chunkZ: Int, val world: World) : Viewable() { fun sendUpdateToViewers() { viewers.sendPacket(cachedPacket) + viewers.sendPacket(lightPacket) } override fun addViewer(player: Player): Boolean { if (!super.addViewer(player)) return false player.sendPacket(cachedPacket) + player.sendPacket(lightPacket) return true } From 691ed0343b3a864374012b614fd68d72eefcf92b Mon Sep 17 00:00:00 2001 From: LukynkaCZE Date: Mon, 4 Aug 2025 04:21:20 +0200 Subject: [PATCH 3/8] block light propagation --- .../io/github/dockyardmc/world/LightEngine.kt | 87 +++++++++++++++++-- 1 file changed, 81 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/io/github/dockyardmc/world/LightEngine.kt b/src/main/kotlin/io/github/dockyardmc/world/LightEngine.kt index 568bdd053..148385eb9 100644 --- a/src/main/kotlin/io/github/dockyardmc/world/LightEngine.kt +++ b/src/main/kotlin/io/github/dockyardmc/world/LightEngine.kt @@ -1,7 +1,10 @@ package io.github.dockyardmc.world +import io.github.dockyardmc.maths.vectors.Vector3 +import io.github.dockyardmc.world.block.Block import io.github.dockyardmc.world.chunk.Chunk import io.github.dockyardmc.world.chunk.ChunkSection +import java.util.* class LightEngine( val chunk: Chunk @@ -28,7 +31,8 @@ class LightEngine( for (z in 0..15) { var foundSolid = false for (y in 15 downTo 0) { - var light = 15 + var light = 0 +// var light = 15 foundSolid = foundSolid || section.getBlock(x, y, z) != 0 @@ -37,15 +41,75 @@ class LightEngine( } set(x, y, z, light) - //if(light == 0) { - // log("Writing light 0 for $x, $y, $z (section index $sectionIndex)") - // log("${recalcArray[getCoordIndex(x, y, z) ushr 1]}") - //} } } } - skyLight[sectionIndex] = recalcArray + recalculateBlockLight(section, sectionIndex) + } + + fun recalculateBlockLight(section: ChunkSection, sectionIndex: Int) { + recalcArray = ByteArray(ARRAY_SIZE) + val lightQueue: Queue = ArrayDeque() + + for (x in 0..15) { + for (z in 0..15) { + for (y in 15 downTo 0) { + var light = 0 + + val blockId = section.getBlock(x, y, z) + val block = Block.getBlockByStateId(blockId).registryBlock + + if (block.lightEmission > 0) { + light = block.lightEmission + lightQueue.add(Vector3(x, y, z)) + } + + set(x, y, z, light) + } + } + } + + lightPropagation(lightQueue, section, sectionIndex) + blockLight[sectionIndex] = recalcArray + } + + fun lightPropagation(queue: Queue, section: ChunkSection, sectionIndex: Int) { + val directions = arrayOf( + Vector3(1, 0, 0), Vector3(-1, 0, 0), + Vector3(0, 1, 0), Vector3(0, -1, 0), + Vector3(0, 0, 1), Vector3(0, 0, -1) + ) + + while (queue.isNotEmpty()) { + val (x, y, z) = queue.poll() + val currentLightLevel = get(x, y, z) + + if (currentLightLevel <= 1) { + continue + } + + val newLightLevel = currentLightLevel - 1 + + for ((dX, dY, dZ) in directions) { + val neighborX = x + dX + val neighborY = y + dY + val neighborZ = z + dZ + if (neighborX in 0..15 && neighborY in 0..15 && neighborZ in 0..15) { + val neighborBlockId = section.getBlock(neighborX, neighborY, neighborZ) + val neighborBlock = Block.getBlockByStateId(neighborBlockId).registryBlock + + if (!neighborBlock.canOcclude) { + val neighborCurrentLight = get(neighborX, neighborY, neighborZ) + + if (newLightLevel > neighborCurrentLight) { + set(neighborX, neighborY, neighborZ, newLightLevel) + queue.add(Vector3(neighborX, neighborY, neighborZ)) + } + } + } + } + } } operator fun set(x: Int, y: Int, z: Int, value: Int) { @@ -60,6 +124,17 @@ class LightEngine( recalcArray[i] = (recalcArray[i].toInt() and (0xF0 ushr shift) or (value shl shift)).toByte() } + operator fun get(x: Int, y: Int, z: Int): Int { + return this[x or (z shl 4) or (y shl 8)] + } + + operator fun get(index: Int): Int { + val shift = index and 1 shl 2 + val i = index ushr 1 + + return (recalcArray[i].toInt() ushr shift) and 0xF + } + fun hasNonZeroData(array: ByteArray?): Boolean { if (array == null) return false return array.isNotEmpty() && array.any { it != 0.toByte() } From b1d24678382c8851bb9ab9a179d4fdd8bf5ada9a Mon Sep 17 00:00:00 2001 From: LukynkaCZE Date: Mon, 4 Aug 2025 19:33:51 +0200 Subject: [PATCH 4/8] fix x and z being swapped when getting chunk pos from location --- src/main/kotlin/io/github/dockyardmc/world/chunk/ChunkPos.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/io/github/dockyardmc/world/chunk/ChunkPos.kt b/src/main/kotlin/io/github/dockyardmc/world/chunk/ChunkPos.kt index 0f544551a..a9ce7ba2d 100644 --- a/src/main/kotlin/io/github/dockyardmc/world/chunk/ChunkPos.kt +++ b/src/main/kotlin/io/github/dockyardmc/world/chunk/ChunkPos.kt @@ -49,7 +49,7 @@ data class ChunkPos(val x: Int, val z: Int) : NetworkWritable { val x = ChunkUtils.getChunkCoordinate(location.x) val z = ChunkUtils.getChunkCoordinate(location.z) - return ChunkPos(x, z) + return ChunkPos(z, x) } } } \ No newline at end of file From 653f56eee36b9a5e451cbde0d587498223434213 Mon Sep 17 00:00:00 2001 From: LukynkaCZE Date: Tue, 16 Sep 2025 21:00:40 +0200 Subject: [PATCH 5/8] fix double init in chunk --- .../io/github/dockyardmc/world/chunk/Chunk.kt | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/io/github/dockyardmc/world/chunk/Chunk.kt b/src/main/kotlin/io/github/dockyardmc/world/chunk/Chunk.kt index 2cf82f736..3e9b3026f 100644 --- a/src/main/kotlin/io/github/dockyardmc/world/chunk/Chunk.kt +++ b/src/main/kotlin/io/github/dockyardmc/world/chunk/Chunk.kt @@ -53,7 +53,7 @@ class Chunk(val chunkX: Int, val chunkZ: Int, val world: World) : Viewable() { val lightPacket: ClientboundUpdateLightPacket get() { - if(!this::cachedLightPacket.isInitialized) updateLight() + if (!this::cachedLightPacket.isInitialized) updateLightOnly() return cachedLightPacket } @@ -81,17 +81,10 @@ class Chunk(val chunkX: Int, val chunkZ: Int, val world: World) : Viewable() { sendUpdateToViewers() } - fun updateLight() { + fun updateLightOnly() { lightEngine.recalculateChunk() cachedLightPacket = ClientboundUpdateLightPacket(chunkX, chunkZ, Light(lightEngine)) - } - - init { - val sectionsAmount = maxSection - minSection - repeat(sectionsAmount) { - sections.add(ChunkSection.empty()) - } - update() + sendUpdateToViewers() } fun setBlockRaw(x: Int, y: Int, z: Int, blockStateId: Int, shouldCache: Boolean = true) { From 21ec4c86549dbea13bd9cfc7861d4ad9ee17ff04 Mon Sep 17 00:00:00 2001 From: LukynkaCZE Date: Tue, 16 Sep 2025 21:49:13 +0200 Subject: [PATCH 6/8] convert stuff to data classes and use stream codecs --- .../io/github/dockyardmc/codec/ExtraCodecs.kt | 36 ++++++++ .../clientbound/ClientboundChunkDataPacket.kt | 82 +++++++++++-------- .../ClientboundUpdateLightPacket.kt | 26 ++++-- .../io/github/dockyardmc/world/Light.kt | 54 ------------ .../io/github/dockyardmc/world/LightData.kt | 41 ++++++++++ .../io/github/dockyardmc/world/LightEngine.kt | 38 +++++++++ .../dockyardmc/world/block/BlockEntity.kt | 25 ++++++ .../io/github/dockyardmc/world/chunk/Chunk.kt | 5 +- .../dockyardmc/world/chunk/ChunkSection.kt | 40 +++++++-- 9 files changed, 237 insertions(+), 110 deletions(-) create mode 100644 src/main/kotlin/io/github/dockyardmc/codec/ExtraCodecs.kt delete mode 100644 src/main/kotlin/io/github/dockyardmc/world/Light.kt create mode 100644 src/main/kotlin/io/github/dockyardmc/world/LightData.kt diff --git a/src/main/kotlin/io/github/dockyardmc/codec/ExtraCodecs.kt b/src/main/kotlin/io/github/dockyardmc/codec/ExtraCodecs.kt new file mode 100644 index 000000000..66d79e432 --- /dev/null +++ b/src/main/kotlin/io/github/dockyardmc/codec/ExtraCodecs.kt @@ -0,0 +1,36 @@ +package io.github.dockyardmc.codec + +import io.github.dockyardmc.extentions.readVarInt +import io.github.dockyardmc.extentions.writeVarInt +import io.github.dockyardmc.tide.codec.Codec +import io.github.dockyardmc.tide.stream.StreamCodec +import io.netty.buffer.ByteBuf + +object ExtraCodecs { + + object LongArray { + val STREAM = object : StreamCodec { + + override fun write(buffer: ByteBuf, value: kotlin.LongArray) { + buffer.writeVarInt(value.size) + value.forEach { buffer.writeLong(it) } + } + + override fun read(buffer: ByteBuf): kotlin.LongArray { + val size = buffer.readVarInt() + val longs = mutableListOf() + for (i in 0 until size) { + longs.add(buffer.readLong()) + } + return longs.toLongArray() + } + } + + val CODEC = Codec.LONG_ARRAY + } + + object BitSet { + val STREAM = LongArray.STREAM.transform(java.util.BitSet::toLongArray, java.util.BitSet::valueOf) + val CODEC = LongArray.CODEC.transform(java.util.BitSet::valueOf, java.util.BitSet::toLongArray) + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundChunkDataPacket.kt b/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundChunkDataPacket.kt index 80a9e5451..d069b4490 100644 --- a/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundChunkDataPacket.kt +++ b/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundChunkDataPacket.kt @@ -1,46 +1,58 @@ package io.github.dockyardmc.protocol.packets.play.clientbound -import io.github.dockyardmc.extentions.* +import io.github.dockyardmc.codec.ExtraCodecs import io.github.dockyardmc.protocol.packets.ClientboundPacket -import io.github.dockyardmc.protocol.types.writeMap -import io.github.dockyardmc.world.Light +import io.github.dockyardmc.tide.stream.StreamCodec +import io.github.dockyardmc.world.LightData import io.github.dockyardmc.world.block.BlockEntity import io.github.dockyardmc.world.chunk.ChunkHeightmap import io.github.dockyardmc.world.chunk.ChunkSection -import io.github.dockyardmc.world.chunk.ChunkUtils -import io.netty.buffer.ByteBuf -import io.netty.buffer.Unpooled -import it.unimi.dsi.fastutil.objects.ObjectCollection -class ClientboundChunkDataPacket(x: Int, z: Int, heightmaps: Map, sections: MutableList, blockEntities: ObjectCollection, light: Light) : ClientboundPacket() { +data class ClientboundChunkDataPacket(val x: Int, val z: Int, val heightmaps: Map, val sections: List, val blockEntities: List, val light: LightData) : ClientboundPacket() { + + companion object { + val STREAM_CODEC = StreamCodec.of( + StreamCodec.INT, ClientboundChunkDataPacket::x, + StreamCodec.INT, ClientboundChunkDataPacket::z, + StreamCodec.enum().mapTo(ExtraCodecs.LongArray.STREAM), ClientboundChunkDataPacket::heightmaps, + ChunkSection.BYTE_ARRAY_STREAM_CODEC, ClientboundChunkDataPacket::sections, + BlockEntity.STREAM_CODEC.list(), ClientboundChunkDataPacket::blockEntities, + LightData.STREAM_CODEC, ClientboundChunkDataPacket::light, + ::ClientboundChunkDataPacket + ) + } init { - //X Z - buffer.writeInt(x) - buffer.writeInt(z) - - //Heightmaps - buffer.writeMap>(heightmaps.mapValues { map -> map.value.toList() }, ByteBuf::writeEnum, ByteBuf::writeLongArray) - - //Chunk Sections - val chunkSectionData = Unpooled.buffer() - sections.forEach { section -> - section.write(chunkSectionData) - } - buffer.writeByteArray(chunkSectionData.toByteArraySafe()) - - //Block Entities - buffer.writeVarInt(blockEntities.size) - blockEntities.forEach { blockEntity -> - val id = blockEntity.blockEntityTypeId - val point = ChunkUtils.chunkBlockIndexGetGlobal(blockEntity.positionIndex, 0, 0) - - buffer.writeByte(((point.x and 15) shl 4 or (point.z and 15))) - buffer.writeShort(point.y) - buffer.writeVarInt(id) - buffer.writeNBT(blockEntity.data) - } - - light.write(buffer) + STREAM_CODEC.write(buffer, this) } + +// init { +// //X Z +// buffer.writeInt(x) +// buffer.writeInt(z) +// +// //Heightmaps +// buffer.writeMap>(heightmaps.mapValues { map -> map.value.toList() }, ByteBuf::writeEnum, ByteBuf::writeLongArray) +// +// //Chunk Sections +// val chunkSectionData = Unpooled.buffer() +// sections.forEach { section -> +// section.write(chunkSectionData) +// } +// buffer.writeByteArray(chunkSectionData.toByteArraySafe()) +// +// //Block Entities +// buffer.writeVarInt(blockEntities.size) +// blockEntities.forEach { blockEntity -> +// val id = blockEntity.blockEntityTypeId +// val point = ChunkUtils.chunkBlockIndexGetGlobal(blockEntity.positionIndex, 0, 0) +// +// buffer.writeByte(((point.x and 15) shl 4 or (point.z and 15))) +// buffer.writeShort(point.y) +// buffer.writeVarInt(id) +// buffer.writeNBT(blockEntity.data) +// } +// +// light.write(buffer) +// } } \ No newline at end of file diff --git a/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundUpdateLightPacket.kt b/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundUpdateLightPacket.kt index f8504887e..f324a55cc 100644 --- a/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundUpdateLightPacket.kt +++ b/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundUpdateLightPacket.kt @@ -1,17 +1,25 @@ package io.github.dockyardmc.protocol.packets.play.clientbound -import io.github.dockyardmc.extentions.writeVarInt import io.github.dockyardmc.protocol.packets.ClientboundPacket -import io.github.dockyardmc.world.Light +import io.github.dockyardmc.tide.stream.StreamCodec +import io.github.dockyardmc.world.LightData -class ClientboundUpdateLightPacket( - chunkX: Int, - chunkZ: Int, - light: Light +data class ClientboundUpdateLightPacket( + val chunkX: Int, + val chunkZ: Int, + val light: LightData ) : ClientboundPacket() { + + companion object { + val STREAM_CODEC = StreamCodec.of( + StreamCodec.VAR_INT, ClientboundUpdateLightPacket::chunkX, + StreamCodec.VAR_INT, ClientboundUpdateLightPacket::chunkZ, + LightData.STREAM_CODEC, ClientboundUpdateLightPacket::light, + ::ClientboundUpdateLightPacket + ) + } + init { - buffer.writeVarInt(chunkX) - buffer.writeVarInt(chunkZ) - light.write(buffer) + STREAM_CODEC.write(buffer, this) } } \ No newline at end of file diff --git a/src/main/kotlin/io/github/dockyardmc/world/Light.kt b/src/main/kotlin/io/github/dockyardmc/world/Light.kt deleted file mode 100644 index 029e922f3..000000000 --- a/src/main/kotlin/io/github/dockyardmc/world/Light.kt +++ /dev/null @@ -1,54 +0,0 @@ -package io.github.dockyardmc.world - -import io.github.dockyardmc.extentions.writeByteArray -import io.github.dockyardmc.extentions.writeLongArray -import io.github.dockyardmc.protocol.NetworkWritable -import io.github.dockyardmc.protocol.types.writeList -import io.netty.buffer.ByteBuf -import java.util.* - -class Light( - val lightEngine: LightEngine -) : NetworkWritable { - val skyMask: BitSet = BitSet() - val blockMask: BitSet = BitSet() - val emptySkyMask: BitSet = BitSet() - val emptyBlockMask: BitSet = BitSet() - val skyLight: MutableList = mutableListOf() - val blockLight: MutableList = mutableListOf() - - init { - // first section is below the world. how awesome. we love minecraft - emptySkyMask.set(0) - emptyBlockMask.set(0) - - // last section is one section above the world. why. - emptySkyMask.set(skyLight.size + 1) - emptyBlockMask.set(skyLight.size + 1) - - lightEngine.skyLight.indices.forEach { i -> - if (lightEngine.hasNonZeroData(lightEngine.skyLight[i])) { - skyMask.set(i + 1) - skyLight.add(lightEngine.skyLight[i]) - } else { - emptySkyMask.set(i + 1) - } - - if (lightEngine.hasNonZeroData(lightEngine.blockLight[i])) { - blockMask.set(i + 1) - blockLight.add(lightEngine.blockLight[i]) - } else { - emptyBlockMask.set(i + 1) - } - } - } - - override fun write(buffer: ByteBuf) { - buffer.writeLongArray(skyMask.toLongArray().toList()) - buffer.writeLongArray(blockMask.toLongArray().toList()) - buffer.writeLongArray(emptySkyMask.toLongArray().toList()) - buffer.writeLongArray(emptyBlockMask.toLongArray().toList()) - buffer.writeList(skyLight, ByteBuf::writeByteArray) - buffer.writeList(blockLight, ByteBuf::writeByteArray) - } -} diff --git a/src/main/kotlin/io/github/dockyardmc/world/LightData.kt b/src/main/kotlin/io/github/dockyardmc/world/LightData.kt new file mode 100644 index 000000000..174a3b567 --- /dev/null +++ b/src/main/kotlin/io/github/dockyardmc/world/LightData.kt @@ -0,0 +1,41 @@ +package io.github.dockyardmc.world + +import io.github.dockyardmc.codec.ExtraCodecs +import io.github.dockyardmc.tide.codec.Codec +import io.github.dockyardmc.tide.codec.StructCodec +import io.github.dockyardmc.tide.stream.StreamCodec +import io.netty.buffer.ByteBuf +import java.util.* + +data class LightData( + val skyMask: BitSet = BitSet(), + val blockMask: BitSet = BitSet(), + val emptySkyMask: BitSet = BitSet(), + val emptyBlockMask: BitSet = BitSet(), + val skyLight: List = mutableListOf(), + val blockLight: List = mutableListOf() +) { + + companion object { + + val STREAM_CODEC = StreamCodec.of( + ExtraCodecs.BitSet.STREAM, LightData::skyMask, + ExtraCodecs.BitSet.STREAM, LightData::blockMask, + ExtraCodecs.BitSet.STREAM, LightData::emptySkyMask, + ExtraCodecs.BitSet.STREAM, LightData::emptyBlockMask, + StreamCodec.BYTE_ARRAY.list(), LightData::skyLight, + StreamCodec.BYTE_ARRAY.list(), LightData::blockLight, + ::LightData + ) + + val CODEC = StructCodec.of( + "sky_mask", ExtraCodecs.BitSet.CODEC, LightData::skyMask, + "block_mask", ExtraCodecs.BitSet.CODEC, LightData::blockMask, + "empty_sky_mask", ExtraCodecs.BitSet.CODEC, LightData::emptySkyMask, + "empty_block_mask", ExtraCodecs.BitSet.CODEC, LightData::emptyBlockMask, + "sky_light", Codec.BYTE_BUFFER.list(), LightData::skyLight, + "block_light", Codec.BYTE_BUFFER.list(), LightData::blockLight, + ::LightData + ) + } +} diff --git a/src/main/kotlin/io/github/dockyardmc/world/LightEngine.kt b/src/main/kotlin/io/github/dockyardmc/world/LightEngine.kt index 148385eb9..adac4dc67 100644 --- a/src/main/kotlin/io/github/dockyardmc/world/LightEngine.kt +++ b/src/main/kotlin/io/github/dockyardmc/world/LightEngine.kt @@ -1,9 +1,11 @@ package io.github.dockyardmc.world +import io.github.dockyardmc.extentions.toByteBuf import io.github.dockyardmc.maths.vectors.Vector3 import io.github.dockyardmc.world.block.Block import io.github.dockyardmc.world.chunk.Chunk import io.github.dockyardmc.world.chunk.ChunkSection +import io.netty.buffer.ByteBuf import java.util.* class LightEngine( @@ -18,6 +20,42 @@ class LightEngine( val skyLight: Array = Array(chunk.maxSection - chunk.minSection) { ByteArray(0) } val blockLight: Array = Array(chunk.maxSection - chunk.minSection) { ByteArray(0) } + + fun createLightData(): LightData { + val skyMask = BitSet() + val blockMask = BitSet() + val emptySkyMask = BitSet() + val emptyBlockMask = BitSet() + val skyLight = mutableListOf() + val blockLight = mutableListOf() + + // first section is below the world. how awesome. we love minecraft + emptySkyMask.set(0) + emptyBlockMask.set(0) + + // last section is one section above the world. why. + emptySkyMask.set(this.skyLight.size + 1) + emptyBlockMask.set(this.skyLight.size + 1) + + this.skyLight.indices.forEach { i -> + if (this.hasNonZeroData(this.skyLight[i])) { + skyMask.set(i + 1) + skyLight.add(this.skyLight[i].toByteBuf()) + } else { + emptySkyMask.set(i + 1) + } + + if (this.hasNonZeroData(this.blockLight[i])) { + blockMask.set(i + 1) + blockLight.add(this.blockLight[i].toByteBuf()) + } else { + emptyBlockMask.set(i + 1) + } + } + + return LightData(skyMask, blockMask, emptySkyMask, emptyBlockMask, skyLight, blockLight) + } + fun recalculateChunk() { chunk.sections.forEachIndexed { i, section -> recalculateSection(section, i) diff --git a/src/main/kotlin/io/github/dockyardmc/world/block/BlockEntity.kt b/src/main/kotlin/io/github/dockyardmc/world/block/BlockEntity.kt index 0b51e099d..036130224 100644 --- a/src/main/kotlin/io/github/dockyardmc/world/block/BlockEntity.kt +++ b/src/main/kotlin/io/github/dockyardmc/world/block/BlockEntity.kt @@ -1,6 +1,11 @@ package io.github.dockyardmc.world.block +import io.github.dockyardmc.extentions.writeNBT +import io.github.dockyardmc.extentions.writeVarInt import io.github.dockyardmc.registry.registries.RegistryBlock +import io.github.dockyardmc.tide.stream.StreamCodec +import io.github.dockyardmc.world.chunk.ChunkUtils +import io.netty.buffer.ByteBuf import net.kyori.adventure.nbt.CompoundBinaryTag data class BlockEntity( @@ -9,4 +14,24 @@ data class BlockEntity( val data: CompoundBinaryTag, ) { val blockEntityTypeId get() = block.blockEntityId!! + + companion object { + val STREAM_CODEC = object : StreamCodec { + + override fun write(buffer: ByteBuf, value: BlockEntity) { + val id = value.blockEntityTypeId + val point = ChunkUtils.chunkBlockIndexGetGlobal(value.positionIndex, 0, 0) + + buffer.writeByte(((point.x and 15) shl 4 or (point.z and 15))) + buffer.writeShort(point.y) + buffer.writeVarInt(id) + buffer.writeNBT(value.data) + + } + + override fun read(buffer: ByteBuf): BlockEntity { + throw UnsupportedOperationException() + } + } + } } \ No newline at end of file diff --git a/src/main/kotlin/io/github/dockyardmc/world/chunk/Chunk.kt b/src/main/kotlin/io/github/dockyardmc/world/chunk/Chunk.kt index 3e9b3026f..abebc1095 100644 --- a/src/main/kotlin/io/github/dockyardmc/world/chunk/Chunk.kt +++ b/src/main/kotlin/io/github/dockyardmc/world/chunk/Chunk.kt @@ -9,7 +9,6 @@ import io.github.dockyardmc.protocol.packets.play.clientbound.ClientboundUpdateL import io.github.dockyardmc.registry.registries.Biome import io.github.dockyardmc.registry.registries.RegistryBlock import io.github.dockyardmc.utils.viewable.Viewable -import io.github.dockyardmc.world.Light import io.github.dockyardmc.world.LightEngine import io.github.dockyardmc.world.World import io.github.dockyardmc.world.block.Block @@ -77,13 +76,13 @@ class Chunk(val chunkX: Int, val chunkZ: Int, val world: World) : Viewable() { heightmapData[type] = heightmap.getRawData() } - cachedPacket = ClientboundChunkDataPacket(chunkX, chunkZ, heightmapData, sections, blockEntities.values, Light(lightEngine)) + cachedPacket = ClientboundChunkDataPacket(chunkX, chunkZ, heightmapData, sections, blockEntities.values.toList(), lightEngine.createLightData()) sendUpdateToViewers() } fun updateLightOnly() { lightEngine.recalculateChunk() - cachedLightPacket = ClientboundUpdateLightPacket(chunkX, chunkZ, Light(lightEngine)) + cachedLightPacket = ClientboundUpdateLightPacket(chunkX, chunkZ, lightEngine.createLightData()) sendUpdateToViewers() } diff --git a/src/main/kotlin/io/github/dockyardmc/world/chunk/ChunkSection.kt b/src/main/kotlin/io/github/dockyardmc/world/chunk/ChunkSection.kt index 9a9d2f486..455dddc2b 100644 --- a/src/main/kotlin/io/github/dockyardmc/world/chunk/ChunkSection.kt +++ b/src/main/kotlin/io/github/dockyardmc/world/chunk/ChunkSection.kt @@ -1,17 +1,18 @@ package io.github.dockyardmc.world.chunk -import io.github.dockyardmc.protocol.NetworkWritable import io.github.dockyardmc.registry.Biomes import io.github.dockyardmc.registry.Blocks +import io.github.dockyardmc.tide.stream.StreamCodec import io.github.dockyardmc.world.palette.Palette import io.github.dockyardmc.world.palette.writePalette import io.netty.buffer.ByteBuf +import io.netty.buffer.Unpooled -class ChunkSection( +data class ChunkSection( private var blockPalette: Palette, private var biomePalette: Palette, +) { - ) : NetworkWritable { var nonEmptyBlockCount: Int = 0 init { @@ -61,6 +62,33 @@ class ChunkSection( } companion object { + + val BYTE_ARRAY_STREAM_CODEC = StreamCodec.BYTE_ARRAY.transform>( + { from -> + val innerBuffer = Unpooled.buffer() + from.forEach { section -> + STREAM_CODEC.write(innerBuffer, section) + } + innerBuffer + }, + { _ -> + throw UnsupportedOperationException() + } + ) + + val STREAM_CODEC = object : StreamCodec { + + override fun write(buffer: ByteBuf, value: ChunkSection) { + buffer.writeShort(value.blockPalette.count()) + buffer.writePalette(value.blockPalette) + buffer.writePalette(value.biomePalette) + } + + override fun read(buffer: ByteBuf): ChunkSection { + throw UnsupportedOperationException() + } + } + fun empty(): ChunkSection { val defaultBlocks = Palette.blocks() val defaultBiomes = Palette.biomes() @@ -69,10 +97,4 @@ class ChunkSection( return ChunkSection(defaultBlocks, defaultBiomes) } } - - override fun write(buffer: ByteBuf) { - buffer.writeShort(this.blockPalette.count()) - buffer.writePalette(this.blockPalette) - buffer.writePalette(this.biomePalette) - } } \ No newline at end of file From 1985814264cf19070d60aceeaa6be8601bd65aca Mon Sep 17 00:00:00 2001 From: LukynkaCZE Date: Tue, 16 Sep 2025 22:11:36 +0200 Subject: [PATCH 7/8] update tide --- build.gradle.kts | 2 +- .../io/github/dockyardmc/codec/ExtraCodecs.kt | 29 ++-------------- .../clientbound/ClientboundChunkDataPacket.kt | 33 +------------------ 3 files changed, 5 insertions(+), 59 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 44287ca7e..3ed0ec8fb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -56,7 +56,7 @@ dependencies { // Networking api("io.ktor:ktor-server-netty:3.1.2") - api("io.github.dockyardmc:tide:3.7") + api("io.github.dockyardmc:tide:3.8") api("io.github.dockyardmc:bytesocks-client-java:1.0-SNAPSHOT") { exclude(module = "slf4j-api") } diff --git a/src/main/kotlin/io/github/dockyardmc/codec/ExtraCodecs.kt b/src/main/kotlin/io/github/dockyardmc/codec/ExtraCodecs.kt index 66d79e432..3652b8aab 100644 --- a/src/main/kotlin/io/github/dockyardmc/codec/ExtraCodecs.kt +++ b/src/main/kotlin/io/github/dockyardmc/codec/ExtraCodecs.kt @@ -1,36 +1,13 @@ package io.github.dockyardmc.codec -import io.github.dockyardmc.extentions.readVarInt -import io.github.dockyardmc.extentions.writeVarInt import io.github.dockyardmc.tide.codec.Codec import io.github.dockyardmc.tide.stream.StreamCodec -import io.netty.buffer.ByteBuf object ExtraCodecs { - object LongArray { - val STREAM = object : StreamCodec { - - override fun write(buffer: ByteBuf, value: kotlin.LongArray) { - buffer.writeVarInt(value.size) - value.forEach { buffer.writeLong(it) } - } - - override fun read(buffer: ByteBuf): kotlin.LongArray { - val size = buffer.readVarInt() - val longs = mutableListOf() - for (i in 0 until size) { - longs.add(buffer.readLong()) - } - return longs.toLongArray() - } - } - - val CODEC = Codec.LONG_ARRAY - } - object BitSet { - val STREAM = LongArray.STREAM.transform(java.util.BitSet::toLongArray, java.util.BitSet::valueOf) - val CODEC = LongArray.CODEC.transform(java.util.BitSet::valueOf, java.util.BitSet::toLongArray) + val STREAM = StreamCodec.LONG_ARRAY.transform(java.util.BitSet::toLongArray, java.util.BitSet::valueOf) + val CODEC = Codec.LONG_ARRAY.transform(java.util.BitSet::valueOf, java.util.BitSet::toLongArray) } + } \ No newline at end of file diff --git a/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundChunkDataPacket.kt b/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundChunkDataPacket.kt index d069b4490..4b9a56b99 100644 --- a/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundChunkDataPacket.kt +++ b/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundChunkDataPacket.kt @@ -1,6 +1,5 @@ package io.github.dockyardmc.protocol.packets.play.clientbound -import io.github.dockyardmc.codec.ExtraCodecs import io.github.dockyardmc.protocol.packets.ClientboundPacket import io.github.dockyardmc.tide.stream.StreamCodec import io.github.dockyardmc.world.LightData @@ -14,7 +13,7 @@ data class ClientboundChunkDataPacket(val x: Int, val z: Int, val heightmaps: Ma val STREAM_CODEC = StreamCodec.of( StreamCodec.INT, ClientboundChunkDataPacket::x, StreamCodec.INT, ClientboundChunkDataPacket::z, - StreamCodec.enum().mapTo(ExtraCodecs.LongArray.STREAM), ClientboundChunkDataPacket::heightmaps, + StreamCodec.enum().mapTo(StreamCodec.LONG_ARRAY), ClientboundChunkDataPacket::heightmaps, ChunkSection.BYTE_ARRAY_STREAM_CODEC, ClientboundChunkDataPacket::sections, BlockEntity.STREAM_CODEC.list(), ClientboundChunkDataPacket::blockEntities, LightData.STREAM_CODEC, ClientboundChunkDataPacket::light, @@ -25,34 +24,4 @@ data class ClientboundChunkDataPacket(val x: Int, val z: Int, val heightmaps: Ma init { STREAM_CODEC.write(buffer, this) } - -// init { -// //X Z -// buffer.writeInt(x) -// buffer.writeInt(z) -// -// //Heightmaps -// buffer.writeMap>(heightmaps.mapValues { map -> map.value.toList() }, ByteBuf::writeEnum, ByteBuf::writeLongArray) -// -// //Chunk Sections -// val chunkSectionData = Unpooled.buffer() -// sections.forEach { section -> -// section.write(chunkSectionData) -// } -// buffer.writeByteArray(chunkSectionData.toByteArraySafe()) -// -// //Block Entities -// buffer.writeVarInt(blockEntities.size) -// blockEntities.forEach { blockEntity -> -// val id = blockEntity.blockEntityTypeId -// val point = ChunkUtils.chunkBlockIndexGetGlobal(blockEntity.positionIndex, 0, 0) -// -// buffer.writeByte(((point.x and 15) shl 4 or (point.z and 15))) -// buffer.writeShort(point.y) -// buffer.writeVarInt(id) -// buffer.writeNBT(blockEntity.data) -// } -// -// light.write(buffer) -// } } \ No newline at end of file From c7672eb0e38e61eccbbeb053ac2a82eb06277ed5 Mon Sep 17 00:00:00 2001 From: LukynkaCZE Date: Wed, 1 Oct 2025 19:21:05 +0200 Subject: [PATCH 8/8] chunk pos --- .../plugin/LoginPluginMessageHandler.kt | 1 + .../io/github/dockyardmc/world/LightData.kt | 1 - .../io/github/dockyardmc/world/LightEngine.kt | 21 +++++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/io/github/dockyardmc/protocol/plugin/LoginPluginMessageHandler.kt b/src/main/kotlin/io/github/dockyardmc/protocol/plugin/LoginPluginMessageHandler.kt index ab5bab87b..96ab341b5 100644 --- a/src/main/kotlin/io/github/dockyardmc/protocol/plugin/LoginPluginMessageHandler.kt +++ b/src/main/kotlin/io/github/dockyardmc/protocol/plugin/LoginPluginMessageHandler.kt @@ -11,6 +11,7 @@ import java.util.concurrent.CompletableFuture import java.util.concurrent.atomic.AtomicInteger class LoginPluginMessageHandler(val networkManager: PlayerNetworkManager) { + companion object { val REQUEST_ID = AtomicInteger(0) } diff --git a/src/main/kotlin/io/github/dockyardmc/world/LightData.kt b/src/main/kotlin/io/github/dockyardmc/world/LightData.kt index 174a3b567..aed19ece2 100644 --- a/src/main/kotlin/io/github/dockyardmc/world/LightData.kt +++ b/src/main/kotlin/io/github/dockyardmc/world/LightData.kt @@ -15,7 +15,6 @@ data class LightData( val skyLight: List = mutableListOf(), val blockLight: List = mutableListOf() ) { - companion object { val STREAM_CODEC = StreamCodec.of( diff --git a/src/main/kotlin/io/github/dockyardmc/world/LightEngine.kt b/src/main/kotlin/io/github/dockyardmc/world/LightEngine.kt index adac4dc67..4655eaf6a 100644 --- a/src/main/kotlin/io/github/dockyardmc/world/LightEngine.kt +++ b/src/main/kotlin/io/github/dockyardmc/world/LightEngine.kt @@ -4,6 +4,7 @@ import io.github.dockyardmc.extentions.toByteBuf import io.github.dockyardmc.maths.vectors.Vector3 import io.github.dockyardmc.world.block.Block import io.github.dockyardmc.world.chunk.Chunk +import io.github.dockyardmc.world.chunk.ChunkPos import io.github.dockyardmc.world.chunk.ChunkSection import io.netty.buffer.ByteBuf import java.util.* @@ -133,6 +134,7 @@ class LightEngine( val neighborX = x + dX val neighborY = y + dY val neighborZ = z + dZ + if (neighborX in 0..15 && neighborY in 0..15 && neighborZ in 0..15) { val neighborBlockId = section.getBlock(neighborX, neighborY, neighborZ) val neighborBlock = Block.getBlockByStateId(neighborBlockId).registryBlock @@ -145,11 +147,30 @@ class LightEngine( queue.add(Vector3(neighborX, neighborY, neighborZ)) } } + } else { + propagateToNeighborChunk(neighborX, neighborY, neighborZ, newLightLevel, sectionIndex) } } } } + private fun propagateToNeighborChunk(x: Int, y: Int, z: Int, lightLevel: Int, sectionIndex: Int) { + val neighborChunk = when { + x < 0 -> chunk.world.getChunk(ChunkPos(chunk.chunkX - 1, chunk.chunkZ)) + x > 15 -> chunk.world.getChunk(ChunkPos(chunk.chunkX + 1, chunk.chunkZ)) + z < 0 -> chunk.world.getChunk(ChunkPos(chunk.chunkX, chunk.chunkZ - 1)) + z > 15 -> chunk.world.getChunk(ChunkPos(chunk.chunkX, chunk.chunkZ + 1)) + else -> return // shouldn't happen + } ?: return + + val neighborLight = neighborChunk.lightEngine +// neighborLight[x, y, z] = lightLevel + +// neighborLight.recalculateChunk() + // Just tell the neighbor to recalculate this section +// neighborChunk.lightEngine?.recalculateSection(neighborChunk.sections[sectionIndex], sectionIndex) + } + operator fun set(x: Int, y: Int, z: Int, value: Int) { this[x or (z shl 4) or (y shl 8)] = value }