diff --git a/src/main/kotlin/com/github/itzilly/sbt/features/GemstoneFinder.kt b/src/main/kotlin/com/github/itzilly/sbt/features/GemstoneFinder.kt index dd0203d..0ea72b5 100644 --- a/src/main/kotlin/com/github/itzilly/sbt/features/GemstoneFinder.kt +++ b/src/main/kotlin/com/github/itzilly/sbt/features/GemstoneFinder.kt @@ -16,12 +16,11 @@ enum class GemstoneColors(val color: Int) { TOPAZ(0xFFFF55), // Yellow JASPER(0xFF55FF) // Magenta } - object GemstoneFinder { - private var gemstoneList: ArrayList = ArrayList() + private var gemstoneClusters: ArrayList = ArrayList() fun clear() { - gemstoneList.clear() + gemstoneClusters.clear() } fun find() { @@ -40,6 +39,8 @@ object GemstoneFinder { ) val world = Minecraft.getMinecraft().thePlayer.worldObj + val gemstonesMap = mutableMapOf>() + for (chunkX in minBounds.x / 16..maxBounds.x / 16) { for (chunkZ in minBounds.z / 16..maxBounds.z / 16) { val chunk = world.getChunkFromChunkCoords(chunkX, chunkZ) @@ -73,8 +74,8 @@ object GemstoneFinder { else -> null } - if (gemstoneColor != null && !gemstonesList(blockPos.x, blockPos.y, blockPos.z)) { - gemstoneList.add(GemstoneBlock(blockPos.x, blockPos.y, blockPos.z, gemstoneColor)) + if (gemstoneColor != null) { + gemstonesMap.getOrPut(gemstoneColor) { mutableListOf() }.add(blockPos) } } } @@ -82,62 +83,65 @@ object GemstoneFinder { } } } - mergeAdjacentBlocks() + + for ((color, positions) in gemstonesMap) { + val clusters = clusterGemstones(positions) + for (cluster in clusters) { + gemstoneClusters.add(GemstoneCluster(cluster, color)) + } + } } - private fun mergeAdjacentBlocks() { - val mergedList = ArrayList() + private fun clusterGemstones(positions: List): List> { + val clusters = mutableListOf>() + val visited = mutableSetOf() - while (gemstoneList.isNotEmpty()) { - val firstBlock = gemstoneList.removeAt(0) - val group = mutableListOf(firstBlock) + fun dfs(start: BlockPos, cluster: MutableList) { + val stack = ArrayDeque() + stack.add(start) - val queue = ArrayDeque() - queue.add(firstBlock) + while (stack.isNotEmpty()) { + val pos = stack.removeLast() + if (pos in visited) continue + visited.add(pos) + cluster.add(pos) - while (queue.isNotEmpty()) { - val current = queue.removeFirst() - val neighbors = gemstoneList.filter { - it.color == current.color && isAdjacent(it, current) + // Check adjacent blocks in all six directions + for (offset in listOf(BlockPos(1, 0, 0), BlockPos(-1, 0, 0), BlockPos(0, 1, 0), BlockPos(0, -1, 0), BlockPos(0, 0, 1), BlockPos(0, 0, -1))) { + val neighbor = pos.add(offset) + if (neighbor in positions && neighbor !in visited) { + stack.add(neighbor) + } } - group.addAll(neighbors) - gemstoneList.removeAll(neighbors) - queue.addAll(neighbors) } - - mergedList.add(mergeGroup(group)) } - gemstoneList = mergedList - } + for (pos in positions) { + if (pos !in visited) { + val cluster = mutableListOf() + dfs(pos, cluster) + clusters.add(cluster) + } + } - private fun isAdjacent(a: GemstoneBlock, b: GemstoneBlock): Boolean { - return (a.x == b.x && a.y == b.y && (a.z == b.z + 1 || a.z == b.z - 1)) || - (a.x == b.x && (a.y == b.y + 1 || a.y == b.y - 1) && a.z == b.z) || - ((a.x == b.x + 1 || a.x == b.x - 1) && a.y == b.y && a.z == b.z) - } - - private fun mergeGroup(group: List): GemstoneBlock { - val minX = group.minOf { it.x } - val minY = group.minOf { it.y } - val minZ = group.minOf { it.z } - val maxX = group.maxOf { it.x } - val maxY = group.maxOf { it.y } - val maxZ = group.maxOf { it.z } - return GemstoneBlock(minX, minY, minZ, group.first().color, maxX, maxY, maxZ) + return clusters } @SubscribeEvent fun renderBlockOverlay(event: RenderWorldLastEvent) { val player = Minecraft.getMinecraft().thePlayer - for (gemstone: GemstoneBlock in gemstoneList) { - val startPos = BlockPos(gemstone.x, gemstone.y, gemstone.z) - val endPos = BlockPos(gemstone.maxX, gemstone.maxY, gemstone.maxZ) - RenderFuncs.drawBlockOutline(player, startPos, endPos, event.partialTicks, gemstone.color.color) + for (cluster in gemstoneClusters) { + RenderFuncs.drawClusterOutline(player, cluster.positions, event.partialTicks, cluster.color.color) } } + data class GemstoneCluster( + val positions: List, + val color: GemstoneColors + ) + + data class GemstoneBlock( val x: Int, val y: Int, @@ -147,8 +151,4 @@ object GemstoneFinder { val maxY: Int = y, val maxZ: Int = z ) - - fun gemstonesList(x: Int, y: Int, z: Int): Boolean { - return gemstoneList.any { it.x == x && it.y == y && it.z == z } - } } \ No newline at end of file diff --git a/src/main/kotlin/com/github/itzilly/sbt/renderer/RenderFuncs.kt b/src/main/kotlin/com/github/itzilly/sbt/renderer/RenderFuncs.kt index 79fa8e1..7a9e3bf 100644 --- a/src/main/kotlin/com/github/itzilly/sbt/renderer/RenderFuncs.kt +++ b/src/main/kotlin/com/github/itzilly/sbt/renderer/RenderFuncs.kt @@ -7,6 +7,7 @@ import net.minecraft.client.renderer.GlStateManager import net.minecraft.entity.Entity import net.minecraft.util.AxisAlignedBB import net.minecraft.util.BlockPos +import net.minecraft.util.EnumFacing import org.lwjgl.opengl.GL11 import java.awt.Color @@ -117,28 +118,15 @@ object RenderFuncs { RenderUtils.drawLine(pos1, pos2, lineColor, 2, false, partialTicks) } - - fun drawBlockOutline(entity: Entity, startPos: BlockPos, endPos: BlockPos, partialTicks: Float, outlineColor: Int) { + fun drawClusterOutline(entity: Entity, positions: List, partialTicks: Float, outlineColor: Int) { val padding = 0.0020000000949949026 + // Correctly calculate the player's interpolated position val entityX = entity.lastTickPosX + (entity.posX - entity.lastTickPosX) * partialTicks val entityY = entity.lastTickPosY + (entity.posY - entity.lastTickPosY) * partialTicks val entityZ = entity.lastTickPosZ + (entity.posZ - entity.lastTickPosZ) * partialTicks - val minX = minOf(startPos.x.toDouble(), endPos.x.toDouble()) - val minY = minOf(startPos.y.toDouble(), endPos.y.toDouble()) - val minZ = minOf(startPos.z.toDouble(), endPos.z.toDouble()) - val maxX = maxOf(startPos.x.toDouble() + 1.0, endPos.x.toDouble() + 1.0) - val maxY = maxOf(startPos.y.toDouble() + 1.0, endPos.y.toDouble() + 1.0) - val maxZ = maxOf(startPos.z.toDouble() + 1.0, endPos.z.toDouble() + 1.0) - - val boundingBox = AxisAlignedBB(minX, minY, minZ, maxX, maxY, maxZ).expand(padding, padding, padding) - - // Convert integer color to r, g, b, a values - val r = (outlineColor shr 16 and 0xFF) / 255.0f - val g = (outlineColor shr 8 and 0xFF) / 255.0f - val b = (outlineColor and 0xFF) / 255.0f - val a = (outlineColor shr 24 and 0xFF) / 255.0f + val world = Minecraft.getMinecraft().theWorld GL11.glPushMatrix() GlStateManager.disableTexture2D() @@ -150,11 +138,44 @@ object RenderFuncs { GL11.glHint(GL11.GL_LINE_SMOOTH_HINT, GL11.GL_NICEST) GL11.glLineWidth(2.0f) + // Convert integer color to r, g, b, a values + val r = (outlineColor shr 16 and 0xFF) / 255.0f + val g = (outlineColor shr 8 and 0xFF) / 255.0f + val b = (outlineColor and 0xFF) / 255.0f + val a = (outlineColor shr 24 and 0xFF) / 255.0f + // Set the color with alpha GL11.glColor4f(r, g, b, a) - // Draw the outline using the bounding box - RenderUtils.drawBlock(boundingBox.offset(-entityX, -entityY, -entityZ), outlineColor, outlineColor) + // Iterate over the positions and draw the outline around each block + for (pos in positions) { + val blockState = world.getBlockState(pos) + val boundingBox = blockState.block.getCollisionBoundingBox(world, pos, blockState) + ?: AxisAlignedBB(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble(), pos.x + 1.0, pos.y + 1.0, pos.z + 1.0).expand(padding, padding, padding) + + // Offset the bounding box by the interpolated player position + val offsetBoundingBox = boundingBox.offset(-entityX, -entityY, -entityZ) + + // Check adjacent blocks and only draw faces that are exposed (not adjacent to another gemstone block) + if (!positions.contains(pos.add(1, 0, 0))) { + RenderUtils.drawBlockFace(offsetBoundingBox, EnumFacing.EAST, outlineColor) + } + if (!positions.contains(pos.add(-1, 0, 0))) { + RenderUtils.drawBlockFace(offsetBoundingBox, EnumFacing.WEST, outlineColor) + } + if (!positions.contains(pos.add(0, 1, 0))) { + RenderUtils.drawBlockFace(offsetBoundingBox, EnumFacing.UP, outlineColor) + } + if (!positions.contains(pos.add(0, -1, 0))) { + RenderUtils.drawBlockFace(offsetBoundingBox, EnumFacing.DOWN, outlineColor) + } + if (!positions.contains(pos.add(0, 0, 1))) { + RenderUtils.drawBlockFace(offsetBoundingBox, EnumFacing.SOUTH, outlineColor) + } + if (!positions.contains(pos.add(0, 0, -1))) { + RenderUtils.drawBlockFace(offsetBoundingBox, EnumFacing.NORTH, outlineColor) + } + } GL11.glDisable(GL11.GL_LINE_SMOOTH) GL11.glDisable(GL11.GL_BLEND) diff --git a/src/main/kotlin/com/github/itzilly/sbt/renderer/RenderUtils.kt b/src/main/kotlin/com/github/itzilly/sbt/renderer/RenderUtils.kt index 410f13f..d9fd307 100644 --- a/src/main/kotlin/com/github/itzilly/sbt/renderer/RenderUtils.kt +++ b/src/main/kotlin/com/github/itzilly/sbt/renderer/RenderUtils.kt @@ -8,6 +8,7 @@ import net.minecraft.client.renderer.Tessellator import net.minecraft.client.renderer.WorldRenderer import net.minecraft.client.renderer.vertex.DefaultVertexFormats import net.minecraft.util.AxisAlignedBB +import net.minecraft.util.EnumFacing import org.lwjgl.opengl.GL11 import java.awt.Color @@ -256,4 +257,63 @@ object RenderUtils { .color(outlineEndColor.red, outlineEndColor.green, outlineEndColor.blue, outlineEndColor.alpha).endVertex() TESSELLATOR.draw() } + + fun drawBlockFace(box: AxisAlignedBB, face: EnumFacing, color: Int) { + WORLD_RENDERER.begin(GL11.GL_LINE_STRIP, DefaultVertexFormats.POSITION_COLOR) + + val r = (color shr 16 and 0xFF) / 255.0f + val g = (color shr 8 and 0xFF) / 255.0f + val b = (color and 0xFF) / 255.0f + val a = (color shr 24 and 0xFF) / 255.0f + + GL11.glColor4f(r, g, b, a) + + when (face) { + EnumFacing.UP -> { + WORLD_RENDERER.pos(box.minX, box.maxY, box.minZ).endVertex() + WORLD_RENDERER.pos(box.maxX, box.maxY, box.minZ).endVertex() + WORLD_RENDERER.pos(box.maxX, box.maxY, box.maxZ).endVertex() + WORLD_RENDERER.pos(box.minX, box.maxY, box.maxZ).endVertex() + WORLD_RENDERER.pos(box.minX, box.maxY, box.minZ).endVertex() + } + EnumFacing.DOWN -> { + WORLD_RENDERER.pos(box.minX, box.minY, box.minZ).endVertex() + WORLD_RENDERER.pos(box.maxX, box.minY, box.minZ).endVertex() + WORLD_RENDERER.pos(box.maxX, box.minY, box.maxZ).endVertex() + WORLD_RENDERER.pos(box.minX, box.minY, box.maxZ).endVertex() + WORLD_RENDERER.pos(box.minX, box.minY, box.minZ).endVertex() + } + EnumFacing.NORTH -> { + WORLD_RENDERER.pos(box.minX, box.minY, box.minZ).endVertex() + WORLD_RENDERER.pos(box.maxX, box.minY, box.minZ).endVertex() + WORLD_RENDERER.pos(box.maxX, box.maxY, box.minZ).endVertex() + WORLD_RENDERER.pos(box.minX, box.maxY, box.minZ).endVertex() + WORLD_RENDERER.pos(box.minX, box.minY, box.minZ).endVertex() + } + EnumFacing.SOUTH -> { + WORLD_RENDERER.pos(box.minX, box.minY, box.maxZ).endVertex() + WORLD_RENDERER.pos(box.maxX, box.minY, box.maxZ).endVertex() + WORLD_RENDERER.pos(box.maxX, box.maxY, box.maxZ).endVertex() + WORLD_RENDERER.pos(box.minX, box.maxY, box.maxZ).endVertex() + WORLD_RENDERER.pos(box.minX, box.minY, box.maxZ).endVertex() + } + EnumFacing.WEST -> { + WORLD_RENDERER.pos(box.minX, box.minY, box.minZ).endVertex() + WORLD_RENDERER.pos(box.minX, box.minY, box.maxZ).endVertex() + WORLD_RENDERER.pos(box.minX, box.maxY, box.maxZ).endVertex() + WORLD_RENDERER.pos(box.minX, box.maxY, box.minZ).endVertex() + WORLD_RENDERER.pos(box.minX, box.minY, box.minZ).endVertex() + } + EnumFacing.EAST -> { + WORLD_RENDERER.pos(box.maxX, box.minY, box.minZ).endVertex() + WORLD_RENDERER.pos(box.maxX, box.minY, box.maxZ).endVertex() + WORLD_RENDERER.pos(box.maxX, box.maxY, box.maxZ).endVertex() + WORLD_RENDERER.pos(box.maxX, box.maxY, box.minZ).endVertex() + WORLD_RENDERER.pos(box.maxX, box.minY, box.minZ).endVertex() + } + else -> {} + } + + TESSELLATOR.draw() + } } \ No newline at end of file