fix with negative dimensions y > 256, rework chunk reading

This commit is contained in:
Bixilon 2021-11-20 20:28:25 +01:00
parent 44e8b358a2
commit beaeafbb5b
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
26 changed files with 506 additions and 261 deletions

View File

@ -24,9 +24,11 @@ data class DimensionProperties(
val minY: Int = 0,
val hasCeiling: Boolean = false,
val ultraWarm: Boolean = false,
val height: Int = 256,
@Deprecated("Height does not differ from logical height in 1.18")
val dataHeight: Int = 256,
val supports3DBiomes: Boolean = true,
) {
val height = logicalHeight + minY
val lowestSection = if (minY < 0) {
(minY + 1) / ProtocolDefinition.SECTION_HEIGHT_Y - 1
} else {
@ -69,7 +71,7 @@ data class DimensionProperties(
minY = data["min_y"]?.toInt() ?: 0,
hasCeiling = data["has_ceiling"]?.toBoolean() ?: false,
ultraWarm = data["ultrawarm"]?.toBoolean() ?: false,
height = data["height"]?.toInt() ?: 256,
dataHeight = data["height"]?.toInt() ?: 256,
supports3DBiomes = data["supports_3d_biomes"]?.toBoolean() ?: true,
)
}

View File

@ -16,7 +16,6 @@ package de.bixilon.minosoft.data.registries.registries.registry
import de.bixilon.minosoft.util.collections.Clearable
interface AbstractRegistry<T> : Iterable<T>, Clearable, Parentable<AbstractRegistry<T>> {
val size: Int
operator fun get(any: Any?): T?

View File

@ -17,8 +17,8 @@ import de.bixilon.minosoft.data.registries.blocks.BlockState
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
import de.bixilon.minosoft.util.KUtil.toSynchronizedMap
class BlockStateRegistry(var flattened: Boolean) : AbstractRegistry<BlockState> {
override var parent: AbstractRegistry<BlockState>? = null
class BlockStateRegistry(var flattened: Boolean) : AbstractRegistry<BlockState?> {
override var parent: AbstractRegistry<BlockState?>? = null
private val idMap: MutableMap<Int, BlockState> = mutableMapOf()
override val size: Int
@ -66,7 +66,7 @@ class BlockStateRegistry(var flattened: Boolean) : AbstractRegistry<BlockState>
return forceGet(id)
}
override fun getId(value: BlockState): Int {
override fun getId(value: BlockState?): Int {
TODO("Not yet implemented")
}
}

View File

@ -105,7 +105,7 @@ class Chunk(
data.blocks?.let {
for ((index, blocks) in it.withIndex()) {
blocks ?: continue
val section = getOrPut(index - lowestSection)
val section = getOrPut(index + lowestSection)
section.blocks = blocks
}
blocksInitialized = true
@ -148,7 +148,7 @@ class Chunk(
var section = sections[sectionIndex]
if (section == null) {
section = ChunkSection(connection.registries)
section = ChunkSection()
val neighbours: Array<Chunk> = world.getChunkNeighbours(chunkPosition).unsafeCast()
val cacheBiomeAccessor = world.cacheBiomeAccessor
if (cacheBiomeAccessor != null && biomesInitialized && neighboursLoaded) {

View File

@ -16,11 +16,11 @@ package de.bixilon.minosoft.data.world
import de.bixilon.minosoft.data.entities.block.BlockEntity
import de.bixilon.minosoft.data.registries.blocks.BlockState
import de.bixilon.minosoft.data.world.biome.source.BiomeSource
import de.bixilon.minosoft.data.world.container.RegistrySectionDataProvider
import de.bixilon.minosoft.data.world.container.SectionDataProvider
import glm_.vec3.Vec3i
class ChunkData(
var blocks: Array<RegistrySectionDataProvider<BlockState?>?>? = null,
var blocks: Array<SectionDataProvider<BlockState?>?>? = null,
var blockEntities: Map<Vec3i, BlockEntity>? = null,
var biomeSource: BiomeSource? = null,
var light: Array<ByteArray?>? = null,

View File

@ -15,9 +15,7 @@ package de.bixilon.minosoft.data.world
import de.bixilon.minosoft.data.entities.block.BlockEntity
import de.bixilon.minosoft.data.registries.biomes.Biome
import de.bixilon.minosoft.data.registries.blocks.BlockState
import de.bixilon.minosoft.data.registries.registries.Registries
import de.bixilon.minosoft.data.world.biome.accessor.NoiseBiomeAccessor
import de.bixilon.minosoft.data.world.container.RegistrySectionDataProvider
import de.bixilon.minosoft.data.world.container.SectionDataProvider
import de.bixilon.minosoft.gui.rendering.util.VecUtil.of
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
@ -30,14 +28,12 @@ import glm_.vec3.Vec3i
* Collection of 16x16x16 blocks
*/
class ChunkSection(
var blocks: RegistrySectionDataProvider<BlockState?>,
var biomes: RegistrySectionDataProvider<Biome>,
var blockEntities: SectionDataProvider<BlockEntity?>,
var light: ByteArray, // packed (skyLight: 0xF0, blockLight: 0x0F)
var blocks: SectionDataProvider<BlockState?> = SectionDataProvider(checkSize = true),
var biomes: SectionDataProvider<Biome> = SectionDataProvider(checkSize = false),
var blockEntities: SectionDataProvider<BlockEntity?> = SectionDataProvider(checkSize = false),
var light: ByteArray = ByteArray(ProtocolDefinition.BLOCKS_PER_SECTION), // packed (skyLight: 0xF0, blockLight: 0x0F)
) {
constructor(registries: Registries) : this(RegistrySectionDataProvider<BlockState?>(registries.blockStateRegistry.unsafeCast(), checkSize = true), RegistrySectionDataProvider(registries.biomeRegistry, checkSize = false), SectionDataProvider(checkSize = false), ByteArray(ProtocolDefinition.BLOCKS_PER_SECTION))
fun tick(connection: PlayConnection, chunkPosition: Vec2i, sectionHeight: Int) {
if (blockEntities.isEmpty) {
return

View File

@ -0,0 +1,29 @@
/*
* Minosoft
* Copyright (C) 2021 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.world.biome.source
import de.bixilon.minosoft.data.registries.biomes.Biome
import de.bixilon.minosoft.gui.rendering.util.VecUtil.sectionHeight
class PalettedBiomeArray(
private val containers: Array<Array<Biome>?>,
private val lowestSection: Int,
val edgeBits: Int,
) : BiomeSource {
private val mask = (1 shl edgeBits) - 1
override fun getBiome(x: Int, y: Int, z: Int): Biome? {
return containers.getOrNull(y.sectionHeight - lowestSection)?.get(((((y.sectionHeight and mask) shl edgeBits) or (z and mask)) shl edgeBits) or (x and mask))
}
}

View File

@ -15,14 +15,15 @@ package de.bixilon.minosoft.data.world.container
import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3iUtil.EMPTY
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
import de.bixilon.minosoft.util.KUtil.unsafeCast
import de.bixilon.minosoft.util.ReadWriteLock
import glm_.vec3.Vec3i
open class SectionDataProvider<T>(
data: Array<Any?>? = null,
class SectionDataProvider<T>(
data: Array<T>? = null,
val checkSize: Boolean = false,
) : Iterable<T> {
protected var data = data
protected var data: Array<Any?>? = data?.unsafeCast()
private set
protected val lock = ReadWriteLock() // lock while reading (blocks writing)
var count: Int = 0
@ -191,9 +192,9 @@ open class SectionDataProvider<T>(
unlock()
}
open fun copy(): SectionDataProvider<T> {
fun copy(): SectionDataProvider<T> {
acquire()
val clone = SectionDataProvider<T>(data?.clone())
val clone = SectionDataProvider<T>(data?.clone()?.unsafeCast())
release()
return clone

View File

@ -0,0 +1,36 @@
/*
* Minosoft
* Copyright (C) 2021 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.world.container.palette
import de.bixilon.minosoft.data.world.container.palette.data.PaletteData
import de.bixilon.minosoft.data.world.container.palette.palettes.Palette
class PalettedContainer<T>(
private val edgeBits: Int,
val palette: Palette<T>,
val data: PaletteData,
) {
fun get(x: Int, y: Int, z: Int): T {
return palette.get(data.get((((y shl edgeBits) or z) shl edgeBits) or x))
}
inline fun <reified V : T> unpack(): Array<V> {
val array: Array<V?> = arrayOfNulls(data.size)
for (i in array.indices) {
array[i] = palette.get(data.get(i)) as V
}
return array as Array<V>
}
}

View File

@ -0,0 +1,34 @@
/*
* Minosoft
* Copyright (C) 2021 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.world.container.palette
import de.bixilon.minosoft.data.registries.registries.registry.AbstractRegistry
import de.bixilon.minosoft.data.world.container.palette.data.PaletteData
import de.bixilon.minosoft.data.world.container.palette.palettes.PaletteFactory
import de.bixilon.minosoft.protocol.protocol.PlayInByteBuffer
object PalettedContainerReader {
fun <T> read(buffer: PlayInByteBuffer, registry: AbstractRegistry<T>, paletteFactory: PaletteFactory): PalettedContainer<T> {
val bits = buffer.readUnsignedByte()
val palette = paletteFactory.createPalette(registry, bits)
palette.read(buffer)
val paletteData = PaletteData.create(buffer.versionId, palette.bits, paletteFactory.containerSize)
paletteData.read(buffer)
return PalettedContainer(paletteFactory.edgeBits, palette, paletteData)
}
}

View File

@ -0,0 +1,59 @@
/*
* Minosoft
* Copyright (C) 2021 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.world.container.palette.data
import de.bixilon.minosoft.protocol.protocol.PlayInByteBuffer
import de.bixilon.minosoft.protocol.protocol.ProtocolVersions.V_1_16
class ArrayPaletteData(
val versionId: Int,
val elementBits: Int,
override val size: Int,
) : PaletteData {
init {
check(elementBits in 0..32)
}
private lateinit var data: LongArray
override fun read(buffer: PlayInByteBuffer) {
data = buffer.readLongArray()
}
override operator fun get(index: Int): Int {
val individualValueMask = (1 shl elementBits) - 1
var blockId: Long = if (versionId < V_1_16) { // ToDo: When did this changed? is just a guess
val startLong = index * elementBits / Long.SIZE_BITS
val startOffset = index * elementBits % Long.SIZE_BITS
val endLong = ((index + 1) * elementBits - 1) / Long.SIZE_BITS
if (startLong == endLong) {
data[startLong] ushr startOffset
} else {
val endOffset = Long.SIZE_BITS - startOffset
data[startLong] ushr startOffset or (data[endLong] shl endOffset)
}
} else {
val startLong = index / (Long.SIZE_BITS / elementBits)
val startOffset = index % (Long.SIZE_BITS / elementBits) * elementBits
data[startLong] ushr startOffset
}
blockId = blockId and individualValueMask.toLong()
return blockId.toInt()
}
}

View File

@ -0,0 +1,30 @@
/*
* Minosoft
* Copyright (C) 2021 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.world.container.palette.data
import de.bixilon.minosoft.protocol.protocol.PlayInByteBuffer
class EmptyPaletteData(override val size: Int) : PaletteData {
override fun get(index: Int): Int {
if (index in 0 until size) {
return 0
}
throw IndexOutOfBoundsException("Index $index > $size")
}
override fun read(buffer: PlayInByteBuffer) {
check(buffer.readVarInt() == 0) { "No data expected!" }
}
}

View File

@ -1,6 +1,6 @@
/*
* Minosoft
* Copyright (C) 2020 Moritz Zwerger
* Copyright (C) 2021 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
@ -10,25 +10,24 @@
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.world.palette
import de.bixilon.minosoft.data.registries.blocks.BlockState
package de.bixilon.minosoft.data.world.container.palette.data
import de.bixilon.minosoft.protocol.protocol.PlayInByteBuffer
interface Palette {
interface PaletteData {
val size: Int
fun blockById(id: Int): BlockState?
fun get(index: Int): Int
val bitsPerBlock: Int
fun read(buffer: PlayInByteBuffer)
companion object {
fun choosePalette(bitsPerBlock: Int, buffer: PlayInByteBuffer): Palette {
if (bitsPerBlock <= 4) {
return IndirectPalette(4, buffer)
} else if (bitsPerBlock <= 8) {
return IndirectPalette(bitsPerBlock, buffer)
fun create(versionId: Int, bits: Int, size: Int): PaletteData {
return when (bits) {
0 -> EmptyPaletteData(size)
else -> ArrayPaletteData(versionId, bits, size)
}
return DirectPalette(buffer)
}
}
}

View File

@ -11,34 +11,23 @@
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.world.container
package de.bixilon.minosoft.data.world.container.palette.palettes
import de.bixilon.minosoft.data.registries.registries.registry.AbstractRegistry
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
import de.bixilon.minosoft.protocol.protocol.PlayInByteBuffer
class RegistrySectionDataProvider<T>(
val registry: AbstractRegistry<T>,
data: Array<Any?>? = null,
checkSize: Boolean = false,
) : SectionDataProvider<T>(data, checkSize = checkSize) {
class ArrayPalette<T>(private val registry: AbstractRegistry<T>, override val bits: Int) : Palette<T> {
private var array: Array<Any?> = arrayOfNulls(0)
override fun read(buffer: PlayInByteBuffer) {
array = arrayOfNulls(buffer.readVarInt())
for (i in array.indices) {
array[i] = registry[buffer.readVarInt()]
}
}
@Suppress("UNCHECKED_CAST")
fun setIdData(ids: IntArray) {
val data: Array<Any?> = arrayOfNulls(ProtocolDefinition.BLOCKS_PER_SECTION)
for ((index, id) in ids.withIndex()) {
data[index] = registry[id]
}
setData(data as Array<T>)
}
override fun copy(): RegistrySectionDataProvider<T> {
acquire()
val clone = RegistrySectionDataProvider(registry, data?.clone())
release()
return clone
override fun get(index: Int): T {
return array[index] as T
}
}

View File

@ -0,0 +1,28 @@
/*
* Minosoft
* Copyright (C) 2021 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.world.container.palette.palettes
import de.bixilon.minosoft.data.registries.registries.registry.AbstractRegistry
object BiomePaletteFactory : PaletteFactory {
override val edgeBits = 2
override fun <T : Any?> createPalette(registry: AbstractRegistry<T>, bits: Int): Palette<T> {
return when (bits) {
0 -> SingularPalette(registry)
1, 2, 3 -> ArrayPalette(registry, bits)
else -> RegistryPalette(registry)
}
}
}

View File

@ -0,0 +1,29 @@
/*
* Minosoft
* Copyright (C) 2021 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.world.container.palette.palettes
import de.bixilon.minosoft.data.registries.registries.registry.AbstractRegistry
object BlockStatePaletteFactory : PaletteFactory {
override val edgeBits = 4
override fun <T : Any?> createPalette(registry: AbstractRegistry<T>, bits: Int): Palette<T> {
return when (bits) {
0 -> SingularPalette(registry)
1, 2, 3, 4 -> ArrayPalette(registry, 4)
5, 6, 7, 8 -> ArrayPalette(registry, bits)
else -> RegistryPalette(registry)
}
}
}

View File

@ -0,0 +1,24 @@
/*
* Minosoft
* Copyright (C) 2021 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.world.container.palette.palettes
import de.bixilon.minosoft.protocol.protocol.PlayInByteBuffer
interface Palette<T> {
val bits: Int
fun read(buffer: PlayInByteBuffer)
fun get(index: Int): T
}

View File

@ -0,0 +1,23 @@
/*
* Minosoft
* Copyright (C) 2021 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.world.container.palette.palettes
import de.bixilon.minosoft.data.registries.registries.registry.AbstractRegistry
interface PaletteFactory {
val edgeBits: Int
val containerSize: Int
get() = 1 shl (edgeBits * 3)
fun <T> createPalette(registry: AbstractRegistry<T>, bits: Int): Palette<T>
}

View File

@ -0,0 +1,29 @@
/*
* Minosoft
* Copyright (C) 2021 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.world.container.palette.palettes
import de.bixilon.minosoft.data.registries.registries.registry.AbstractRegistry
import de.bixilon.minosoft.protocol.protocol.PlayInByteBuffer
import de.bixilon.minosoft.util.MMath
class RegistryPalette<T>(private val registry: AbstractRegistry<T>) : Palette<T> {
override val bits = MMath.ceilLog2(registry.size)
override fun read(buffer: PlayInByteBuffer) {}
@Suppress("UNCHECKED_CAST")
override fun get(index: Int): T {
return registry[index] as T
}
}

View File

@ -0,0 +1,32 @@
/*
* Minosoft
* Copyright (C) 2021 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.world.container.palette.palettes
import de.bixilon.minosoft.data.registries.registries.registry.AbstractRegistry
import de.bixilon.minosoft.protocol.protocol.PlayInByteBuffer
class SingularPalette<T>(private val registry: AbstractRegistry<T>) : Palette<T> {
override val bits: Int = 0
var item: T? = null
private set
override fun read(buffer: PlayInByteBuffer) {
item = registry[buffer.readVarInt()]
}
@Suppress("UNCHECKED_CAST")
override fun get(index: Int): T {
return item as T
}
}

View File

@ -1,42 +0,0 @@
/*
* Minosoft
* Copyright (C) 2020 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.world.palette
import de.bixilon.minosoft.data.registries.blocks.BlockState
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
import de.bixilon.minosoft.protocol.protocol.PlayInByteBuffer
import de.bixilon.minosoft.protocol.protocol.ProtocolVersions
import kotlin.math.ceil
import kotlin.math.ln
class DirectPalette(buffer: PlayInByteBuffer) : Palette {
private var connection: PlayConnection = buffer.connection
init {
if (buffer.versionId < ProtocolVersions.V_17W47A) {
buffer.readVarInt()
}
}
override fun blockById(id: Int): BlockState? {
return connection.registries.blockStateRegistry[id]
}
override val bitsPerBlock: Int
get() {
if (this.connection.version.versionId < ProtocolVersions.V_18W10D) {
return 13
}
return ceil(ln(connection.registries.blockStateRegistry.size.toDouble()) / ln(2.0)).toInt()
}
}

View File

@ -1,45 +0,0 @@
/*
* Minosoft
* Copyright (C) 2020 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.world.palette
import de.bixilon.minosoft.config.StaticConfiguration
import de.bixilon.minosoft.data.registries.blocks.BlockState
import de.bixilon.minosoft.protocol.protocol.PlayInByteBuffer
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
import de.bixilon.minosoft.util.logging.Log
class IndirectPalette(
override val bitsPerBlock: Int,
buffer: PlayInByteBuffer,
) : Palette {
private val connection = buffer.connection
private var palette = buffer.readVarIntArray()
override fun blockById(id: Int): BlockState? {
var blockId = id
if (blockId < palette.size) {
blockId = palette[blockId]
}
val block = connection.registries.blockStateRegistry[blockId]
if (StaticConfiguration.DEBUG_MODE && block == null && blockId != ProtocolDefinition.AIR_BLOCK_ID) {
val blockName: String = if (connection.version.isFlattened()) {
blockId.toString()
} else {
"${blockId shr 4}:${blockId and 0x0F} ($blockId)"
}
Log.warn("Server sent unknown block: $blockName")
}
return block
}
}

View File

@ -23,9 +23,7 @@ import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3iUtil.EMPTY
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
import de.bixilon.minosoft.protocol.packets.s2c.PlayS2CPacket
import de.bixilon.minosoft.protocol.protocol.PlayInByteBuffer
import de.bixilon.minosoft.protocol.protocol.ProtocolVersions
import de.bixilon.minosoft.protocol.protocol.ProtocolVersions.V_18W44A
import de.bixilon.minosoft.protocol.protocol.ProtocolVersions.V_21W37A
import de.bixilon.minosoft.protocol.protocol.ProtocolVersions.*
import de.bixilon.minosoft.util.KUtil.toInt
import de.bixilon.minosoft.util.KUtil.unsafeCast
import de.bixilon.minosoft.util.Util
@ -51,16 +49,15 @@ class ChunkDataS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket() {
init {
val dimension = buffer.connection.world.dimension!!
chunkPosition = buffer.readChunkPosition()
if (buffer.versionId < ProtocolVersions.V_20W45A) {
if (buffer.versionId < V_20W45A) {
isFullChunk = !buffer.readBoolean()
}
when {
buffer.versionId < ProtocolVersions.V_14W26A -> {
if (buffer.versionId < V_14W26A) { // ToDo
val sectionBitMask = BitSet.valueOf(buffer.readByteArray(2))
val addBitMask = BitSet.valueOf(buffer.readByteArray(2))
// decompress chunk data
val decompressed: PlayInByteBuffer = if (buffer.versionId < ProtocolVersions.V_14W28A) {
val decompressed: PlayInByteBuffer = if (buffer.versionId < V_14W28A) {
Util.decompress(buffer.readByteArray(buffer.readInt()), buffer.connection)
} else {
buffer
@ -71,42 +68,45 @@ class ChunkDataS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket() {
} else {
this.chunkData.replace(chunkData)
}
}
buffer.versionId < V_21W37A -> {
if (buffer.versionId >= ProtocolVersions.V_1_16_PRE7 && buffer.versionId < ProtocolVersions.V_1_16_2_PRE2) {
} else {
if (buffer.versionId in V_1_16_PRE7 until V_1_16_2_PRE2) {
buffer.readBoolean() // ToDo: ignore old data???
}
val sectionBitMask: BitSet = when {
buffer.versionId < ProtocolVersions.V_15W34C -> {
BitSet.valueOf(buffer.readByteArray(2))
}
buffer.versionId < ProtocolVersions.V_15W36D -> {
BitSet.valueOf(buffer.readByteArray(4))
}
buffer.versionId < ProtocolVersions.V_21W03A -> {
BitSet.valueOf(longArrayOf(buffer.readVarInt().toLong()))
}
else -> {
BitSet.valueOf(buffer.readLongArray())
}
val sectionBitMask = when {
buffer.versionId < V_15W34C -> BitSet.valueOf(buffer.readByteArray(2))
buffer.versionId < V_15W36D -> BitSet.valueOf(buffer.readByteArray(4))
buffer.versionId < V_21W03A -> BitSet.valueOf(longArrayOf(buffer.readVarInt().toLong()))
buffer.versionId < V_21W37A -> BitSet.valueOf(buffer.readLongArray())
else -> null
}
if (buffer.versionId >= V_18W44A) {
heightMap = buffer.readNBT()?.compoundCast()
}
if (!isFullChunk) {
if (!isFullChunk && buffer.versionId < V_21W37A) {
this.chunkData.biomeSource = SpatialBiomeArray(buffer.readBiomeArray())
}
val size = buffer.readVarInt()
val lastBufferPosition = buffer.pointer
val chunkData = ChunkUtil.readChunkPacket(buffer, dimension, sectionBitMask, null, !isFullChunk, dimension.hasSkyLight)
if (buffer.versionId < V_21W37A) {
val chunkData = ChunkUtil.readChunkPacket(buffer, dimension, sectionBitMask!!, null, !isFullChunk, dimension.hasSkyLight)
if (chunkData == null) {
unloadChunk = true
} else {
this.chunkData.replace(chunkData)
}
// set position of the byte buffer, because of some reasons HyPixel makes some weird stuff and sends way to much 0 bytes. (~ 190k), thanks @pokechu22
} else {
this.chunkData.replace(ChunkUtil.readPaletteChunk(buffer, dimension, null, isFullChunk = true, containsSkyLight = false))
}
// set position to expected read positions; the server sometimes sends a bunch of useless zeros (~ 190k), thanks @pokechu22
buffer.pointer = size + lastBufferPosition
if (buffer.versionId >= ProtocolVersions.V_1_9_4) {
// block entities
when {
buffer.versionId < V_1_9_4 -> {
}
buffer.versionId < V_21W37A -> {
val blockEntities: MutableMap<Vec3i, BlockEntity> = mutableMapOf()
val positionOffset = Vec3i.of(chunkPosition, dimension.lowestSection, Vec3i.EMPTY)
for (i in 0 until buffer.readVarInt()) {
@ -123,16 +123,7 @@ class ChunkDataS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket() {
}
this.chunkData.blockEntities = blockEntities
}
}
else -> {
heightMap = buffer.readNBT()?.compoundCast()
val sectionBuffer = PlayInByteBuffer(buffer.readByteArray(), buffer.connection)
for (sectionHeight in dimension.lowestSection until dimension.highestSection) {
val nonAirBlocks = sectionBuffer.readShort()
// ToDo: BlockStates, Biomes
}
val blockEntities: MutableMap<Vec3i, BlockEntity> = mutableMapOf()
for (i in 0 until buffer.readVarInt()) {
@ -144,10 +135,12 @@ class ChunkDataS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket() {
entity.updateNBT(nbt)
blockEntities[Vec3i(xz shr 4, y, xz and 0x0F)] = entity
}
chunkData.blockEntities = blockEntities
this.chunkData.blockEntities = blockEntities
}
}
val lightPacket = ChunkLightDataS2CP(buffer) { chunkPosition }
chunkData.replace(lightPacket.chunkData)
if (buffer.versionId >= V_21W37A) {
this.chunkData.replace(ChunkLightDataS2CP(buffer) { chunkPosition }.chunkData)
}
}
}

View File

@ -163,6 +163,12 @@ open class InByteBuffer {
return array
}
fun readLongArray(target: LongArray, size: Int = readVarInt()) {
for (i in 0 until size) {
target[i] = readLong()
}
}
fun readVarLong(): Long {
var byteCount = 0

View File

@ -15,9 +15,12 @@ package de.bixilon.minosoft.util
import de.bixilon.minosoft.util.KUtil.decide
import glm_.vec2.Vec2i
import kotlin.math.ceil
import kotlin.math.floor
import kotlin.math.ln
object MMath {
private const val LN_2 = 0.69314718056 // ln(2.0)
fun clamp(value: Vec2i, min: Vec2i, max: Vec2i): Vec2i {
value.x = clamp(value.x, min.x, max.x)
@ -118,4 +121,8 @@ object MMath {
val int = this.toInt()
return (this > int).decide(int + 1, int)
}
fun ceilLog2(value: Int): Int {
return ceil(ln(value.toDouble()) / LN_2).toInt()
}
}

View File

@ -20,14 +20,18 @@ import de.bixilon.minosoft.data.registries.dimension.DimensionProperties
import de.bixilon.minosoft.data.world.Chunk
import de.bixilon.minosoft.data.world.ChunkData
import de.bixilon.minosoft.data.world.ChunkSection
import de.bixilon.minosoft.data.world.biome.source.PalettedBiomeArray
import de.bixilon.minosoft.data.world.biome.source.XZBiomeArray
import de.bixilon.minosoft.data.world.container.RegistrySectionDataProvider
import de.bixilon.minosoft.data.world.palette.Palette.Companion.choosePalette
import de.bixilon.minosoft.data.world.container.SectionDataProvider
import de.bixilon.minosoft.data.world.container.palette.PalettedContainer
import de.bixilon.minosoft.data.world.container.palette.PalettedContainerReader
import de.bixilon.minosoft.data.world.container.palette.palettes.BiomePaletteFactory
import de.bixilon.minosoft.data.world.container.palette.palettes.BlockStatePaletteFactory
import de.bixilon.minosoft.data.world.container.palette.palettes.SingularPalette
import de.bixilon.minosoft.gui.rendering.util.vec.vec2.Vec2iUtil.abs
import de.bixilon.minosoft.protocol.protocol.PlayInByteBuffer
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
import de.bixilon.minosoft.protocol.protocol.ProtocolVersions.*
import de.bixilon.minosoft.util.KUtil.unsafeCast
import glm_.vec2.Vec2i
import java.util.*
@ -63,13 +67,13 @@ object ChunkUtil {
// parse data
var arrayPosition = 0
val sectionBlocks: Array<RegistrySectionDataProvider<BlockState?>?> = arrayOfNulls(dimension.sections)
val sectionBlocks: Array<SectionDataProvider<BlockState?>?> = arrayOfNulls(dimension.sections)
for ((sectionIndex, sectionHeight) in (dimension.lowestSection until dimension.highestSection).withIndex()) {
if (!sectionBitMask[sectionIndex]) {
continue
}
val blocks = arrayOfNulls<BlockState>(ProtocolDefinition.BLOCKS_PER_SECTION)
val blocks: Array<BlockState?> = arrayOfNulls(ProtocolDefinition.BLOCKS_PER_SECTION)
for (blockNumber in 0 until ProtocolDefinition.BLOCKS_PER_SECTION) {
var blockId = (blockData[arrayPosition].toInt() and 0xFF) shl 4
@ -95,7 +99,7 @@ object ChunkUtil {
blocks[blockNumber] = buffer.connection.registries.blockStateRegistry[blockId] ?: continue
}
sectionBlocks[sectionHeight] = RegistrySectionDataProvider(buffer.connection.registries.blockStateRegistry.unsafeCast(), blocks.unsafeCast(), true)
sectionBlocks[sectionHeight] = SectionDataProvider(blocks, true)
}
chunkData.blocks = sectionBlocks
return chunkData
@ -129,7 +133,7 @@ object ChunkUtil {
}
var arrayPos = 0
val sectionBlocks: Array<RegistrySectionDataProvider<BlockState?>?> = arrayOfNulls(dimension.sections)
val sectionBlocks: Array<SectionDataProvider<BlockState?>?> = arrayOfNulls(dimension.sections)
for ((sectionIndex, sectionHeight) in (dimension.lowestSection until dimension.highestSection).withIndex()) { // max sections per chunks in chunk column
if (!sectionBitMask[sectionIndex]) {
continue
@ -140,56 +144,38 @@ object ChunkUtil {
val block = buffer.connection.registries.blockStateRegistry[blockId] ?: continue
blocks[blockNumber] = block
}
sectionBlocks[sectionHeight] = RegistrySectionDataProvider(buffer.connection.registries.blockStateRegistry.unsafeCast(), blocks.unsafeCast(), true)
sectionBlocks[sectionHeight] = SectionDataProvider(blocks, true)
}
chunkData.blocks = sectionBlocks
return chunkData
}
fun readPaletteChunk(buffer: PlayInByteBuffer, dimension: DimensionProperties, sectionBitMask: BitSet, isFullChunk: Boolean, containsSkyLight: Boolean = false): ChunkData {
fun readPaletteChunk(buffer: PlayInByteBuffer, dimension: DimensionProperties, sectionBitMask: BitSet?, isFullChunk: Boolean, containsSkyLight: Boolean = false): ChunkData {
val chunkData = ChunkData()
val sectionBlocks: Array<RegistrySectionDataProvider<BlockState?>?> = arrayOfNulls(dimension.sections)
val sectionBlocks: Array<SectionDataProvider<BlockState?>?> = arrayOfNulls(dimension.sections)
val light: Array<ByteArray?> = arrayOfNulls(dimension.sections)
var lightReceived = 0
val biomes: Array<Array<Biome>?> = arrayOfNulls(dimension.sections)
for ((sectionIndex, sectionHeight) in (dimension.lowestSection until sectionBitMask.length()).withIndex()) { // max sections per chunks in chunk column
if (!sectionBitMask[sectionIndex]) {
for ((sectionIndex, sectionHeight) in (dimension.lowestSection until (sectionBitMask?.length() ?: dimension.highestSection)).withIndex()) { // max sections per chunks in chunk column
if (sectionBitMask?.get(sectionIndex) == false) {
continue
}
if (buffer.versionId >= V_18W43A) {
buffer.readShort() // block count
buffer.readShort() // non-air block count
}
val palette = choosePalette(buffer.readUnsignedByte(), buffer)
val individualValueMask = (1 shl palette.bitsPerBlock) - 1
val blockContainer: PalettedContainer<BlockState?> = PalettedContainerReader.read(buffer, buffer.connection.registries.blockStateRegistry, paletteFactory = BlockStatePaletteFactory)
val data = buffer.readLongArray()
val blocks = arrayOfNulls<BlockState>(ProtocolDefinition.BLOCKS_PER_SECTION)
for (blockNumber in 0 until ProtocolDefinition.BLOCKS_PER_SECTION) {
var blockId: Long = if (buffer.versionId < V_1_16) { // ToDo: When did this changed? is just a guess
val startLong = blockNumber * palette.bitsPerBlock / Long.SIZE_BITS
val startOffset = blockNumber * palette.bitsPerBlock % Long.SIZE_BITS
val endLong = ((blockNumber + 1) * palette.bitsPerBlock - 1) / Long.SIZE_BITS
if (startLong == endLong) {
data[startLong] ushr startOffset
} else {
val endOffset = Long.SIZE_BITS - startOffset
data[startLong] ushr startOffset or (data[endLong] shl endOffset)
if (blockContainer.palette !is SingularPalette<*> || blockContainer.palette.item != null) {
sectionBlocks[sectionHeight - dimension.lowestSection] = SectionDataProvider(blockContainer.unpack(), checkSize = true)
}
} else {
val startLong = blockNumber / (Long.SIZE_BITS / palette.bitsPerBlock)
val startOffset = blockNumber % (Long.SIZE_BITS / palette.bitsPerBlock) * palette.bitsPerBlock
data[startLong] ushr startOffset
if (buffer.versionId >= V_21W37A) {
val biomeContainer: PalettedContainer<Biome> = PalettedContainerReader.read(buffer, buffer.connection.registries.biomeRegistry, paletteFactory = BiomePaletteFactory)
biomes[sectionHeight - dimension.lowestSection] = biomeContainer.unpack()
}
blockId = blockId and individualValueMask.toLong()
val block = palette.blockById(blockId.toInt()) ?: continue
blocks[blockNumber] = block
}
if (buffer.versionId < V_18W43A) {
val blockLight = buffer.readByteArray(ProtocolDefinition.BLOCKS_PER_SECTION / 2)
@ -200,14 +186,15 @@ object ChunkUtil {
light[sectionHeight - dimension.lowestSection] = LightUtil.mergeLight(blockLight, skyLight ?: LightUtil.EMPTY_LIGHT_ARRAY)
lightReceived++
}
sectionBlocks[sectionHeight - dimension.lowestSection] = RegistrySectionDataProvider(buffer.connection.registries.blockStateRegistry.unsafeCast(), blocks.unsafeCast(), true)
}
chunkData.blocks = sectionBlocks
if (lightReceived > 0) {
chunkData.light = light
}
if (buffer.versionId < V_19W36A && isFullChunk) {
if (buffer.versionId >= V_21W37A) {
chunkData.biomeSource = PalettedBiomeArray(biomes, dimension.lowestSection, BiomePaletteFactory.edgeBits)
} else if (buffer.versionId < V_19W36A && isFullChunk) {
chunkData.biomeSource = readLegacyBiomeArray(buffer)
}