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 new file mode 100644 index 000000000..3652b8aab --- /dev/null +++ b/src/main/kotlin/io/github/dockyardmc/codec/ExtraCodecs.kt @@ -0,0 +1,13 @@ +package io.github.dockyardmc.codec + +import io.github.dockyardmc.tide.codec.Codec +import io.github.dockyardmc.tide.stream.StreamCodec + +object ExtraCodecs { + + object BitSet { + 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/entity/Entity.kt b/src/main/kotlin/io/github/dockyardmc/entity/Entity.kt index 9ecebc427..03bf3b92c 100644 --- a/src/main/kotlin/io/github/dockyardmc/entity/Entity.kt +++ b/src/main/kotlin/io/github/dockyardmc/entity/Entity.kt @@ -94,6 +94,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 @@ -126,14 +128,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 5af30baf6..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,55 +1,27 @@ package io.github.dockyardmc.protocol.packets.play.clientbound -import io.github.dockyardmc.extentions.* import io.github.dockyardmc.protocol.packets.ClientboundPacket -import io.github.dockyardmc.protocol.types.writeList -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(StreamCodec.LONG_ARRAY), 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 stuff - buffer.writeLongArray(light.skyMask.toLongArray().toList()) - buffer.writeLongArray(light.blockMask.toLongArray().toList()) - - buffer.writeLongArray(light.emptySkyMask.toLongArray().toList()) - buffer.writeLongArray(light.emptyBlockMask.toLongArray().toList()) - - buffer.writeList(light.skyLight, ByteBuf::writeByteArray) - buffer.writeList(light.blockLight, ByteBuf::writeByteArray) + STREAM_CODEC.write(buffer, this) } } \ 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..f324a55cc --- /dev/null +++ b/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundUpdateLightPacket.kt @@ -0,0 +1,25 @@ +package io.github.dockyardmc.protocol.packets.play.clientbound + +import io.github.dockyardmc.protocol.packets.ClientboundPacket +import io.github.dockyardmc.tide.stream.StreamCodec +import io.github.dockyardmc.world.LightData + +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 { + STREAM_CODEC.write(buffer, this) + } +} \ 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 3b2a24dbf..18f08070d 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 @@ -81,7 +81,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/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 new file mode 100644 index 000000000..aed19ece2 --- /dev/null +++ b/src/main/kotlin/io/github/dockyardmc/world/LightData.kt @@ -0,0 +1,40 @@ +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 new file mode 100644 index 000000000..4655eaf6a --- /dev/null +++ b/src/main/kotlin/io/github/dockyardmc/world/LightEngine.kt @@ -0,0 +1,201 @@ +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.ChunkPos +import io.github.dockyardmc.world.chunk.ChunkSection +import io.netty.buffer.ByteBuf +import java.util.* + +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 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) + } + } + + 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 = 0 +// var light = 15 + + foundSolid = foundSolid || section.getBlock(x, y, z) != 0 + + if (foundSolid) { + light = 0 + } + + set(x, y, z, light) + } + } + } + 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)) + } + } + } 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 + } + + // 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() + } + + 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() } + } +} 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/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/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 3cd0d7bd7..abebc1095 100644 --- a/src/main/kotlin/io/github/dockyardmc/world/chunk/Chunk.kt +++ b/src/main/kotlin/io/github/dockyardmc/world/chunk/Chunk.kt @@ -5,10 +5,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.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 import io.github.dockyardmc.world.block.BlockEntity @@ -26,9 +27,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 +42,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 +50,12 @@ class Chunk(val chunkX: Int, val chunkZ: Int, val world: World) : Viewable() { return cachedPacket } + val lightPacket: ClientboundUpdateLightPacket + get() { + if (!this::cachedLightPacket.isInitialized) updateLightOnly() + return cachedLightPacket + } + init { val sectionsAmount = maxSection - minSection repeat(sectionsAmount) { @@ -67,16 +76,14 @@ 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) + cachedPacket = ClientboundChunkDataPacket(chunkX, chunkZ, heightmapData, sections, blockEntities.values.toList(), lightEngine.createLightData()) sendUpdateToViewers() } - init { - val sectionsAmount = maxSection - minSection - repeat(sectionsAmount) { - sections.add(ChunkSection.empty()) - } - update() + fun updateLightOnly() { + lightEngine.recalculateChunk() + cachedLightPacket = ClientboundUpdateLightPacket(chunkX, chunkZ, lightEngine.createLightData()) + sendUpdateToViewers() } fun setBlockRaw(x: Int, y: Int, z: Int, blockStateId: Int, shouldCache: Boolean = true) { @@ -215,11 +222,13 @@ class Chunk(val chunkX: Int, val chunkZ: Int, val world: World) : Viewable() { private 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 } 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 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