diff --git a/src/main/java/de/bixilon/minosoft/data/Directions.kt b/src/main/java/de/bixilon/minosoft/data/Directions.kt index d8df51ce4..8c5d46522 100644 --- a/src/main/java/de/bixilon/minosoft/data/Directions.kt +++ b/src/main/java/de/bixilon/minosoft/data/Directions.kt @@ -14,7 +14,7 @@ package de.bixilon.minosoft.data import glm_.vec3.Vec3 -enum class Directions (direction: Vec3) { +enum class Directions(direction: Vec3) { DOWN(Vec3(0, -1, 0)), UP(Vec3(0, 1, 0)), NORTH(Vec3(0, 0, -1)), @@ -35,6 +35,8 @@ enum class Directions (direction: Vec3) { companion object { val DIRECTIONS = values() + val SIDES = arrayOf(NORTH, SOUTH, WEST, EAST) + const val SIDES_OFFSET = 2 @JvmStatic fun byId(id: Int): Directions { diff --git a/src/main/java/de/bixilon/minosoft/data/mappings/Dimension.kt b/src/main/java/de/bixilon/minosoft/data/mappings/Dimension.kt index a39187831..4f7df0370 100644 --- a/src/main/java/de/bixilon/minosoft/data/mappings/Dimension.kt +++ b/src/main/java/de/bixilon/minosoft/data/mappings/Dimension.kt @@ -40,6 +40,11 @@ data class Dimension( } else { minY / ProtocolDefinition.SECTION_HEIGHT_Y } + val highestSection = if (height < 0) { + (height + 1) / ProtocolDefinition.SECTION_HEIGHT_Y - 1 + } else { + height / ProtocolDefinition.SECTION_HEIGHT_Y + } override fun toString(): String { return resourceLocation.toString() diff --git a/src/main/java/de/bixilon/minosoft/data/world/BlockPosition.kt b/src/main/java/de/bixilon/minosoft/data/world/BlockPosition.kt index fc33f6afa..09cc00468 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/BlockPosition.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/BlockPosition.kt @@ -12,13 +12,14 @@ */ package de.bixilon.minosoft.data.world +import de.bixilon.minosoft.data.Directions import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition import glm_.vec3.Vec3 data class BlockPosition(val x: Int, val y: Int, val z: Int) { constructor(chunkPosition: ChunkPosition, sectionHeight: Int, inChunkSectionPosition: InChunkSectionPosition) : this(chunkPosition.x * ProtocolDefinition.SECTION_WIDTH_X + inChunkSectionPosition.x, sectionHeight * ProtocolDefinition.SECTION_HEIGHT_Y + inChunkSectionPosition.y, chunkPosition.z * ProtocolDefinition.SECTION_WIDTH_Z + inChunkSectionPosition.z) // ToDo - fun getChunkLocation(): ChunkPosition { + fun getChunkPosition(): ChunkPosition { val chunkX = if (this.x >= 0) { this.x / ProtocolDefinition.SECTION_WIDTH_X } else { @@ -32,7 +33,7 @@ data class BlockPosition(val x: Int, val y: Int, val z: Int) { return ChunkPosition(chunkX, chunkY) } - fun getInChunkLocation(): InChunkPosition { + fun getInChunkPosition(): InChunkPosition { var x: Int = this.x % ProtocolDefinition.SECTION_WIDTH_X if (x < 0) { x += ProtocolDefinition.SECTION_WIDTH_X @@ -44,8 +45,16 @@ data class BlockPosition(val x: Int, val y: Int, val z: Int) { return InChunkPosition(x, this.y, z) } - fun getInChunkSectionLocation(): InChunkSectionPosition { - val location = getInChunkLocation() + infix operator fun plus(vec3: Vec3): BlockPosition { + return BlockPosition((x + vec3.x).toInt(), (y + vec3.y).toInt(), (z + vec3.z).toInt()) + } + + operator fun plus(directions: Directions): BlockPosition { + return this + directions.directionVector + } + + fun getInChunkSectionPosition(): InChunkSectionPosition { + val location = getInChunkPosition() return InChunkSectionPosition(location.x, getSectionHeight(), location.z) } diff --git a/src/main/java/de/bixilon/minosoft/data/world/ChunkPosition.kt b/src/main/java/de/bixilon/minosoft/data/world/ChunkPosition.kt index 542d029ba..79fd24a4d 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/ChunkPosition.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/ChunkPosition.kt @@ -13,19 +13,18 @@ package de.bixilon.minosoft.data.world import de.bixilon.minosoft.data.Directions +import glm_.vec3.Vec3 data class ChunkPosition(val x: Int, val z: Int) { override fun toString(): String { return "($x $z)" } - fun getLocationByDirection(direction: Directions): ChunkPosition { - return when (direction) { - Directions.NORTH -> ChunkPosition(x, z - 1) - Directions.SOUTH -> ChunkPosition(x, z + 1) - Directions.WEST -> ChunkPosition(x - 1, z) - Directions.EAST -> ChunkPosition(x + 1, z) - else -> throw IllegalArgumentException("Chunk location is just 2d") - } + operator fun plus(vec3: Vec3): ChunkPosition { + return ChunkPosition(x + vec3.x.toInt(), z + vec3.z.toInt()) + } + + operator fun plus(direction: Directions): ChunkPosition { + return this + direction.directionVector } } diff --git a/src/main/java/de/bixilon/minosoft/data/world/InChunkPosition.kt b/src/main/java/de/bixilon/minosoft/data/world/InChunkPosition.kt index 6e87e2954..e74ccdbb6 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/InChunkPosition.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/InChunkPosition.kt @@ -13,6 +13,7 @@ package de.bixilon.minosoft.data.world import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition +import glm_.vec3.Vec3 data class InChunkPosition(val x: Int, val y: Int, val z: Int) { @@ -33,6 +34,10 @@ data class InChunkPosition(val x: Int, val y: Int, val z: Int) { } } + operator fun plus(vec3: Vec3): InChunkPosition { + return InChunkPosition((x + vec3.x).toInt(), (y + vec3.y).toInt(), (z + vec3.z).toInt()) + } + override fun toString(): String { return "($x $y $z)" } diff --git a/src/main/java/de/bixilon/minosoft/data/world/InChunkSectionPosition.kt b/src/main/java/de/bixilon/minosoft/data/world/InChunkSectionPosition.kt index c77bf5831..bf9a53988 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/InChunkSectionPosition.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/InChunkSectionPosition.kt @@ -14,6 +14,7 @@ package de.bixilon.minosoft.data.world import de.bixilon.minosoft.data.Directions import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition +import glm_.vec3.Vec3 /** * Chunk X, Y and Z location (max 16x16x16) @@ -28,14 +29,33 @@ data class InChunkSectionPosition(val x: Int, val y: Int, val z: Int) { return InChunkPosition(x, y + ProtocolDefinition.SECTION_HEIGHT_Y * sectionHeight, z) } - fun getLocationByDirection(direction: Directions): InChunkSectionPosition { - return when (direction) { - Directions.DOWN -> InChunkSectionPosition(x, y - 1, z) - Directions.UP -> InChunkSectionPosition(x, y + 1, z) - Directions.NORTH -> InChunkSectionPosition(x, y, z - 1) - Directions.SOUTH -> InChunkSectionPosition(x, y, z + 1) - Directions.WEST -> InChunkSectionPosition(x - 1, y, z) - Directions.EAST -> InChunkSectionPosition(x + 1, y, z) + operator fun plus(vec3: Vec3): InChunkSectionPosition { + var nextX = x + vec3.x.toInt() + var nextY = y + vec3.y.toInt() + var nextZ = z + vec3.z.toInt() + + if (nextX < 0) { + nextX = ProtocolDefinition.SECTION_MAX_X + } else if (nextX > ProtocolDefinition.SECTION_MAX_X) { + nextX = 0 } + + if (nextY < 0) { + nextY = ProtocolDefinition.SECTION_MAX_Y + } else if (nextY > ProtocolDefinition.SECTION_MAX_Y) { + nextY = 0 + } + + if (nextZ < 0) { + nextZ = ProtocolDefinition.SECTION_MAX_Z + } else if (nextZ > ProtocolDefinition.SECTION_MAX_Z) { + nextZ = 0 + } + + return InChunkSectionPosition(nextX, nextY, nextZ) + } + + operator fun plus(directions: Directions): InChunkSectionPosition { + return this + directions.directionVector } } 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 672af1b9a..ec42728a4 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/World.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/World.kt @@ -33,12 +33,12 @@ class World { fun getBlockInfo(blockPosition: BlockPosition): BlockInfo? { - val chunkLocation = blockPosition.getChunkLocation() - return chunks[chunkLocation]?.getBlockInfo(blockPosition.getInChunkLocation()) + val chunkLocation = blockPosition.getChunkPosition() + return chunks[chunkLocation]?.getBlockInfo(blockPosition.getInChunkPosition()) } - fun getChunk(loc: ChunkPosition): Chunk? { - return chunks[loc] + fun getChunk(chunkPosition: ChunkPosition): Chunk? { + return chunks[chunkPosition] } fun getOrCreateChunk(chunkPosition: ChunkPosition): Chunk { @@ -50,7 +50,7 @@ class World { } fun setBlock(blockPosition: BlockPosition, block: BlockState?) { - chunks[blockPosition.getChunkLocation()]?.setRawBlock(blockPosition.getInChunkLocation(), block) + chunks[blockPosition.getChunkPosition()]?.setRawBlock(blockPosition.getInChunkPosition(), block) } fun unloadChunk(position: ChunkPosition) { @@ -94,7 +94,7 @@ class World { } fun setBlockEntityData(position: BlockPosition, data: BlockEntityMetaData?) { - chunks[position.getChunkLocation()]?.sections?.get(position.getSectionHeight())?.getBlockInfo(position.getInChunkSectionLocation())?.metaData = data + chunks[position.getChunkPosition()]?.sections?.get(position.getSectionHeight())?.getBlockInfo(position.getInChunkSectionPosition())?.metaData = data } fun setBlockEntityData(blockEntities: HashMap) { diff --git a/src/main/java/de/bixilon/minosoft/data/world/biome/NoiseBiomeAccessor.kt b/src/main/java/de/bixilon/minosoft/data/world/biome/NoiseBiomeAccessor.kt index 2066341aa..594538b77 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/biome/NoiseBiomeAccessor.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/biome/NoiseBiomeAccessor.kt @@ -22,7 +22,7 @@ class NoiseBiomeAccessor( ) : BiomeAccessor { override fun getBiome(position: BlockPosition): Biome? { - val inChunk = position.getInChunkSectionLocation() + val inChunk = position.getInChunkSectionPosition() val index = inChunk.y * ProtocolDefinition.SECTION_HEIGHT_Y + ((inChunk.z / 4) * 4 + (inChunk.x / 4)) if (index < 0 || index > biomes.size) { return null diff --git a/src/main/java/de/bixilon/minosoft/data/world/light/ChunkLightAccessor.kt b/src/main/java/de/bixilon/minosoft/data/world/light/ChunkLightAccessor.kt index f0f042ecd..19deff0a6 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/light/ChunkLightAccessor.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/light/ChunkLightAccessor.kt @@ -16,13 +16,21 @@ package de.bixilon.minosoft.data.world.light import de.bixilon.minosoft.data.Directions import de.bixilon.minosoft.data.world.BlockPosition import de.bixilon.minosoft.data.world.InChunkPosition +import de.bixilon.minosoft.data.world.World class ChunkLightAccessor( val blockLightLevel: MutableMap = mutableMapOf(), val skyLightLevel: MutableMap = mutableMapOf(), + val world: World, ) : LightAccessor { override fun getLightLevel(blockPosition: BlockPosition, direction: Directions): Int { - val inChunkLocation = blockPosition.getInChunkLocation() - return blockLightLevel[inChunkLocation]?.toInt() ?: 0 + val inChunkPosition = blockPosition.getInChunkPosition() + val lightLevel = blockLightLevel[inChunkPosition] ?: skyLightLevel[inChunkPosition] + + if (lightLevel == null) { + return 1 + } + + return lightLevel.toInt() } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/Camera.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/Camera.kt index fd0dc52f4..4b762da5b 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/Camera.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/Camera.kt @@ -206,14 +206,14 @@ class Camera( headLocation = Position(cameraPosition) feetLocation = Position(headLocation.x, headLocation.y - PLAYER_HEIGHT, headLocation.z) blockPosition = feetLocation.toBlockPosition() - currentBiome = connection.player.world.getChunk(blockPosition.getChunkLocation())?.biomeAccessor?.getBiome(blockPosition) - chunkPosition = blockPosition.getChunkLocation() + currentBiome = connection.player.world.getChunk(blockPosition.getChunkPosition())?.biomeAccessor?.getBiome(blockPosition) + chunkPosition = blockPosition.getChunkPosition() sectionHeight = blockPosition.getSectionHeight() - inChunkSectionPosition = blockPosition.getInChunkSectionLocation() + inChunkSectionPosition = blockPosition.getInChunkSectionPosition() // recalculate sky color for current biome val blockPosition = Position(cameraPosition).toBlockPosition() - renderWindow.setSkyColor(connection.player.world.getChunk(blockPosition.getChunkLocation())?.biomeAccessor?.getBiome(blockPosition)?.skyColor ?: RenderConstants.DEFAULT_SKY_COLOR) + renderWindow.setSkyColor(connection.player.world.getChunk(blockPosition.getChunkPosition())?.biomeAccessor?.getBiome(blockPosition)?.skyColor ?: RenderConstants.DEFAULT_SKY_COLOR) connection.renderer.renderWindow.worldRenderer.recalculateFrustum(Frustum(this)) connection.player.world.dimension?.hasSkyLight?.let { if (it) { diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/WorldRenderer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/WorldRenderer.kt index ef00dfaef..52094a170 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/WorldRenderer.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/WorldRenderer.kt @@ -13,6 +13,7 @@ package de.bixilon.minosoft.gui.rendering.chunk +import com.google.common.collect.ConcurrentHashMultiset import de.bixilon.minosoft.Minosoft import de.bixilon.minosoft.config.StaticConfiguration import de.bixilon.minosoft.config.config.game.controls.KeyBindingsNames @@ -27,7 +28,6 @@ import de.bixilon.minosoft.gui.rendering.textures.Texture import de.bixilon.minosoft.gui.rendering.textures.TextureArray import de.bixilon.minosoft.protocol.network.Connection import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition -import de.bixilon.minosoft.util.logging.Log import org.lwjgl.opengl.GL11.GL_CULL_FACE import org.lwjgl.opengl.GL11.glEnable import org.lwjgl.opengl.GL13.glDisable @@ -36,64 +36,30 @@ import java.util.concurrent.ConcurrentHashMap class WorldRenderer(private val connection: Connection, private val world: World, val renderWindow: RenderWindow) : Renderer { private lateinit var minecraftTextures: TextureArray lateinit var chunkShader: Shader - private val chunkSectionsToDraw = ConcurrentHashMap>() - private val visibleChunks: MutableSet = mutableSetOf() + val chunkSectionsToDraw = ConcurrentHashMap>() + val visibleChunks: MutableSet = mutableSetOf() private lateinit var frustum: Frustum private var currentTick = 0 // for animation usage private var lastTickIncrementTime = 0L + val queuedChunks: ConcurrentHashMultiset = ConcurrentHashMultiset.create() - private fun prepareChunk(chunkPosition: ChunkPosition, sectionHeight: Int, section: ChunkSection, chunk: Chunk): ChunkMesh { + private fun prepareChunk(chunkPosition: ChunkPosition, sectionHeight: Int, section: ChunkSection): ChunkMesh { + queuedChunks.remove(chunkPosition) if (frustum.containsChunk(chunkPosition, connection)) { visibleChunks.add(chunkPosition) } + val chunk = world.getChunk(chunkPosition)!! + val mesh = ChunkMesh() - val below = world.chunks[chunkPosition]?.sections?.get(sectionHeight - 1) - val above = world.chunks[chunkPosition]?.sections?.get(sectionHeight + 1) - //val north = (world.allChunks[chunkLocation.getLocationByDirection(Directions.NORTH)]?: throw ChunkNotLoadedException("North not loaded")).sections?.get(sectionHeight) - //val south = (world.allChunks[chunkLocation.getLocationByDirection(Directions.SOUTH)]?: throw ChunkNotLoadedException("South not loaded")).sections?.get(sectionHeight) - //val west = (world.allChunks[chunkLocation.getLocationByDirection(Directions.WEST)]?: throw ChunkNotLoadedException("West not loaded")).sections?.get(sectionHeight) - //val east = (world.allChunks[chunkLocation.getLocationByDirection(Directions.EAST)]?: throw ChunkNotLoadedException("North not loaded")).sections?.get(sectionHeight) - val north = world.chunks[chunkPosition.getLocationByDirection(Directions.NORTH)]?.sections?.get(sectionHeight) - val south = world.chunks[chunkPosition.getLocationByDirection(Directions.SOUTH)]?.sections?.get(sectionHeight) - val west = world.chunks[chunkPosition.getLocationByDirection(Directions.WEST)]?.sections?.get(sectionHeight) - val east = world.chunks[chunkPosition.getLocationByDirection(Directions.EAST)]?.sections?.get(sectionHeight) - for ((position, blockInfo) in section.blocks) { - val blockBelow: BlockInfo? = if (position.y == 0 && below != null) { - below.getBlockInfo(position.x, ProtocolDefinition.SECTION_HEIGHT_Y - 1, position.z) - } else { - section.getBlockInfo(position.getLocationByDirection(Directions.DOWN)) - } - val blockAbove: BlockInfo? = if (position.y == ProtocolDefinition.SECTION_HEIGHT_Y - 1 && above != null) { - above.getBlockInfo(position.x, 0, position.z) - } else { - section.getBlockInfo(position.getLocationByDirection(Directions.UP)) - } - val blockNorth: BlockInfo? = if (position.z == 0 && north != null) { - north.getBlockInfo(position.x, position.y, ProtocolDefinition.SECTION_WIDTH_Z - 1) - } else { - section.getBlockInfo(position.getLocationByDirection(Directions.NORTH)) - } - val blockSouth: BlockInfo? = if (position.z == ProtocolDefinition.SECTION_WIDTH_Z - 1 && south != null) { - south.getBlockInfo(position.x, position.y, 0) - } else { - section.getBlockInfo(position.getLocationByDirection(Directions.SOUTH)) - } - val blockWest: BlockInfo? = if (position.x == 0 && west != null) { - west.getBlockInfo(ProtocolDefinition.SECTION_WIDTH_X - 1, position.y, position.x) - } else { - section.getBlockInfo(position.getLocationByDirection(Directions.WEST)) - } - val blockEast: BlockInfo? = if (position.x == ProtocolDefinition.SECTION_WIDTH_X - 1 && east != null) { - east.getBlockInfo(0, position.y, position.z) - } else { - section.getBlockInfo(position.getLocationByDirection(Directions.EAST)) - } val blockPosition = BlockPosition(chunkPosition, sectionHeight, position) - if (blockPosition == BlockPosition(-103, 3, 288)) { - Log.debug("") + + val neighborBlocks: Array = arrayOfNulls(Directions.DIRECTIONS.size) + for (direction in Directions.DIRECTIONS) { + neighborBlocks[direction.ordinal] = world.getBlockInfo(blockPosition + direction) } + val biome = chunk.biomeAccessor!!.getBiome(blockPosition) var tintColor: RGBColor? = null @@ -111,7 +77,7 @@ class WorldRenderer(private val connection: Connection, private val world: World blockInfo.block.tintColor?.let { tintColor = it } } - blockInfo.block.getBlockRenderer(blockPosition).render(blockInfo, chunk.lightAccessor!!, tintColor, blockPosition, mesh, arrayOf(blockBelow, blockAbove, blockNorth, blockSouth, blockWest, blockEast)) + blockInfo.block.getBlockRenderer(blockPosition).render(blockInfo, chunk.lightAccessor!!, tintColor, blockPosition, mesh, neighborBlocks) } return mesh } @@ -173,19 +139,54 @@ class WorldRenderer(private val connection: Connection, private val world: World return textures } - fun prepareChunk(chunkPosition: ChunkPosition, chunk: Chunk) { - chunkSectionsToDraw[chunkPosition] = ConcurrentHashMap() - if (!chunk.isFullyLoaded) { + fun prepareChunk(chunkPosition: ChunkPosition, chunk: Chunk? = world.getChunk(chunkPosition), checkQueued: Boolean = true) { + if (chunk == null || !chunk.isFullyLoaded) { return } + + val neighborsChunkPositions: Array = arrayOf( + chunkPosition + Directions.NORTH, + chunkPosition + Directions.SOUTH, + chunkPosition + Directions.WEST, + chunkPosition + Directions.EAST, + ) + + // ensure all neighbor chunks are loaded + for (direction in Directions.SIDES) { + val neighborChunk = world.chunks[chunkPosition + direction] + if (neighborChunk == null || !neighborChunk.isFullyLoaded) { + // neighbors not loaded, doing later + if (checkQueued) { + checkQueuedChunks(neighborsChunkPositions) + } + queuedChunks.add(chunkPosition) + return + } + } + queuedChunks.remove(chunkPosition) + chunkSectionsToDraw[chunkPosition] = ConcurrentHashMap() + for ((sectionHeight, section) in chunk.sections!!) { - prepareChunkSection(chunkPosition, sectionHeight, section, chunk) + prepareChunkSection(chunkPosition, sectionHeight, section) + } + + if (checkQueued) { + checkQueuedChunks(neighborsChunkPositions) + } + + } + + private fun checkQueuedChunks(chunkPositions: Array) { + for (position in chunkPositions) { + if (queuedChunks.contains(position)) { + prepareChunk(position, checkQueued = false) + } } } - fun prepareChunkSection(chunkPosition: ChunkPosition, sectionHeight: Int, section: ChunkSection, chunk: Chunk) { + fun prepareChunkSection(chunkPosition: ChunkPosition, sectionHeight: Int, section: ChunkSection) { renderWindow.rendering.executor.execute { - val mesh = prepareChunk(chunkPosition, sectionHeight, section, chunk) + val mesh = prepareChunk(chunkPosition, sectionHeight, section) var sectionMap = chunkSectionsToDraw[chunkPosition] if (sectionMap == null) { @@ -201,6 +202,7 @@ class WorldRenderer(private val connection: Connection, private val world: World } fun clearChunkCache() { + queuedChunks.clear() renderWindow.renderQueue.add { for ((location, map) in chunkSectionsToDraw) { for ((sectionHeight, mesh) in map) { @@ -213,6 +215,7 @@ class WorldRenderer(private val connection: Connection, private val world: World } fun unloadChunk(chunkPosition: ChunkPosition) { + queuedChunks.remove(chunkPosition) renderWindow.renderQueue.add { chunkSectionsToDraw[chunkPosition]?.let { for ((_, mesh) in it) { @@ -243,8 +246,8 @@ class WorldRenderer(private val connection: Connection, private val world: World } } } - - fun getChunkSize(): Int { - return chunkSectionsToDraw.size - } +} + +private operator fun Int.plus(upOrDown: Directions): Int { + return this + upOrDown.directionVector.y.toInt() } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/renderable/ElementRenderer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/renderable/ElementRenderer.kt index 69b3667a0..ec360f49c 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/renderable/ElementRenderer.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/renderable/ElementRenderer.kt @@ -98,9 +98,11 @@ class ElementRenderer(element: BlockModelElement, rotation: Vec3, uvLock: Boolea } companion object { + val EMPTY_VECTOR = Vec3() + fun createElements(state: JsonObject, parent: BlockModel): MutableList { val rotation = glm.radians(vec3InJsonObject(state)) - val uvlock = state["uvlock"]?.asBoolean ?: false + val uvLock = state["uvlock"]?.asBoolean ?: false val rescale = state["rescale"]?.asBoolean ?: false val parentElements = parent.elements val result: MutableList = mutableListOf() @@ -139,7 +141,7 @@ class ElementRenderer(element: BlockModelElement, rotation: Vec3, uvLock: Boolea } fun getRotatedDirection(rotation: Vec3, direction: Directions): Directions { - if (rotation == Vec3(0, 0, 0)) { + if (rotation == EMPTY_VECTOR) { return direction } var rotatedDirectionVector = BlockModelElement.rotateVector(direction.directionVector, rotation.z.toDouble(), Axes.Z) @@ -148,7 +150,7 @@ class ElementRenderer(element: BlockModelElement, rotation: Vec3, uvLock: Boolea } fun rotatePositionsAxes(positions: Array, angles: Vec3, rescale: Boolean) { - if (angles == Vec3()) { + if (angles == EMPTY_VECTOR) { return } BlockModelElement.rotatePositions(positions, Axes.X, angles.x.toDouble(), Vec3(), rescale) diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/exceptions/ChunkNotLoadedException.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/exceptions/ChunkNotLoadedException.kt index 71e55897a..97553cdb7 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/exceptions/ChunkNotLoadedException.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/exceptions/ChunkNotLoadedException.kt @@ -13,4 +13,4 @@ package de.bixilon.minosoft.gui.rendering.exceptions -class ChunkNotLoadedException(message: String) : Exception(message) +class ChunkNotLoadedException(message: String = "Chunk is unloaded") : Exception(message) diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/text/HUDDebugScreenElement.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/text/HUDDebugScreenElement.kt index 747ec0a17..a7b899cc1 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/text/HUDDebugScreenElement.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/text/HUDDebugScreenElement.kt @@ -17,6 +17,7 @@ import de.bixilon.minosoft.config.StaticConfiguration import de.bixilon.minosoft.config.config.game.controls.KeyBindingsNames import de.bixilon.minosoft.config.key.KeyAction import de.bixilon.minosoft.config.key.KeyCodes +import de.bixilon.minosoft.data.Directions import de.bixilon.minosoft.gui.rendering.font.FontBindings import de.bixilon.minosoft.modding.loading.ModLoader import de.bixilon.minosoft.util.GitInfo @@ -49,8 +50,8 @@ class HUDDebugScreenElement(private val hudTextElement: HUDTextElement) : HUDTex chatComponents[FontBindings.LEFT_UP]!!.addAll(listOf( "FPS: ${getFPS()}", "Timings: avg ${getAvgFrameTime()}ms, min ${getMinFrameTime()}ms, max ${getMaxFrameTime()}ms", - "Chunks: ${hudTextElement.renderWindow.worldRenderer.getChunkSize()} / ${hudTextElement.connection.player.world.chunks.size}", - "Connected to ${hudTextElement.connection.address} with ${hudTextElement.connection.version}", + "Chunks: q=${hudTextElement.renderWindow.worldRenderer.queuedChunks.size} v=${hudTextElement.renderWindow.worldRenderer.visibleChunks.size} p=${hudTextElement.renderWindow.worldRenderer.chunkSectionsToDraw.size} t=${hudTextElement.connection.player.world.chunks.size}", + "Connected to ${hudTextElement.connection.address} on ${hudTextElement.connection.version} with ${hudTextElement.connection.player.account.username}", "", "XYZ ${getLocation()}", "Block ${getBlockPosition()}", @@ -159,7 +160,7 @@ class HUDDebugScreenElement(private val hudTextElement: HUDTextElement) : HUDTex private fun getFacing(): String { val yaw = hudTextElement.renderWindow.camera.yaw val pitch = hudTextElement.renderWindow.camera.pitch - return "todo (${formatRotation(yaw)} / ${formatRotation(pitch)})" + return "${Directions.byDirection(camera.cameraFront).name.toLowerCase()} (${formatRotation(yaw)} / ${formatRotation(pitch)})" } diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketBlockChange.java b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketBlockChange.java deleted file mode 100644 index 74027696e..000000000 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketBlockChange.java +++ /dev/null @@ -1,81 +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 . - * - * This software is not affiliated with Mojang AB, the original developer of Minecraft. - */ - -package de.bixilon.minosoft.protocol.packets.clientbound.play; - -import de.bixilon.minosoft.data.mappings.blocks.BlockState; -import de.bixilon.minosoft.data.mappings.tweaker.VersionTweaker; -import de.bixilon.minosoft.data.world.BlockPosition; -import de.bixilon.minosoft.data.world.Chunk; -import de.bixilon.minosoft.data.world.ChunkSection; -import de.bixilon.minosoft.modding.event.events.BlockChangeEvent; -import de.bixilon.minosoft.protocol.network.Connection; -import de.bixilon.minosoft.protocol.packets.ClientboundPacket; -import de.bixilon.minosoft.protocol.protocol.InByteBuffer; -import de.bixilon.minosoft.util.logging.Log; - -import static de.bixilon.minosoft.protocol.protocol.ProtocolVersions.V_14W03B; - -public class PacketBlockChange extends ClientboundPacket { - BlockPosition position; - BlockState block; - - @Override - public boolean read(InByteBuffer buffer) { - if (buffer.getVersionId() < V_14W03B) { - this.position = buffer.readBlockPositionByte(); - this.block = buffer.getConnection().getMapping().getBlockState((buffer.readVarInt() << 4) | buffer.readByte()); // ToDo: When was the meta data "compacted"? (between 1.7.10 - 1.8) - return true; - } - this.position = buffer.readPosition(); - this.block = buffer.getConnection().getMapping().getBlockState(buffer.readVarInt()); - return true; - - } - - @Override - public void handle(Connection connection) { - Chunk chunk = connection.getPlayer().getWorld().getChunk(getPosition().getChunkLocation()); - if (chunk == null) { - // thanks mojang - return; - } - connection.fireEvent(new BlockChangeEvent(connection, this)); - - int sectionHeight = getPosition().getSectionHeight(); - ChunkSection section = chunk.getSectionOrCreate(sectionHeight); - - // tweak - if (!connection.getVersion().isFlattened()) { - BlockState block = VersionTweaker.transformBlock(getBlock(), chunk.getSections(), this.position.getInChunkSectionLocation(), this.position.getSectionHeight()); - section.setRawBlock(getPosition().getInChunkLocation().getInChunkSectionLocation(), block); - } else { - section.setRawBlock(getPosition().getInChunkLocation().getInChunkSectionLocation(), getBlock()); - } - - connection.getRenderer().getRenderWindow().getWorldRenderer().prepareChunkSection(getPosition().getChunkLocation(), sectionHeight, section, chunk); - } - - @Override - public void log() { - Log.protocol(String.format("[IN] Block change received at %s (block=%s)", this.position, this.block)); - } - - public BlockPosition getPosition() { - return this.position; - } - - public BlockState getBlock() { - return this.block; - } -} diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketBlockChange.kt b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketBlockChange.kt new file mode 100644 index 000000000..29576fe5d --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketBlockChange.kt @@ -0,0 +1,63 @@ +/* + * 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 . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ +package de.bixilon.minosoft.protocol.packets.clientbound.play + +import de.bixilon.minosoft.data.mappings.blocks.BlockState +import de.bixilon.minosoft.data.mappings.tweaker.VersionTweaker +import de.bixilon.minosoft.data.world.BlockPosition +import de.bixilon.minosoft.modding.event.events.BlockChangeEvent +import de.bixilon.minosoft.protocol.network.Connection +import de.bixilon.minosoft.protocol.packets.ClientboundPacket +import de.bixilon.minosoft.protocol.protocol.InByteBuffer +import de.bixilon.minosoft.protocol.protocol.ProtocolVersions +import de.bixilon.minosoft.util.logging.Log + +class PacketBlockChange : ClientboundPacket() { + lateinit var position: BlockPosition + var block: BlockState? = null + override fun read(buffer: InByteBuffer): Boolean { + if (buffer.versionId < ProtocolVersions.V_14W03B) { + position = buffer.readBlockPositionByte() + block = buffer.connection.mapping.getBlockState(buffer.readVarInt() shl 4 or buffer.readByte().toInt()) // ToDo: When was the meta data "compacted"? (between 1.7.10 - 1.8) + return true + } + position = buffer.readPosition() + block = buffer.connection.mapping.getBlockState(buffer.readVarInt()) + return true + } + + override fun handle(connection: Connection) { + val chunk = connection.player.world.getChunk(position.getChunkPosition()) ?: return // thanks mojang + if (!chunk.isFullyLoaded) { + return + } + connection.fireEvent(BlockChangeEvent(connection, this)) + val sectionHeight = position.getSectionHeight() + val inChunkSectionPosition = position.getInChunkSectionPosition() + val section = chunk.getSectionOrCreate(sectionHeight) + + // tweak + if (!connection.version.isFlattened()) { + val block = VersionTweaker.transformBlock(block!!, chunk.sections!!, inChunkSectionPosition, sectionHeight) + section.setRawBlock(inChunkSectionPosition, block) + } else { + section.setRawBlock(inChunkSectionPosition, block) + } + + connection.renderer.renderWindow.worldRenderer.prepareChunkSection(position.getChunkPosition(), sectionHeight, section) + } + + override fun log() { + Log.protocol(String.format("[IN] Block change received at %s (block=%s)", position, block)) + } +} diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketChunkBulk.kt b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketChunkBulk.kt index 2d92fef31..1162fd2cf 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketChunkBulk.kt +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketChunkBulk.kt @@ -20,8 +20,8 @@ import de.bixilon.minosoft.protocol.network.Connection import de.bixilon.minosoft.protocol.packets.ClientboundPacket import de.bixilon.minosoft.protocol.protocol.InByteBuffer import de.bixilon.minosoft.protocol.protocol.ProtocolVersions -import de.bixilon.minosoft.util.ChunkUtil import de.bixilon.minosoft.util.Util +import de.bixilon.minosoft.util.chunk.ChunkUtil import de.bixilon.minosoft.util.logging.Log import java.util.* @@ -44,7 +44,7 @@ class PacketChunkBulk : ClientboundPacket() { // chunk meta data for (i in 0 until chunkCount) { - val chunkPosition = buffer.readChunkLocation() + val chunkPosition = buffer.readChunkPosition() val sectionBitMask = longArrayOf(buffer.readUnsignedShort().toLong()) val addBitMask = buffer.readUnsignedShort() data[chunkPosition] = ChunkUtil.readChunkPacket(decompressed, dimension, sectionBitMask, addBitMask, true, containsSkyLight) diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketChunkData.kt b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketChunkData.kt index 9f1eb64c4..8ccb76926 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketChunkData.kt +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketChunkData.kt @@ -24,8 +24,8 @@ import de.bixilon.minosoft.protocol.network.Connection import de.bixilon.minosoft.protocol.packets.ClientboundPacket import de.bixilon.minosoft.protocol.protocol.InByteBuffer import de.bixilon.minosoft.protocol.protocol.ProtocolVersions -import de.bixilon.minosoft.util.ChunkUtil import de.bixilon.minosoft.util.Util +import de.bixilon.minosoft.util.chunk.ChunkUtil import de.bixilon.minosoft.util.logging.Log import de.bixilon.minosoft.util.nbt.tag.CompoundTag import java.util.* diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketMultiBlockChange.java b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketMultiBlockChange.java deleted file mode 100644 index d4931ac0e..000000000 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketMultiBlockChange.java +++ /dev/null @@ -1,129 +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 . - * - * This software is not affiliated with Mojang AB, the original developer of Minecraft. - */ - -package de.bixilon.minosoft.protocol.packets.clientbound.play; - -import de.bixilon.minosoft.data.mappings.blocks.BlockState; -import de.bixilon.minosoft.data.mappings.tweaker.VersionTweaker; -import de.bixilon.minosoft.data.world.Chunk; -import de.bixilon.minosoft.data.world.ChunkPosition; -import de.bixilon.minosoft.data.world.ChunkSection; -import de.bixilon.minosoft.data.world.InChunkPosition; -import de.bixilon.minosoft.modding.event.events.MultiBlockChangeEvent; -import de.bixilon.minosoft.protocol.network.Connection; -import de.bixilon.minosoft.protocol.packets.ClientboundPacket; -import de.bixilon.minosoft.protocol.protocol.InByteBuffer; -import de.bixilon.minosoft.util.logging.Log; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; - -import static de.bixilon.minosoft.protocol.protocol.ProtocolVersions.*; - -public class PacketMultiBlockChange extends ClientboundPacket { - private final HashMap blocks = new HashMap<>(); - private ChunkPosition chunkPosition; - - @Override - public boolean read(InByteBuffer buffer) { - if (buffer.getVersionId() < V_14W26C) { - if (buffer.getVersionId() < V_1_7_5) { - this.chunkPosition = new ChunkPosition(buffer.readVarInt(), buffer.readVarInt()); - } else { - this.chunkPosition = new ChunkPosition(buffer.readInt(), buffer.readInt()); - } - short count = buffer.readShort(); - int dataSize = buffer.readInt(); // should be count * 4 - for (int i = 0; i < count; i++) { - int raw = buffer.readInt(); - byte meta = (byte) (raw & 0xF); - short blockId = (short) ((raw & 0xFF_F0) >>> 4); - byte y = (byte) ((raw & 0xFF_00_00) >>> 16); - byte z = (byte) ((raw & 0x0F_00_00_00) >>> 24); - byte x = (byte) ((raw & 0xF0_00_00_00) >>> 28); - this.blocks.put(new InChunkPosition(x, y, z), buffer.getConnection().getMapping().getBlockState((blockId << 4) | meta)); - } - return true; - } - if (buffer.getVersionId() < V_20W28A) { - this.chunkPosition = new ChunkPosition(buffer.readInt(), buffer.readInt()); - int count = buffer.readVarInt(); - for (int i = 0; i < count; i++) { - byte pos = buffer.readByte(); - byte y = buffer.readByte(); - int blockId = buffer.readVarInt(); - this.blocks.put(new InChunkPosition((pos & 0xF0 >>> 4) & 0xF, y, pos & 0xF), buffer.getConnection().getMapping().getBlockState(blockId)); - } - return true; - } - long rawPos = buffer.readLong(); - this.chunkPosition = new ChunkPosition((int) (rawPos >> 42), (int) (rawPos << 22 >> 42)); - int yOffset = ((int) rawPos & 0xFFFFF) * 16; - if (buffer.getVersionId() > V_1_16_2_PRE3) { - buffer.readBoolean(); // ToDo - } - int count = buffer.readVarInt(); - for (int i = 0; i < count; i++) { - long data = buffer.readVarLong(); - this.blocks.put(new InChunkPosition((int) ((data >> 8) & 0xF), yOffset + (int) ((data >> 4) & 0xF), (int) (data & 0xF)), buffer.getConnection().getMapping().getBlockState(((int) (data >>> 12)))); - } - return true; - } - - @Override - public void handle(Connection connection) { - Chunk chunk = connection.getPlayer().getWorld().getChunk(getChunkPosition()); - if (chunk == null) { - // thanks mojang - return; - } - connection.fireEvent(new MultiBlockChangeEvent(connection, this)); - chunk.setRawBlocks(getBlocks()); - - // tweak - if (!connection.getVersion().isFlattened()) { - for (Map.Entry entry : getBlocks().entrySet()) { - BlockState block = VersionTweaker.transformBlock(entry.getValue(), chunk.getSections(), entry.getKey().getInChunkSectionLocation(), entry.getKey().getSectionHeight()); - if (block == entry.getValue()) { - continue; - } - chunk.setRawBlock(entry.getKey(), block); - } - } - - HashSet sectionHeights = new HashSet<>(); - - for (var entry : this.blocks.entrySet()) { - sectionHeights.add(entry.getKey().getSectionHeight()); - } - - for (var sectionHeight : sectionHeights) { - ChunkSection section = chunk.getSectionOrCreate(sectionHeight); - connection.getRenderer().getRenderWindow().getWorldRenderer().prepareChunkSection(getChunkPosition(), sectionHeight, section, chunk); - } - } - - @Override - public void log() { - Log.protocol(String.format("[IN] Multi block change received at %s (size=%d)", this.chunkPosition, this.blocks.size())); - } - - public ChunkPosition getChunkPosition() { - return this.chunkPosition; - } - - public HashMap getBlocks() { - return this.blocks; - } -} diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketMultiBlockChange.kt b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketMultiBlockChange.kt new file mode 100644 index 000000000..d13fe7e42 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketMultiBlockChange.kt @@ -0,0 +1,108 @@ +/* + * 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 . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ +package de.bixilon.minosoft.protocol.packets.clientbound.play + +import de.bixilon.minosoft.data.mappings.blocks.BlockState +import de.bixilon.minosoft.data.mappings.tweaker.VersionTweaker +import de.bixilon.minosoft.data.world.ChunkPosition +import de.bixilon.minosoft.data.world.InChunkPosition +import de.bixilon.minosoft.modding.event.events.MultiBlockChangeEvent +import de.bixilon.minosoft.protocol.network.Connection +import de.bixilon.minosoft.protocol.packets.ClientboundPacket +import de.bixilon.minosoft.protocol.protocol.InByteBuffer +import de.bixilon.minosoft.protocol.protocol.ProtocolVersions +import de.bixilon.minosoft.util.logging.Log +import java.util.* + +class PacketMultiBlockChange : ClientboundPacket() { + val blocks = HashMap() + var chunkPosition: ChunkPosition? = null + private set + + override fun read(buffer: InByteBuffer): Boolean { + if (buffer.versionId < ProtocolVersions.V_14W26C) { + chunkPosition = if (buffer.versionId < ProtocolVersions.V_1_7_5) { + ChunkPosition(buffer.readVarInt(), buffer.readVarInt()) + } else { + buffer.readChunkPosition() + } + val count = buffer.readShort() + val dataSize = buffer.readInt() // should be count * 4 + for (i in 0 until count) { + val raw = buffer.readInt() + val meta = (raw and 0xF) + val blockId = (raw and 0xFFF0 ushr 4) + val y = (raw and 0xFF0000 ushr 16) + val z = (raw and 0x0F000000 ushr 24) + val x = (raw and -0x10000000 ushr 28) + blocks[InChunkPosition(x, y, z)] = buffer.connection.mapping.getBlockState((blockId shl 4) or meta) + } + return true + } + if (buffer.versionId < ProtocolVersions.V_20W28A) { + chunkPosition = ChunkPosition(buffer.readInt(), buffer.readInt()) + val count = buffer.readVarInt() + for (i in 0 until count) { + val position = buffer.readByte().toInt() + val y = buffer.readByte() + val blockId = buffer.readVarInt() + blocks[InChunkPosition(position and 0xF0 ushr 4 and 0xF, y.toInt(), position and 0xF)] = buffer.connection.mapping.getBlockState(blockId) + } + return true + } + val rawPos = buffer.readLong() + chunkPosition = ChunkPosition((rawPos shr 42).toInt(), (rawPos shl 22 shr 42).toInt()) + val yOffset = (rawPos.toInt() and 0xFFFFF) * 16 + if (buffer.versionId > ProtocolVersions.V_1_16_2_PRE3) { + buffer.readBoolean() // ToDo + } + val count = buffer.readVarInt() + for (i in 0 until count) { + val data = buffer.readVarLong() + blocks[InChunkPosition((data shr 8 and 0xF).toInt(), yOffset + (data shr 4 and 0xF).toInt(), (data and 0xF).toInt())] = buffer.connection.mapping.getBlockState((data ushr 12).toInt()) + } + return true + } + + override fun handle(connection: Connection) { + val chunk = connection.player.world.getChunk(chunkPosition!!) ?: return // thanks mojang + if (!chunk.isFullyLoaded) { + return + } + connection.fireEvent(MultiBlockChangeEvent(connection, this)) + chunk.setRawBlocks(blocks) + + // tweak + if (!connection.version.isFlattened()) { + for ((key, value) in blocks) { + val block = VersionTweaker.transformBlock(value!!, chunk.sections!!, key.getInChunkSectionLocation(), key.getSectionHeight()) + if (block === value) { + continue + } + chunk.setRawBlock(key, block) + } + } + val sectionHeights = HashSet() + for ((key) in blocks) { + sectionHeights.add(key.getSectionHeight()) + } + for (sectionHeight in sectionHeights) { + val section = chunk.getSectionOrCreate(sectionHeight) + connection.renderer.renderWindow.worldRenderer.prepareChunkSection(chunkPosition!!, sectionHeight, section) + } + } + + override fun log() { + Log.protocol(String.format("[IN] Multi block change received at %s (size=%d)", chunkPosition, blocks.size)) + } +} diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketUpdateLight.java b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketUpdateLight.java deleted file mode 100644 index 09964d110..000000000 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketUpdateLight.java +++ /dev/null @@ -1,70 +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 . - * - * This software is not affiliated with Mojang AB, the original developer of Minecraft. - */ - -package de.bixilon.minosoft.protocol.packets.clientbound.play; - -import de.bixilon.minosoft.data.world.Chunk; -import de.bixilon.minosoft.data.world.ChunkPosition; -import de.bixilon.minosoft.data.world.light.LightAccessor; -import de.bixilon.minosoft.protocol.network.Connection; -import de.bixilon.minosoft.protocol.packets.ClientboundPacket; -import de.bixilon.minosoft.protocol.protocol.InByteBuffer; -import de.bixilon.minosoft.util.ChunkUtil; -import de.bixilon.minosoft.util.logging.Log; - -import static de.bixilon.minosoft.protocol.protocol.ProtocolVersions.V_1_16_PRE3; -import static de.bixilon.minosoft.protocol.protocol.ProtocolVersions.V_20W49A; - -public class PacketUpdateLight extends ClientboundPacket { - private ChunkPosition position; - private LightAccessor lightAccessor; - - @Override - public boolean read(InByteBuffer buffer) { - this.position = new ChunkPosition(buffer.readVarInt(), buffer.readVarInt()); - if (buffer.getVersionId() >= V_1_16_PRE3) { - boolean trustEdges = buffer.readBoolean(); - } - - long[] skyLightMask; - long[] blockLightMask; - long[] emptySkyLightMask; - long[] emptyBlockLightMask; - if (buffer.getVersionId() < V_20W49A) { - // was a varInt before 20w45a, should we change this? - skyLightMask = new long[]{buffer.readVarLong()}; - blockLightMask = new long[]{buffer.readVarLong()}; - emptyBlockLightMask = new long[]{buffer.readVarLong()}; - emptySkyLightMask = new long[]{buffer.readVarLong()}; - } else { - skyLightMask = buffer.readLongArray(); - blockLightMask = buffer.readLongArray(); - emptySkyLightMask = buffer.readLongArray(); - emptyBlockLightMask = buffer.readLongArray(); - } - this.lightAccessor = ChunkUtil.readSkyLightPacket(buffer, skyLightMask, blockLightMask, emptyBlockLightMask, emptySkyLightMask); - return true; - } - - @Override - public void log() { - Log.protocol("[IN] Received light update (position=%s)", this.position); - } - - @Override - public void handle(Connection connection) { - Chunk chunk = connection.getPlayer().getWorld().getOrCreateChunk(this.position); - chunk.setLightAccessor(this.lightAccessor); - connection.getRenderer().getRenderWindow().getWorldRenderer().prepareChunk(this.position, chunk); - } -} diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketUpdateLight.kt b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketUpdateLight.kt new file mode 100644 index 000000000..2b8b50c37 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketUpdateLight.kt @@ -0,0 +1,67 @@ +/* + * 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 . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ +package de.bixilon.minosoft.protocol.packets.clientbound.play + +import de.bixilon.minosoft.data.world.ChunkPosition +import de.bixilon.minosoft.data.world.light.LightAccessor +import de.bixilon.minosoft.protocol.network.Connection +import de.bixilon.minosoft.protocol.packets.ClientboundPacket +import de.bixilon.minosoft.protocol.protocol.InByteBuffer +import de.bixilon.minosoft.protocol.protocol.ProtocolVersions +import de.bixilon.minosoft.util.chunk.LightUtil.readLightPacket +import de.bixilon.minosoft.util.logging.Log + +class PacketUpdateLight : ClientboundPacket() { + private var position: ChunkPosition? = null + private var lightAccessor: LightAccessor? = null + override fun read(buffer: InByteBuffer): Boolean { + position = ChunkPosition(buffer.readVarInt(), buffer.readVarInt()) + + if (position == ChunkPosition(-1, 21)) { + Log.debug("") + } + if (buffer.versionId >= ProtocolVersions.V_1_16_PRE3) { + val trustEdges = buffer.readBoolean() + } + val skyLightMask: LongArray + val blockLightMask: LongArray + val emptySkyLightMask: LongArray + val emptyBlockLightMask: LongArray + if (buffer.versionId < ProtocolVersions.V_20W49A) { + // was a varInt before 20w45a, should we change this? + skyLightMask = longArrayOf(buffer.readVarLong()) + blockLightMask = longArrayOf(buffer.readVarLong()) + emptyBlockLightMask = longArrayOf(buffer.readVarLong()) + emptySkyLightMask = longArrayOf(buffer.readVarLong()) + } else { + skyLightMask = buffer.readLongArray() + blockLightMask = buffer.readLongArray() + emptySkyLightMask = buffer.readLongArray() + emptyBlockLightMask = buffer.readLongArray() + } + + + lightAccessor = readLightPacket(buffer, skyLightMask, blockLightMask, emptyBlockLightMask, emptySkyLightMask, buffer.connection.player.world.dimension!!) + return true + } + + override fun log() { + Log.protocol("[IN] Received light update (position=%s)", position) + } + + override fun handle(connection: Connection) { + val chunk = connection.player.world.getOrCreateChunk(position!!) + chunk.lightAccessor = lightAccessor + connection.renderer.renderWindow.worldRenderer.prepareChunk(position!!, chunk) + } +} diff --git a/src/main/java/de/bixilon/minosoft/protocol/protocol/InByteBuffer.java b/src/main/java/de/bixilon/minosoft/protocol/protocol/InByteBuffer.java index 6ad211e02..29a47dc52 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/protocol/InByteBuffer.java +++ b/src/main/java/de/bixilon/minosoft/protocol/protocol/InByteBuffer.java @@ -592,7 +592,7 @@ public class InByteBuffer { return new ResourceLocation(resourceLocation); } - public ChunkPosition readChunkLocation() { + public ChunkPosition readChunkPosition() { return new ChunkPosition(readInt(), readInt()); } } diff --git a/src/main/java/de/bixilon/minosoft/protocol/protocol/ProtocolDefinition.java b/src/main/java/de/bixilon/minosoft/protocol/protocol/ProtocolDefinition.java index 8d063d433..400f72aaa 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/protocol/ProtocolDefinition.java +++ b/src/main/java/de/bixilon/minosoft/protocol/protocol/ProtocolDefinition.java @@ -56,8 +56,11 @@ public final class ProtocolDefinition { public static final Pattern SCOREBOARD_OBJECTIVE_PATTERN = Pattern.compile("[a-zA-z-.+]{1,16}"); public static final int SECTION_WIDTH_X = 16; + public static final int SECTION_MAX_X = SECTION_WIDTH_X - 1; public static final int SECTION_WIDTH_Z = 16; + public static final int SECTION_MAX_Z = SECTION_WIDTH_Z - 1; public static final int SECTION_HEIGHT_Y = 16; + public static final int SECTION_MAX_Y = SECTION_HEIGHT_Y - 1; public static final int SECTIONS_PER_CHUNK = 16; public static final int BLOCKS_PER_SECTION = SECTION_WIDTH_X * SECTION_HEIGHT_Y * SECTION_WIDTH_X; diff --git a/src/main/java/de/bixilon/minosoft/util/ChunkUtil.java b/src/main/java/de/bixilon/minosoft/util/chunk/ChunkUtil.java similarity index 91% rename from src/main/java/de/bixilon/minosoft/util/ChunkUtil.java rename to src/main/java/de/bixilon/minosoft/util/chunk/ChunkUtil.java index 88bda6a2e..cf656cf4f 100644 --- a/src/main/java/de/bixilon/minosoft/util/ChunkUtil.java +++ b/src/main/java/de/bixilon/minosoft/util/chunk/ChunkUtil.java @@ -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. * @@ -11,7 +11,7 @@ * This software is not affiliated with Mojang AB, the original developer of Minecraft. */ -package de.bixilon.minosoft.util; +package de.bixilon.minosoft.util.chunk; import de.bixilon.minosoft.data.mappings.Dimension; import de.bixilon.minosoft.data.mappings.biomes.Biome; @@ -23,10 +23,10 @@ import de.bixilon.minosoft.data.world.InChunkSectionPosition; import de.bixilon.minosoft.data.world.biome.DummyBiomeAccessor; import de.bixilon.minosoft.data.world.biome.XZBiomeAccessor; import de.bixilon.minosoft.data.world.light.DummyLightAccessor; -import de.bixilon.minosoft.data.world.light.LightAccessor; import de.bixilon.minosoft.data.world.palette.Palette; import de.bixilon.minosoft.protocol.protocol.InByteBuffer; import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition; +import de.bixilon.minosoft.util.BitByte; import java.util.BitSet; import java.util.HashMap; @@ -203,6 +203,7 @@ public final class ChunkUtil { if (containsSkyLight) { byte[] skyLight = buffer.readBytes(ProtocolDefinition.BLOCKS_PER_SECTION >> 1); } + // ToDo } sectionMap.put(dimension.getLowestSection() + c, new ChunkSection(blockMap)); @@ -219,24 +220,4 @@ public final class ChunkUtil { return chunkData; } - public static LightAccessor readSkyLightPacket(InByteBuffer buffer, long[] skyLightMask, long[] blockLightMask, long[] emptyBlockLightMask, long[] emptySkyLightMask) { - readLightArray(buffer, BitSet.valueOf(skyLightMask)); - readLightArray(buffer, BitSet.valueOf(blockLightMask)); - // ToDo - return DummyLightAccessor.INSTANCE; - } - - private static void readLightArray(InByteBuffer buffer, BitSet lightMask) { - int highestSectionIndex = ProtocolDefinition.SECTIONS_PER_CHUNK + 2; - if (buffer.getVersionId() >= V_20W49A) { - buffer.readVarInt(); // section count - highestSectionIndex = lightMask.length(); - } - for (int c = 0; c < highestSectionIndex; c++) { // light sections - if (!lightMask.get(c)) { - continue; - } - byte[] light = buffer.readBytes(buffer.readVarInt()); - } - } } diff --git a/src/main/java/de/bixilon/minosoft/util/chunk/LightUtil.kt b/src/main/java/de/bixilon/minosoft/util/chunk/LightUtil.kt new file mode 100644 index 000000000..fea174001 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/util/chunk/LightUtil.kt @@ -0,0 +1,67 @@ +/* + * 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 . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.util.chunk + +import de.bixilon.minosoft.data.mappings.Dimension +import de.bixilon.minosoft.data.world.InChunkPosition +import de.bixilon.minosoft.data.world.light.ChunkLightAccessor +import de.bixilon.minosoft.data.world.light.DummyLightAccessor +import de.bixilon.minosoft.data.world.light.LightAccessor +import de.bixilon.minosoft.protocol.protocol.InByteBuffer +import de.bixilon.minosoft.protocol.protocol.ProtocolVersions +import java.util.* + +object LightUtil { + + fun readLightPacket(buffer: InByteBuffer, skyLightMask: LongArray, blockLightMask: LongArray, emptyBlockLightMask: LongArray, emptySkyLightMask: LongArray, dimension: Dimension): LightAccessor { + // ToDo + val blockLight = readLightArray(buffer, BitSet.valueOf(blockLightMask), dimension) + if (!dimension.hasSkyLight) { + return ChunkLightAccessor(blockLight, world = buffer.connection.player.world) + } + val skyLight = readLightArray(buffer, BitSet.valueOf(skyLightMask), dimension) + return DummyLightAccessor + return ChunkLightAccessor(blockLight, skyLight, buffer.connection.player.world) + } + + private fun readLightArray(buffer: InByteBuffer, lightMask: BitSet, dimension: Dimension): MutableMap { + var highestSectionIndex = dimension.highestSection + 1 + val lowesSectionIndex = dimension.lowestSection - 1 + if (buffer.versionId >= ProtocolVersions.V_20W49A) { + buffer.readVarInt() // section count + highestSectionIndex = lightMask.length() + } + + val lightLevels: MutableMap = mutableMapOf() + + + for ((arrayIndex, c) in (lowesSectionIndex until highestSectionIndex).withIndex()) { // light sections + if (!lightMask[arrayIndex]) { + continue + } + val lightArray = buffer.readBytes(buffer.readVarInt()) + var index = 0 + for (y in 0 until 16) { + for (z in 0 until 16) { + for (x in 0 until 16 step 2) { + lightLevels[InChunkPosition(x, y + c * 16, z)] = (lightArray[index].toInt() and 0x0F).toByte() + lightLevels[InChunkPosition(x + 1, y + c * 16, z)] = ((lightArray[index].toInt() ushr 4) and 0x0F).toByte() + index++ + } + } + } + } + return lightLevels + } +}