diff --git a/src/main/java/de/bixilon/minosoft/data/registries/dimension/DimensionProperties.kt b/src/main/java/de/bixilon/minosoft/data/registries/dimension/DimensionProperties.kt index 42877009a..0ba86469c 100644 --- a/src/main/java/de/bixilon/minosoft/data/registries/dimension/DimensionProperties.kt +++ b/src/main/java/de/bixilon/minosoft/data/registries/dimension/DimensionProperties.kt @@ -70,7 +70,7 @@ data class DimensionProperties( hasCeiling = data["has_ceiling"]?.toBoolean() ?: false, ultraWarm = data["ultrawarm"]?.toBoolean() ?: false, height = data["height"]?.toInt() ?: 256, - supports3DBiomes = data["supports_3d_biomes"]?.toBoolean() ?: false, + supports3DBiomes = data["supports_3d_biomes"]?.toBoolean() ?: true, ) } } diff --git a/src/main/java/de/bixilon/minosoft/data/world/Chunk.kt b/src/main/java/de/bixilon/minosoft/data/world/Chunk.kt index b59a76992..efb58c947 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/Chunk.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/Chunk.kt @@ -13,11 +13,16 @@ 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.world.ChunkSection.Companion.index +import de.bixilon.minosoft.data.world.biome.accessor.BiomeAccessor import de.bixilon.minosoft.data.world.biome.source.BiomeSource +import de.bixilon.minosoft.gui.rendering.util.VecUtil.inChunkPosition import de.bixilon.minosoft.gui.rendering.util.VecUtil.inChunkSectionPosition import de.bixilon.minosoft.gui.rendering.util.VecUtil.sectionHeight +import de.bixilon.minosoft.modding.event.EventInitiators +import de.bixilon.minosoft.modding.event.events.ChunkDataChangeEvent import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition import glm_.vec2.Vec2i @@ -28,21 +33,22 @@ import glm_.vec3.Vec3i */ class Chunk( private val connection: PlayConnection, + private val chunkPosition: Vec2i, private var sections: Array? = null, var biomeSource: BiomeSource? = null, -) : Iterable { +) : Iterable, BiomeAccessor { var bottomLight: IntArray? = null var topLight: IntArray? = null val lowestSection = connection.world.dimension!!.lowestSection val highestSection = connection.world.dimension!!.highestSection - var blocksInitialized: Boolean = false - val biomesInitialized - get() = biomeSource != null + var blocksInitialized = false // All block data was received + var biomesInitialized = false // All biome data is initialized (aka. cache built, or similar) var lightInitialized = false + var neighboursLoaded = false val isFullyLoaded: Boolean - get() = blocksInitialized && biomesInitialized && lightInitialized + get() = blocksInitialized && biomesInitialized && lightInitialized && neighboursLoaded operator fun get(sectionHeight: Int): ChunkSection? = sections?.getOrNull(sectionHeight - lowestSection) @@ -115,7 +121,10 @@ class Chunk( topLight = it lightInitialized = true } - data.biomeSource?.let { this.biomeSource = it } + data.biomeSource?.let { + this.biomeSource = it + } + connection.world.onChunkUpdate(chunkPosition, this) } @Synchronized @@ -126,6 +135,10 @@ class Chunk( var section = sections[sectionIndex] if (section == null) { section = ChunkSection(connection.registries) + val cacheBiomeAccessor = connection.world.cacheBiomeAccessor + if (cacheBiomeAccessor != null && biomesInitialized && neighboursLoaded) { + section.buildBiomeCache(chunkPosition, sectionHeight, cacheBiomeAccessor) + } sections[sectionIndex] = section } return section @@ -154,7 +167,27 @@ class Chunk( return get(position.sectionHeight)?.light?.get(index) ?: 0xFF } + fun buildBiomeCache() { + val cacheBiomeAccessor = connection.world.cacheBiomeAccessor ?: return + check(neighboursLoaded) + // val neighbours = connection.world.getChunkNeighbours(chunkPosition) + for ((sectionIndex, section) in sections!!.withIndex()) { + section ?: continue + val sectionHeight = sectionIndex + lowestSection + section.buildBiomeCache(chunkPosition, sectionHeight, cacheBiomeAccessor) + } + biomesInitialized = true + connection.fireEvent(ChunkDataChangeEvent(connection, EventInitiators.UNKNOWN, chunkPosition, this)) + } + override fun iterator(): Iterator { return sections!!.iterator() } + + override fun getBiome(blockPosition: Vec3i): Biome? { + if (connection.world.cacheBiomeAccessor != null) { + return get(blockPosition.sectionHeight)?.biomes?.get(blockPosition.x, blockPosition.sectionHeight, blockPosition.z) + } + return biomeSource?.getBiome(blockPosition.inChunkPosition) + } } diff --git a/src/main/java/de/bixilon/minosoft/data/world/ChunkSection.kt b/src/main/java/de/bixilon/minosoft/data/world/ChunkSection.kt index 7c291fc56..0e4b7be4b 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/ChunkSection.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/ChunkSection.kt @@ -16,6 +16,7 @@ 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.BiomeAccessor 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 @@ -30,7 +31,6 @@ import glm_.vec3.Vec3i */ class ChunkSection( var blocks: RegistrySectionDataProvider, - @Deprecated("TODO") var biomes: RegistrySectionDataProvider, var blockEntities: SectionDataProvider, var light: IntArray, // packed (skyLight: 0xF0, blockLight: 0x0F) @@ -84,4 +84,10 @@ class ChunkSection( return y shl 8 or (z shl 4) or x } } + + fun buildBiomeCache(chunkPosition: Vec2i, sectionHeight: Int, biomeAccessor: BiomeAccessor) { + for (blockIndex in 0 until ProtocolDefinition.BLOCKS_PER_SECTION) { + biomes[blockIndex] = biomeAccessor.getBiome(Vec3i.of(chunkPosition, sectionHeight, blockIndex.indexPosition))!! + } + } } diff --git a/src/main/java/de/bixilon/minosoft/data/world/World.kt b/src/main/java/de/bixilon/minosoft/data/world/World.kt index 72e23316c..2ef89189e 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/World.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/World.kt @@ -22,7 +22,7 @@ import de.bixilon.minosoft.data.registries.blocks.types.FluidBlock import de.bixilon.minosoft.data.registries.dimension.DimensionProperties import de.bixilon.minosoft.data.registries.sounds.SoundEvent import de.bixilon.minosoft.data.world.biome.accessor.BiomeAccessor -import de.bixilon.minosoft.data.world.biome.accessor.NullBiomeAccessor +import de.bixilon.minosoft.data.world.biome.accessor.WorldBiomeAccessor import de.bixilon.minosoft.gui.rendering.particle.ParticleRenderer import de.bixilon.minosoft.gui.rendering.particle.types.Particle import de.bixilon.minosoft.gui.rendering.sound.AudioPlayer @@ -39,6 +39,8 @@ import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition import de.bixilon.minosoft.util.KUtil.synchronizedMapOf import de.bixilon.minosoft.util.KUtil.toSynchronizedMap import de.bixilon.minosoft.util.MMath +import de.bixilon.minosoft.util.chunk.ChunkUtil +import de.bixilon.minosoft.util.chunk.ChunkUtil.fullyLoaded import de.bixilon.minosoft.util.collections.SynchronizedMap import glm_.func.common.clamp import glm_.vec2.Vec2i @@ -55,6 +57,7 @@ import kotlin.random.Random class World( val connection: PlayConnection, ) : BiomeAccessor { + var cacheBiomeAccessor: BiomeAccessor? = null val chunks: SynchronizedMap = synchronizedMapOf() val entities = WorldEntities() var hardcore = false @@ -62,7 +65,7 @@ class World( var difficulty: Difficulties? = null var difficultyLocked = false var hashedSeed = 0L - var biomeAccessor: BiomeAccessor = NullBiomeAccessor + val biomeAccessor: BiomeAccessor = WorldBiomeAccessor(this) var time = 0L var age = 0L var raining = false @@ -82,7 +85,7 @@ class World( } fun getOrCreateChunk(chunkPosition: Vec2i): Chunk { - return chunks.getOrPut(chunkPosition) { Chunk(connection) } + return chunks.getOrPut(chunkPosition) { Chunk(connection, chunkPosition) } } fun setBlockState(blockPosition: Vec3i, blockState: BlockState?) { @@ -121,6 +124,9 @@ class World( fun unloadChunk(chunkPosition: Vec2i) { chunks.remove(chunkPosition) ?: return + for (neighbour in getChunkNeighbours(chunkPosition)) { + neighbour?.neighboursLoaded = false + } connection.fireEvent(ChunkUnloadEvent(connection, EventInitiators.UNKNOWN, chunkPosition)) } @@ -235,6 +241,56 @@ class World( return base * 0.8 + 0.2 } + + /** + * @return All 8 neighbour chunks + */ + fun getChunkNeighbours(neighbourPositions: Array): Array { + val chunks: Array = arrayOfNulls(neighbourPositions.size) + for ((index, neighbourPosition) in neighbourPositions.withIndex()) { + chunks[index] = this[neighbourPosition] ?: continue + } + return chunks + } + + fun getChunkNeighbours(chunkPosition: Vec2i): Array { + return getChunkNeighbours(ChunkUtil.getChunkNeighbourPositions(chunkPosition)) + } + + fun onChunkUpdate(chunkPosition: Vec2i, chunk: Chunk = get(chunkPosition)!!) { + val neighbourPositions = ChunkUtil.getChunkNeighbourPositions(chunkPosition) + val neighbours = getChunkNeighbours(neighbourPositions) + if (chunk.neighboursLoaded) { + return + } + if (neighbours.fullyLoaded) { + chunk.neighboursLoaded = true + chunk.buildBiomeCache() + } + for ((index, neighbourPosition) in neighbourPositions.withIndex()) { + if (neighbourPosition == chunkPosition) { + continue + } + val neighbourChunk = neighbours[index] ?: continue + + if (neighbourChunk.neighboursLoaded) { + continue + } + var biomeSourceLoaded = true + for (neighbourNeighbourChunk in getChunkNeighbours(neighbourPosition)) { + if (neighbourNeighbourChunk?.biomeSource == null) { + biomeSourceLoaded = false + break + } + } + if (biomeSourceLoaded) { + neighbourChunk.neighboursLoaded = true + neighbourChunk.buildBiomeCache() + } + } + } + + companion object { const val MAX_SIZE = 29999999 const val MAX_SIZEf = MAX_SIZE.toFloat() diff --git a/src/main/java/de/bixilon/minosoft/data/world/biome/accessor/BlockBiomeAccessor.kt b/src/main/java/de/bixilon/minosoft/data/world/biome/accessor/WorldBiomeAccessor.kt similarity index 84% rename from src/main/java/de/bixilon/minosoft/data/world/biome/accessor/BlockBiomeAccessor.kt rename to src/main/java/de/bixilon/minosoft/data/world/biome/accessor/WorldBiomeAccessor.kt index b0bf4ed34..cc2762a99 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/biome/accessor/BlockBiomeAccessor.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/biome/accessor/WorldBiomeAccessor.kt @@ -14,16 +14,14 @@ package de.bixilon.minosoft.data.world.biome.accessor import de.bixilon.minosoft.data.registries.biomes.Biome - import de.bixilon.minosoft.data.world.World import de.bixilon.minosoft.gui.rendering.util.VecUtil.chunkPosition import de.bixilon.minosoft.gui.rendering.util.VecUtil.inChunkPosition import glm_.vec3.Vec3i -class BlockBiomeAccessor(private val world: World) : BiomeAccessor { +class WorldBiomeAccessor(val world: World) : BiomeAccessor { override fun getBiome(blockPosition: Vec3i): Biome? { - val biomePosition = blockPosition.inChunkPosition - return world[blockPosition.chunkPosition]?.biomeSource?.getBiome(biomePosition) + return world[blockPosition.chunkPosition]?.getBiome(blockPosition.inChunkPosition) } } diff --git a/src/main/java/de/bixilon/minosoft/data/world/biome/source/XZBiomeArray.kt b/src/main/java/de/bixilon/minosoft/data/world/biome/source/XZBiomeArray.kt index 588ed8817..bbda30137 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/biome/source/XZBiomeArray.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/biome/source/XZBiomeArray.kt @@ -18,6 +18,7 @@ import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition import glm_.vec3.Vec3i class XZBiomeArray(private val biomes: Array) : BiomeSource { + init { check(biomes.size == ProtocolDefinition.SECTION_WIDTH_X * ProtocolDefinition.SECTION_WIDTH_Z) { "Biome array size does not match the xz block count!" } } diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/JoinGameS2CP.kt b/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/JoinGameS2CP.kt index 51aec361a..878ed381c 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/JoinGameS2CP.kt +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/JoinGameS2CP.kt @@ -20,7 +20,6 @@ import de.bixilon.minosoft.data.registries.ResourceLocation import de.bixilon.minosoft.data.registries.dimension.Dimension import de.bixilon.minosoft.data.registries.dimension.DimensionProperties import de.bixilon.minosoft.data.registries.other.game.event.handlers.gamemode.GamemodeChangeEvent -import de.bixilon.minosoft.data.world.biome.accessor.BlockBiomeAccessor import de.bixilon.minosoft.data.world.biome.accessor.NoiseBiomeAccessor import de.bixilon.minosoft.modding.channels.DefaultPluginChannels import de.bixilon.minosoft.modding.event.EventInitiators @@ -162,10 +161,8 @@ class JoinGameS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket() { connection.world.entities.add(entityId, null, playerEntity) connection.world.hashedSeed = hashedSeed - connection.world.biomeAccessor = if (connection.version.versionId < ProtocolVersions.V_19W36A) { - BlockBiomeAccessor(connection.world) - } else { - NoiseBiomeAccessor(connection.world) + if (connection.version.versionId >= ProtocolVersions.V_19W36A) { + connection.world.cacheBiomeAccessor = NoiseBiomeAccessor(connection.world) } TimeWorker.addTask(TimeWorkerTask(150, true) { // ToDo: Temp workaround connection.sendPacket(ClientSettingsC2SP("en_us")) diff --git a/src/main/java/de/bixilon/minosoft/util/chunk/ChunkUtil.kt b/src/main/java/de/bixilon/minosoft/util/chunk/ChunkUtil.kt index 1893491a2..3c64d19c1 100644 --- a/src/main/java/de/bixilon/minosoft/util/chunk/ChunkUtil.kt +++ b/src/main/java/de/bixilon/minosoft/util/chunk/ChunkUtil.kt @@ -16,7 +16,9 @@ package de.bixilon.minosoft.util.chunk import de.bixilon.minosoft.data.registries.biomes.Biome import de.bixilon.minosoft.data.registries.blocks.BlockState 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.XZBiomeArray import de.bixilon.minosoft.data.world.container.RegistrySectionDataProvider import de.bixilon.minosoft.data.world.palette.Palette.Companion.choosePalette @@ -24,6 +26,7 @@ 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.* @@ -221,4 +224,44 @@ object ChunkUtil { } return XZBiomeArray(biomes.toTypedArray()) } + + val Array.fullyLoaded: Boolean + get() { + for (neighbour in this) { + if (neighbour?.isFullyLoaded != true) { + return false + } + } + return true + } + + + fun getChunkNeighbourPositions(chunkPosition: Vec2i): Array { + return arrayOf( + chunkPosition + Vec2i(-1, -1), + chunkPosition + Vec2i(-1, 0), + chunkPosition + Vec2i(-1, 1), + chunkPosition + Vec2i(0, -1), + chunkPosition + Vec2i(0, 1), + chunkPosition + Vec2i(1, -1), + chunkPosition + Vec2i(1, 0), + chunkPosition + Vec2i(1, 1), + ) + } + + /** + * @param neighbourChunks: **Fully loaded** neighbour chunks + */ + private fun getSectionNeighbours(neighbourChunks: Array, chunk: Chunk, sectionHeight: Int): Array { + return arrayOf( + chunk[sectionHeight - 1], + chunk[sectionHeight + 1], + neighbourChunks[3][sectionHeight], + neighbourChunks[4][sectionHeight], + neighbourChunks[1][sectionHeight], + neighbourChunks[6][sectionHeight], + ) + } + + }