From b597e09add54242dfce8602b3ed752d800b0993c Mon Sep 17 00:00:00 2001 From: tako <74718793+TakoTheDev@users.noreply.github.com> Date: Fri, 13 Sep 2024 22:20:37 +0200 Subject: [PATCH] old fill --- .../impl/dungeons/solvers/IceFillSolver.kt | 306 +++++++----------- 1 file changed, 114 insertions(+), 192 deletions(-) diff --git a/src/main/kotlin/gg/skytils/skytilsmod/features/impl/dungeons/solvers/IceFillSolver.kt b/src/main/kotlin/gg/skytils/skytilsmod/features/impl/dungeons/solvers/IceFillSolver.kt index 7c987654c..a7ad8bf1f 100644 --- a/src/main/kotlin/gg/skytils/skytilsmod/features/impl/dungeons/solvers/IceFillSolver.kt +++ b/src/main/kotlin/gg/skytils/skytilsmod/features/impl/dungeons/solvers/IceFillSolver.kt @@ -17,7 +17,6 @@ */ package gg.skytils.skytilsmod.features.impl.dungeons.solvers -import gg.essential.universal.UChat import gg.essential.universal.UMatrixStack import gg.skytils.skytilsmod.Skytils import gg.skytils.skytilsmod.Skytils.Companion.mc @@ -25,9 +24,7 @@ import gg.skytils.skytilsmod.core.tickTimer import gg.skytils.skytilsmod.features.impl.funny.Funny import gg.skytils.skytilsmod.listeners.DungeonListener import gg.skytils.skytilsmod.utils.RenderUtil -import gg.skytils.skytilsmod.utils.SuperSecretSettings import gg.skytils.skytilsmod.utils.Utils -import gg.skytils.skytilsmod.utils.ifNull import kotlinx.coroutines.Job import kotlinx.coroutines.launch import net.minecraft.client.renderer.GlStateManager @@ -50,69 +47,54 @@ object IceFillSolver { tickTimer(20, repeats = true) { if (!Utils.inDungeons || !Skytils.config.iceFillSolver || mc.thePlayer == null) return@tickTimer val world: World = mc.theWorld - if (DungeonListener.missingPuzzles.contains("Ice Fill") && puzzles == null && job?.isActive != true) { + if (DungeonListener.missingPuzzles.contains("Ice Fill") && puzzles == null && (job == null || job?.isActive == false)) { job = Skytils.launch { val playerX = mc.thePlayer.posX.toInt() val playerZ = mc.thePlayer.posZ.toInt() - val xRange = playerX - 30..playerX + 30 - val zRange = playerZ - 30..playerZ + 30 - findChest@ for (te in world.loadedTileEntityList) { + val xRange = playerX - 25..playerX + 25 + val zRange = playerZ - 25..playerZ + 25 + findChest@ for (te in mc.theWorld.loadedTileEntityList) { if (te.pos.y == 75 && te is TileEntityChest && te.numPlayersUsing == 0 && te.pos.x in xRange && te.pos.z in zRange ) { val pos = te.pos if (world.getBlockState(pos.down()).block == Blocks.stone) { for (direction in EnumFacing.HORIZONTALS) { + fun checkChestTorches(dir: EnumFacing): Boolean { + return world.getBlockState( + pos.offset( + dir, + 1 + ) + ).block == Blocks.torch && world.getBlockState( + pos.offset( + dir.opposite, + 3 + ) + ).block == Blocks.torch + } + if (world.getBlockState(pos.offset(direction)).block == Blocks.cobblestone && world.getBlockState( pos.offset(direction.opposite, 2) ).block == Blocks.iron_bars) { - val offsetDir = listOf(direction.rotateYCCW(), direction.rotateY()).find { - return@find world.getBlockState( - pos.offset( - it, - 1 - ) - ).block == Blocks.torch && world.getBlockState( - pos.offset( - it.opposite, - 3 - ) - ).block == Blocks.torch - }?.opposite ?: continue + val offsetDir: EnumFacing? = if (checkChestTorches(direction.rotateY())) { + direction.rotateYCCW() + } else if (checkChestTorches(direction.rotateYCCW())) { + direction.rotateY() + } else continue if (world.getBlockState( pos.offset(direction.opposite) .offset(offsetDir) .down(2) ).block == Blocks.stone_brick_stairs) { - //chestCenter: -11 75 -89; direction: east - val chestCenter = pos.offset(offsetDir) - - val starts = Triple( - //three: -33 70 -89 - chestCenter.down(5).offset(direction.opposite, 22), - //five: -28 71 -89 - chestCenter.down(4).offset(direction.opposite, 17), - //seven: -21 72 -89 - chestCenter.down(3).offset(direction.opposite, 10), - ) - val ends = Triple( - //three: -29 70 -89 - starts.first.offset(direction, 3), - //five: -23 71 -89 - starts.second.offset(direction, 5), - //seven: -14 72 -89 - starts.third.offset(direction, 7), - ) - puzzles = Triple( - IceFillPuzzle(pos, world, starts.first, ends.first, direction), - IceFillPuzzle(pos, world, starts.second, ends.second, direction), - IceFillPuzzle(pos, world, starts.third, ends.third, direction) + IceFillPuzzle(world, 70, pos, direction), + IceFillPuzzle(world, 71, pos, direction), + IceFillPuzzle(world, 72, pos, direction) ) - println( - "An Ice Fill chest is at $pos, is facing $direction and is offset $offsetDir" + "An Ice Fill chest is at $pos and is facing $direction. Offset direction is $offsetDir." ) break@findChest } @@ -126,9 +108,23 @@ object IceFillSolver { } } + private fun checkForStart(world: World, pos: BlockPos, facing: EnumFacing) = + world.getBlockState(pos).block === Blocks.air && + world.getBlockState(pos.offset(facing.rotateY())).block === Blocks.cobblestone_wall && + world.getBlockState(pos.offset(facing.rotateYCCW())).block === Blocks.cobblestone_wall + + private fun generatePairs(world: World, positions: List) = + positions.flatMap { pos -> getPossibleMoves(world, pos).map { Move(pos, it) } } + + private fun getPossibleMoves(world: World, pos: BlockPos) = + EnumFacing.HORIZONTALS.map { pos.offset(it) }.filter { spot -> + val down = world.getBlockState(spot.down()).block + (down == Blocks.ice || down == Blocks.packed_ice) && world.getBlockState(spot).block != Blocks.stone + } + @SubscribeEvent fun onWorldRender(event: RenderWorldLastEvent) { - if (!Utils.inDungeons || !Skytils.config.iceFillSolver || "Ice Fill" !in DungeonListener.missingPuzzles) return + if (!Skytils.config.iceFillSolver || "Ice Fill" !in DungeonListener.missingPuzzles) return val (three, five, seven) = puzzles ?: return val matrixStack = UMatrixStack.Compat.get() three.draw(matrixStack, event.partialTicks) @@ -138,174 +134,100 @@ object IceFillSolver { @SubscribeEvent fun onWorldChange(event: WorldEvent.Unload) { - //TODO: Will only stop the scan task, not currently running path finders - job?.cancel() - job = null puzzles = null + job = null } - private class IceFillPuzzle( - val chestCenter: BlockPos, val world: World, val start: BlockPos, val end: BlockPos, val facing: EnumFacing - ) { - private val optimal = SuperSecretSettings.azooPuzzoo - private var path: List? = null - - init { - Skytils.launch { - path = findPath().ifNull { - UChat.chat("${Skytils.failPrefix} §cFailed to find a solution for Ice Fill. Please report this on our Discord at discord.gg/skytils.") - println("Ice Fill Data: chestCenter=$chestCenter, start=$start, end=$end, facing=$facing, optimal=$optimal") - } + private class IceFillPuzzle(world: World, y: Int, chestPos: BlockPos, roomFacing: EnumFacing) { + private val spaces: MutableList = ArrayList() + private lateinit var start: BlockPos + var paths: MutableSet> = HashSet() + fun genPaths(world: World) { + // Generate paths + val moves = generatePairs(world, spaces) + val g = Graph(moves, world) + val path: MutableList = ArrayList() + path.add(start) + try { + getPaths(g, start, mutableSetOf(start), path, spaces.size) + } catch (e: Exception) { + e.printStackTrace() } } - private fun findPath(): List? { - val spaces = getSpaces() - - val moves = spaces.associate { - val neighbors = EnumFacing.HORIZONTALS.associateBy { direction -> it.offset(direction) } - .filterKeys { spot -> spot in spaces } - .mapKeys { (pos, _) -> spaces.indexOf(pos) } - Pair(spaces.indexOf(it), neighbors) - } - - val startIndex = spaces.indexOf(start) - val n = spaces.size - val visited = BooleanArray(n).also { it[startIndex] = true } - val startPath = IntArray(n) { -1 }.also { it[0] = startIndex } - - if (optimal) { - val optimizedMoves = Array(n) { - moves[it]!!.map { map -> - map.key + (map.value.ordinal.toLong() shl 32) - }.toLongArray() - } - - return getOptimalPath( - optimizedMoves, n, startIndex, visited, startPath, 1, facing.ordinal, 0, Int.MAX_VALUE - )?.first?.map { Vec3(spaces.elementAt(it)).addVector(0.5, 0.01, 0.5) } - } else { - val simplifiedMoves = moves.mapValues { (_, y) -> y.map { it.key } } - - return getFirstPath( - Array(n) { simplifiedMoves[it]!! }, n, startIndex, visited, startPath, 1 - )?.map { Vec3(spaces.elementAt(it)).addVector(0.5, 0.01, 0.5) } - } - } - - fun draw(matrixStack: UMatrixStack, partialTicks: Float) { - path?.let { - GlStateManager.pushMatrix() + fun draw(matrixStack: UMatrixStack, partialTicks: Float) = + paths.firstOrNull()?.zipWithNext { first, second -> GlStateManager.disableCull() - - it.zipWithNext { first, second -> - RenderUtil.draw3DLine(first, second, 5, Color.MAGENTA, partialTicks, matrixStack, Funny.alphaMult) - } - GlStateManager.popMatrix() + RenderUtil.draw3DLine( + Vec3(first).addVector(0.5, 0.01, 0.5), + Vec3(second).addVector(0.5, 0.01, 0.5), + 5, + Color.RED, + partialTicks, + matrixStack, + Funny.alphaMult + ) + GlStateManager.enableCull() } - } - private fun getFirstPath( - moves: Array>, - n: Int, - visiting: Int, - visited: BooleanArray, - path: IntArray, - depth: Int, - ): List? { - if (depth == n) { - return path.toList() - } + private fun getPaths( + g: Graph, + v: BlockPos, + visited: MutableSet, + path: MutableList, + n: Int + ) { + if (path.size == n) { + val newPath: List = path.toList() + paths.add(newPath) + return + } else { - val move = moves[visiting] + // Check if every move starting from position `v` leads + // to a solution or not + g.adjList[v]?.forEach { w -> + // Only check if we haven't been there before + if (!visited.contains(w)) { + visited.add(w) + path.add(w) - for (index in move) { - if (!visited[index]) { - visited[index] = true - path[depth] = index + // Continue checking down this path + getPaths(g, w, visited, path, n) - getFirstPath(moves, n, index, visited, path, depth + 1)?.let { - return it + // backtrack + visited.remove(w) + path.remove(w) } - - visited[index] = false } } - - return null } - private fun getOptimalPath( - moves: Array, - n: Int, - visiting: Int, - visited: BooleanArray, - path: IntArray, - depth: Int, - lastDirection: Int, - corners: Int, - knownLeastCorners: Int - ): Pair, Int>? { - if (corners >= knownLeastCorners) { - return null - } - - if (depth == n) { - return Pair(path.toList(), corners) - } - - var bestPath: List? = null - var leastCorners = knownLeastCorners - - val move = moves[visiting] - - for (value in move) { - val index = value.toInt() - if (visited[index]) continue - - val direction = (value shr 32).toInt() - - visited[index] = true - path[depth] = index - - val newCorners = if (lastDirection != direction) corners + 1 else corners - - val newPath = getOptimalPath( - moves, n, index, visited, path, depth + 1, direction, newCorners, leastCorners - ) - - if (newPath != null) { - bestPath = newPath.first - leastCorners = newPath.second + init { + chestPos.offset(roomFacing.opposite, 11).run { Utils.getBlocksWithinRangeAtSameY(chestPos, 25, y) } + .forEach { pos -> + when (world.getBlockState(pos.down()).block) { + Blocks.ice, Blocks.packed_ice -> + if (world.getBlockState(pos).block === Blocks.air) + spaces.add(pos) + + Blocks.stone_brick_stairs, Blocks.stone -> { + if (!::start.isInitialized && checkForStart(world, pos, roomFacing)) + start = pos.offset(roomFacing) + } + } } - - visited[index] = false - } - - return bestPath?.let { Pair(it, leastCorners) } + genPaths(world) } + } - private fun getSpaces(): List { - val spaces = mutableListOf(start) - val queue = mutableListOf(start) + private data class Move(var source: BlockPos, var dest: BlockPos) - while (queue.isNotEmpty()) { - val current = queue.removeLast() - EnumFacing.HORIZONTALS.forEach { direction -> - val next = current.offset(direction) - if (next !in spaces && world.getBlockState(next).block === Blocks.air && Utils.equalsOneOf( - world.getBlockState( - next.down() - ).block, Blocks.ice, Blocks.packed_ice - ) - ) { - spaces.add(next) - queue.add(next) - } - } + private class Graph(moves: Collection, world: World) { + val adjList: Map> = buildMap { + moves.forEach { (source, dest) -> + this[source] = getPossibleMoves(world, source) + this[dest] = getPossibleMoves(world, dest) } - - return spaces } } } \ No newline at end of file