Start working on ChunkDataPacketCodec
This commit is contained in:
parent
5aeea48516
commit
63b676f062
6 changed files with 173 additions and 5 deletions
|
@ -14,10 +14,28 @@ data class ChunkData(
|
||||||
/**
|
/**
|
||||||
* 1024 biome IDs (one for each 4x4x4 area), ordered in this order: X, Z, Y.
|
* 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.
|
// 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 {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (javaClass != other?.javaClass) return false
|
if (javaClass != other?.javaClass) return false
|
||||||
|
@ -26,6 +44,10 @@ data class ChunkData(
|
||||||
|
|
||||||
if (!sections.contentDeepEquals(other.sections)) return false
|
if (!sections.contentDeepEquals(other.sections)) return false
|
||||||
if (!biomes.contentEquals(other.biomes)) 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
|
return true
|
||||||
}
|
}
|
||||||
|
@ -33,10 +55,7 @@ data class ChunkData(
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
var result = sections.contentDeepHashCode()
|
var result = sections.contentDeepHashCode()
|
||||||
result = 31 * result + biomes.contentHashCode()
|
result = 31 * result + biomes.contentHashCode()
|
||||||
|
result = 31 * result + (solidBlocksHeightmap?.contentHashCode() ?: 0)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val BIOME_AREAS_IN_CHUNK = 1024
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ val guavaVersion = properties["version.guava"].toString()
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api(project(":blokk-packets"))
|
api(project(":blokk-packets"))
|
||||||
|
api(project(":blokk-nbt"))
|
||||||
|
|
||||||
// Netty
|
// Netty
|
||||||
api("io.netty:netty-buffer:${nettyVersion}")
|
api("io.netty:netty-buffer:${nettyVersion}")
|
||||||
|
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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()
|
Reference in a new issue