Archived
1
0
Fork 0

Start working on ChunkDataPacketCodec

This commit is contained in:
Moritz Ruth 2020-12-13 18:26:18 +01:00
parent 5aeea48516
commit 63b676f062
6 changed files with 173 additions and 5 deletions

View file

@ -14,10 +14,28 @@ data class ChunkData(
/**
* 1024 biome IDs (one for each 4x4x4 area), ordered in this order: X, Z, Y.
*/
val biomes: Array<Biome>
val biomes: Array<Biome>,
/**
* An array (first x, then z) containing the y coordinates of the highest **solid** block in every column.
* Will be computed using [sections] if null.
*/
val solidBlocksHeightmap: ByteArray? = null,
/**
* An array (first x, then z) containing the y coordinates of the highest block in every column which is
* neither [Air][space.blokk.world.block.Air] nor [CaveAir][space.blokk.world.block.CaveAir].
* Will be computed using [sections] if null.
*/
val nonAirBlocksHeightmap: ByteArray? = null
// TODO: Add options for specifying how the encoded packet should be cached.
) {
companion object {
const val BIOME_AREAS_IN_CHUNK = 1024
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
@ -26,6 +44,10 @@ data class ChunkData(
if (!sections.contentDeepEquals(other.sections)) return false
if (!biomes.contentEquals(other.biomes)) return false
if (solidBlocksHeightmap != null) {
if (other.solidBlocksHeightmap == null) return false
if (!solidBlocksHeightmap.contentEquals(other.solidBlocksHeightmap)) return false
} else if (other.solidBlocksHeightmap != null) return false
return true
}
@ -33,10 +55,7 @@ data class ChunkData(
override fun hashCode(): Int {
var result = sections.contentDeepHashCode()
result = 31 * result + biomes.contentHashCode()
result = 31 * result + (solidBlocksHeightmap?.contentHashCode() ?: 0)
return result
}
companion object {
const val BIOME_AREAS_IN_CHUNK = 1024
}
}

View file

@ -17,6 +17,7 @@ val guavaVersion = properties["version.guava"].toString()
dependencies {
api(project(":blokk-packets"))
api(project(":blokk-nbt"))
// Netty
api("io.netty:netty-buffer:${nettyVersion}")

View file

@ -0,0 +1,38 @@
package space.blokk.net.packet.play
import io.netty.buffer.ByteBuf
import space.blokk.nbt.NBT
import space.blokk.net.MinecraftProtocolDataTypes.writeVarInt
import space.blokk.net.packet.OutgoingPacketCodec
import space.blokk.util.generateHeightmap
import space.blokk.util.toCompactLongArray
import space.blokk.world.block.Air
import space.blokk.world.block.CaveAir
import space.blokk.world.block.Material
object ChunkDataPacketCodec : OutgoingPacketCodec<ChunkDataPacket>(0x22, ChunkDataPacket::class) {
private val nonAirHeightmapIgnoredBlocks = setOf(Air::class, CaveAir::class)
private val solidHeightmapIgnoredBlocks = Material.values().filter {
// TODO
true
}
override fun ChunkDataPacket.encode(dst: ByteBuf) {
dst.writeInt(key.x)
dst.writeInt(key.z)
dst.writeBoolean(true) // Full Chunk
var includedSections = 0
data.sections.forEachIndexed { index, value ->
if (value != null) includedSections = includedSections and (1 shl index)
}
dst.writeVarInt(includedSections)
val heightmap = data.solidBlocksHeightmap ?: generateHeightmap(data.sections, listOf())
val heightmaps = NBT()
heightmaps.set("MOTION_BLOCKING", heightmap.toCompactLongArray(9))
heightmaps.set("MOTION_BLOCKING", heightmap.toCompactLongArray(9))
}
}

View file

@ -0,0 +1,70 @@
package space.blokk.util
/**
* Taken from Minestom (https://git.io/JIFbN)
* Original license: Apache License 2.0
*
* Changes: Translated to Kotlin
*/
private val MAGIC = intArrayOf(
-1, -1, 0, Int.MIN_VALUE, 0, 0, 1431655765, 1431655765, 0, Int.MIN_VALUE,
0, 1, 858993459, 858993459, 0, 715827882, 715827882, 0, 613566756, 613566756,
0, Int.MIN_VALUE, 0, 2, 477218588, 477218588, 0, 429496729, 429496729, 0,
390451572, 390451572, 0, 357913941, 357913941, 0, 330382099, 330382099, 0, 306783378,
306783378, 0, 286331153, 286331153, 0, Int.MIN_VALUE, 0, 3, 252645135, 252645135,
0, 238609294, 238609294, 0, 226050910, 226050910, 0, 214748364, 214748364, 0,
204522252, 204522252, 0, 195225786, 195225786, 0, 186737708, 186737708, 0, 178956970,
178956970, 0, 171798691, 171798691, 0, 165191049, 165191049, 0, 159072862, 159072862,
0, 153391689, 153391689, 0, 148102320, 148102320, 0, 143165576, 143165576, 0,
138547332, 138547332, 0, Int.MIN_VALUE, 0, 4, 130150524, 130150524, 0, 126322567,
126322567, 0, 122713351, 122713351, 0, 119304647, 119304647, 0, 116080197, 116080197,
0, 113025455, 113025455, 0, 110127366, 110127366, 0, 107374182, 107374182, 0,
104755299, 104755299, 0, 102261126, 102261126, 0, 99882960, 99882960, 0, 97612893,
97612893, 0, 95443717, 95443717, 0, 93368854, 93368854, 0, 91382282, 91382282,
0, 89478485, 89478485, 0, 87652393, 87652393, 0, 85899345, 85899345, 0,
84215045, 84215045, 0, 82595524, 82595524, 0, 81037118, 81037118, 0, 79536431,
79536431, 0, 78090314, 78090314, 0, 76695844, 76695844, 0, 75350303, 75350303,
0, 74051160, 74051160, 0, 72796055, 72796055, 0, 71582788, 71582788, 0,
70409299, 70409299, 0, 69273666, 69273666, 0, 68174084, 68174084, 0, Int.MIN_VALUE,
0, 5
)
fun IntArray.toCompactLongArray(bitsPerEntry: Int): LongArray =
toCompactLongArray(indices, this::get, size, bitsPerEntry)
fun ByteArray.toCompactLongArray(bitsPerEntry: Int): LongArray =
toCompactLongArray(indices, this::get, size, bitsPerEntry)
private fun toCompactLongArray(
indices: IntRange,
getValue: (index: Int) -> Any,
size: Int,
bitsPerEntry: Int
): LongArray {
val maxEntryValue = (1L shl bitsPerEntry) - 1
val valuesPerLong = (64 / bitsPerEntry)
val magicIndex = 3 * (valuesPerLong - 1)
val divideMul = Integer.toUnsignedLong(MAGIC[magicIndex])
val divideAdd = Integer.toUnsignedLong(MAGIC[magicIndex + 1])
val divideShift = MAGIC[magicIndex + 2]
val longArraySize: Int = (size + valuesPerLong - 1) / valuesPerLong
val data = LongArray(longArraySize)
for (i in indices) {
val value = when (val v = getValue(i)) {
is Int -> v.toLong()
is Byte -> v.toLong()
else -> error("value must be a byte or an int")
}
val cellIndex = (i * divideMul + divideAdd shr 32 shr divideShift).toInt()
val bitIndex: Int = (i - cellIndex * valuesPerLong) * bitsPerEntry
data[cellIndex] =
data[cellIndex] and (maxEntryValue shl bitIndex).inv() or (value and maxEntryValue) shl bitIndex
}
return data
}

View file

@ -0,0 +1,29 @@
package space.blokk.util
import space.blokk.world.Chunk
import space.blokk.world.block.Block
fun generateHeightmap(sections: Array<Array<Block>?>, ignoredBlocks: Iterable<Block.Meta<*>>): ByteArray {
val array = ByteArray(Chunk.AREA)
for (sectionIndex in sections.indices.reversed()) {
val section = sections[sectionIndex] ?: continue
for (y in 0 until Chunk.SECTION_HEIGHT) {
for (z in 0 until Chunk.LENGTH) {
for (x in 0 until Chunk.LENGTH) {
val index = y * Chunk.AREA + z * Chunk.LENGTH + x
val block = section[index]
if (ignoredBlocks.any { it.blockClass == block::class })
array[z * Chunk.LENGTH + x] = (y - 1).toByte()
}
}
}
// All columns already have a value, so the following sections do not need to be checked anymore
if (array.none { it == 0.toByte() }) break
}
return array
}

View file

@ -0,0 +1,11 @@
package space.blokk.net.packet.play
import space.blokk.net.packet.OutgoingPacket
import space.blokk.world.Chunk
import space.blokk.world.ChunkData
// TODO: Implement block entities and full chunk = false
/**
* Sent to inform the player about blocks and biomes in a chunk.
*/
data class ChunkDataPacket(val key: Chunk.Key, val data: ChunkData) : OutgoingPacket()