From 53fc083022b04d88d370b5bda023b6155f97e6e3 Mon Sep 17 00:00:00 2001 From: tako <74718793+TakoTheDev@users.noreply.github.com> Date: Sat, 24 Aug 2024 16:15:59 +0200 Subject: [PATCH 01/11] epic stuff --- .../impl/dungeons/solvers/IceFillSolver.kt | 287 +++++++++++------- 1 file changed, 177 insertions(+), 110 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 a7ad8bf1f..721cea6a4 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,6 +17,8 @@ */ package gg.skytils.skytilsmod.features.impl.dungeons.solvers +import gg.essential.universal.UChat +import gg.essential.universal.UKeyboard import gg.essential.universal.UMatrixStack import gg.skytils.skytilsmod.Skytils import gg.skytils.skytilsmod.Skytils.Companion.mc @@ -24,7 +26,9 @@ 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 @@ -53,48 +57,63 @@ object IceFillSolver { val playerZ = mc.thePlayer.posZ.toInt() val xRange = playerX - 25..playerX + 25 val zRange = playerZ - 25..playerZ + 25 - findChest@ for (te in mc.theWorld.loadedTileEntityList) { + findChest@ for (te in world.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: EnumFacing? = if (checkChestTorches(direction.rotateY())) { - direction.rotateYCCW() - } else if (checkChestTorches(direction.rotateYCCW())) { - direction.rotateY() - } else continue + 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 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(world, 70, pos, direction), - IceFillPuzzle(world, 71, pos, direction), - IceFillPuzzle(world, 72, pos, direction) + IceFillPuzzle(pos, world, starts.first, ends.first, direction), + IceFillPuzzle(pos, world, starts.second, ends.second, direction), + IceFillPuzzle(pos, world, starts.third, ends.third, direction) ) + println( - "An Ice Fill chest is at $pos and is facing $direction. Offset direction is $offsetDir." + "An Ice Fill chest is at $pos, is facing $direction and is offset $offsetDir" ) break@findChest } @@ -108,23 +127,9 @@ 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 (!Skytils.config.iceFillSolver || "Ice Fill" !in DungeonListener.missingPuzzles) return + if (!Utils.inDungeons || !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) @@ -134,100 +139,162 @@ object IceFillSolver { @SubscribeEvent fun onWorldChange(event: WorldEvent.Unload) { - puzzles = null + job?.cancel() job = null + puzzles = null } - 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 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") + } } } - fun draw(matrixStack: UMatrixStack, partialTicks: Float) = - paths.firstOrNull()?.zipWithNext { first, second -> - GlStateManager.disableCull() - 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 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) } - 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 + 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) { + return getOptimalPath( + Array(n) { moves[it]!! }, n, startIndex, visited, startPath, 1, facing, 0, Int.MAX_VALUE + )?.first?.map { Vec3(spaces.elementAt(it)).addVector(0.5, 0.01, 0.5) } } else { + val fixed = moves.mapValues { (_, y) -> y.map { it.key } } - // 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) + return getFirstPath( + Array(n) { fixed[it]!! }, n, startIndex, visited, startPath, 1 + )?.map { Vec3(spaces.elementAt(it)).addVector(0.5, 0.01, 0.5) } + } + } - // Continue checking down this path - getPaths(g, w, visited, path, n) + fun draw(matrixStack: UMatrixStack, partialTicks: Float) { + GlStateManager.pushMatrix() + GlStateManager.disableCull() - // backtrack - visited.remove(w) - path.remove(w) - } + path?.zipWithNext { first, second -> + RenderUtil.draw3DLine(first, second, 5, Color.MAGENTA, partialTicks, matrixStack, Funny.alphaMult) + } + + GlStateManager.popMatrix() + } + + private fun getFirstPath( + moves: Array>, + n: Int, + visiting: Int, + visited: BooleanArray, + path: IntArray, + depth: Int, + ): List? { + if (depth == n) { + return path.toList() + } + + val move = moves[visiting] + + for (index in move) { + if (!visited[index]) { + visited[index] = true + path[depth] = index + + val foundPath = getFirstPath(moves, n, index, visited, path, depth + 1) + if (foundPath != null) return foundPath + + visited[index] = false } } + + return null } - 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) + //TODO: Maybe make it only return the path and not the corners + private fun getOptimalPath( + moves: Array>, + n: Int, + visiting: Int, + visited: BooleanArray, + path: IntArray, + depth: Int, + lastDirection: EnumFacing, + corners: Int, + knownLeastCorners: Int + ): Pair, Int>? { + if (depth == n) { + return Pair(path.toList(), corners) + } + + + var bestPath: List? = null + var leastCorners = knownLeastCorners + + if (leastCorners > corners) { + val move = moves[visiting] + + for ((index, direction) in move) { + if (!visited[index]) { + 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 } + + visited[index] = false } } - genPaths(world) + } + + val best = bestPath + return if (best != null) Pair(best, leastCorners) else null } - } - private data class Move(var source: BlockPos, var dest: BlockPos) + private fun getSpaces(): List { + val spaces = mutableListOf(start) + val queue = mutableListOf(start) - 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) + 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) + } + } } + + return spaces } } } \ No newline at end of file From e9cfc0a85af5f20612f1ac9557fc1ccc0f535b3b Mon Sep 17 00:00:00 2001 From: tako <74718793+TakoTheDev@users.noreply.github.com> Date: Sat, 24 Aug 2024 22:19:28 +0200 Subject: [PATCH 02/11] chore: removed import and correctly ordered puzzles [no ci] --- .../features/impl/dungeons/solvers/IceFillSolver.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 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 721cea6a4..233ee6e6a 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 @@ -18,7 +18,6 @@ package gg.skytils.skytilsmod.features.impl.dungeons.solvers import gg.essential.universal.UChat -import gg.essential.universal.UKeyboard import gg.essential.universal.UMatrixStack import gg.skytils.skytilsmod.Skytils import gg.skytils.skytilsmod.Skytils.Companion.mc @@ -107,9 +106,9 @@ object IceFillSolver { ) puzzles = Triple( - IceFillPuzzle(pos, world, starts.first, ends.first, direction), + IceFillPuzzle(pos, world, starts.third, ends.third, direction), IceFillPuzzle(pos, world, starts.second, ends.second, direction), - IceFillPuzzle(pos, world, starts.third, ends.third, direction) + IceFillPuzzle(pos, world, starts.first, ends.first, direction) ) println( From b2ae4957be28170df3de9142851bcb8f25c347c5 Mon Sep 17 00:00:00 2001 From: tako <74718793+TakoTheDev@users.noreply.github.com> Date: Mon, 26 Aug 2024 19:43:53 +0200 Subject: [PATCH 03/11] improved performance by like 30% --- .../impl/dungeons/solvers/IceFillSolver.kt | 69 +++++++++++-------- 1 file changed, 40 insertions(+), 29 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 233ee6e6a..d5b91942d 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 @@ -54,8 +54,8 @@ object IceFillSolver { job = Skytils.launch { val playerX = mc.thePlayer.posX.toInt() val playerZ = mc.thePlayer.posZ.toInt() - val xRange = playerX - 25..playerX + 25 - val zRange = playerZ - 25..playerZ + 25 + val xRange = playerX - 30..playerX + 30 + val zRange = playerZ - 30..playerZ + 30 findChest@ for (te in world.loadedTileEntityList) { if (te.pos.y == 75 && te is TileEntityChest && te.numPlayersUsing == 0 && te.pos.x in xRange && te.pos.z in zRange ) { @@ -138,6 +138,7 @@ 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 @@ -174,14 +175,20 @@ object IceFillSolver { 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( - Array(n) { moves[it]!! }, n, startIndex, visited, startPath, 1, facing, 0, Int.MAX_VALUE + 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 fixed = moves.mapValues { (_, y) -> y.map { it.key } } + val simplifiedMoves = moves.mapValues { (_, y) -> y.map { it.key } } return getFirstPath( - Array(n) { fixed[it]!! }, n, startIndex, visited, startPath, 1 + Array(n) { simplifiedMoves[it]!! }, n, startIndex, visited, startPath, 1 )?.map { Vec3(spaces.elementAt(it)).addVector(0.5, 0.01, 0.5) } } } @@ -216,8 +223,9 @@ object IceFillSolver { visited[index] = true path[depth] = index - val foundPath = getFirstPath(moves, n, index, visited, path, depth + 1) - if (foundPath != null) return foundPath + getFirstPath(moves, n, index, visited, path, depth + 1)?.let { + return it + } visited[index] = false } @@ -226,51 +234,54 @@ object IceFillSolver { return null } - //TODO: Maybe make it only return the path and not the corners private fun getOptimalPath( - moves: Array>, + moves: Array, n: Int, visiting: Int, visited: BooleanArray, path: IntArray, depth: Int, - lastDirection: EnumFacing, + 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 - if (leastCorners > corners) { - val move = moves[visiting] + val move = moves[visiting] - for ((index, direction) in move) { - if (!visited[index]) { - visited[index] = true - path[depth] = index + for (value in move) { + val index = value.toInt() + if (visited[index]) continue - val newCorners = if (lastDirection != direction) corners + 1 else corners + val direction = (value shr 32).toInt() - val newPath = getOptimalPath( - moves, n, index, visited, path, depth + 1, direction, newCorners, leastCorners - ) - if (newPath != null) { - bestPath = newPath.first - leastCorners = newPath.second - } + visited[index] = true + path[depth] = index - visited[index] = false - } + 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 } + + visited[index] = false } - val best = bestPath - return if (best != null) Pair(best, leastCorners) else null + return bestPath?.let { Pair(it, leastCorners) } } private fun getSpaces(): List { From 7d2a03105bb428173bd1614b777c71660138e404 Mon Sep 17 00:00:00 2001 From: tako <74718793+TakoTheDev@users.noreply.github.com> Date: Wed, 28 Aug 2024 19:43:36 +0200 Subject: [PATCH 04/11] reverted "correctly ordered puzzles" and minor improvements --- .../impl/dungeons/solvers/IceFillSolver.kt | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 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 d5b91942d..7c987654c 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 @@ -50,7 +50,7 @@ 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 == null || job?.isActive == false)) { + if (DungeonListener.missingPuzzles.contains("Ice Fill") && puzzles == null && job?.isActive != true) { job = Skytils.launch { val playerX = mc.thePlayer.posX.toInt() val playerZ = mc.thePlayer.posZ.toInt() @@ -106,9 +106,9 @@ object IceFillSolver { ) puzzles = Triple( - IceFillPuzzle(pos, world, starts.third, ends.third, direction), + IceFillPuzzle(pos, world, starts.first, ends.first, direction), IceFillPuzzle(pos, world, starts.second, ends.second, direction), - IceFillPuzzle(pos, world, starts.first, ends.first, direction) + IceFillPuzzle(pos, world, starts.third, ends.third, direction) ) println( @@ -194,14 +194,15 @@ object IceFillSolver { } fun draw(matrixStack: UMatrixStack, partialTicks: Float) { - GlStateManager.pushMatrix() - GlStateManager.disableCull() + path?.let { + GlStateManager.pushMatrix() + GlStateManager.disableCull() - path?.zipWithNext { first, second -> - RenderUtil.draw3DLine(first, second, 5, Color.MAGENTA, partialTicks, matrixStack, Funny.alphaMult) + it.zipWithNext { first, second -> + RenderUtil.draw3DLine(first, second, 5, Color.MAGENTA, partialTicks, matrixStack, Funny.alphaMult) + } + GlStateManager.popMatrix() } - - GlStateManager.popMatrix() } private fun getFirstPath( From 1ba2086c10a16b839e737c84b395100225d81a3a Mon Sep 17 00:00:00 2001 From: tako <74718793+TakoTheDev@users.noreply.github.com> Date: Fri, 13 Sep 2024 22:19:25 +0200 Subject: [PATCH 05/11] stuff --- .../gg/skytils/skytilsmod/core/Config.kt | 10 ++++++ .../skytilsmod/listeners/DungeonListener.kt | 35 ++++++++++++++++++- .../skytils/skytilsws/client/PacketHandler.kt | 10 ++++++ .../resources/assets/skytils/lang/en_US.lang | 1 + 4 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/gg/skytils/skytilsmod/core/Config.kt b/src/main/kotlin/gg/skytils/skytilsmod/core/Config.kt index 95c78ca81..03069c5f9 100644 --- a/src/main/kotlin/gg/skytils/skytilsmod/core/Config.kt +++ b/src/main/kotlin/gg/skytils/skytilsmod/core/Config.kt @@ -313,6 +313,16 @@ object Config : Vigilant( ) var partyFinderStats = false + @Property( + type = PropertyType.SWITCH, name = "Run Breakdown", + description = "§b[WIP] Shows a Breakdown on what players did in the dungeon.", + category = "Dungeons", subcategory = "Miscellaneous", + i18nName = "skytils.config.dungeons.miscellaneous.run_breakdown", + i18nCategory = "skytils.config.dungeons", + i18nSubcategory = "skytils.config.dungeons.miscellaneous" + ) + var runBreakdown = false + @Property( type = PropertyType.SWITCH, name = "Dungeon Chest Profit", description = "Shows the estimated profit for items from chests in dungeons.", diff --git a/src/main/kotlin/gg/skytils/skytilsmod/listeners/DungeonListener.kt b/src/main/kotlin/gg/skytils/skytilsmod/listeners/DungeonListener.kt index 2690a4a92..6db90307b 100644 --- a/src/main/kotlin/gg/skytils/skytilsmod/listeners/DungeonListener.kt +++ b/src/main/kotlin/gg/skytils/skytilsmod/listeners/DungeonListener.kt @@ -113,6 +113,7 @@ object DungeonListener { private val secretsRegex = Regex("\\s*§7(?\\d+)\\/(?\\d+) Secrets") private val keyPickupRegex = Regex("§r§e§lRIGHT CLICK §r§7on §r§7.+?§r§7 to open it\\. This key can only be used to open §r§a(?\\d+)§r§7 door!§r") private val witherDoorOpenedRegex = Regex("^(?:\\[.+?] )?(?\\w+) opened a WITHER door!$") + private val terminalCompletedRegex = Regex("§r§.(?\\w+)§r§a (?:activated|completed) a (?device|terminal|lever)! \\(§r§c(?\\d)§r§a\\/(?\\d)\\)§r") private const val bloodOpenedString = "§r§cThe §r§c§lBLOOD DOOR§r§c has been opened!§r" val outboundRoomQueue = ConcurrentLinkedQueue() var isSoloDungeon = false @@ -150,7 +151,7 @@ object DungeonListener { if (room.foundSecrets != sec) { room.foundSecrets = sec if (team.size > 1) - WSClient.sendPacketAsync(C2SPacketDungeonRoomSecret(SBInfo.server ?: return@setFoundSecrets, room.mainRoom.data.name, sec)) + WSClient.sendPacketAsync(C2SPacketDungeonRoomSecret(SBInfo.server ?: return@setFoundSecrets, room.mainRoom.data.name, sec, mc.thePlayer.name)) } } } @@ -160,6 +161,17 @@ object DungeonListener { DungeonFeatures.DungeonSecretDisplay.maxSecrets = -1 } } else { + terminalCompletedRegex.find(text)?.let { + val completer = team[it.groups["username"]?.value] + val type = it.groups["type"]?.value + + if (completer != null && type != null) { + when (type) { + "lever" -> completer.leversDone++ + "terminal", "device" -> completer.terminalsDone++ + } + } + } if (text.stripControlCodes() .trim() == "> EXTRA STATS <" ) { @@ -181,6 +193,22 @@ object DungeonListener { if (Skytils.config.autoRepartyOnDungeonEnd) { RepartyCommand.processCommand(mc.thePlayer, emptyArray()) } + if (Skytils.config.runBreakdown) { + tickTimer(6) { + val output = team.map { + //TODO: Maybe also save the rank color? + var output = "§6${it.key}§a | Secrets: §6${it.value.secretsDone}§a | Rooms: §6${it.value.roomsDone}§a | Deaths: §6${it.value.deaths}" + + if (DungeonFeatures.dungeonFloorNumber == 7) { + output += "§a | Terminals: §6${it.value.terminalsDone}§a | Levers: §6${it.value.leversDone}" + } + + output + } + + UChat.chat(output.joinToString("\n")) + } + } } else if (text.startsWith("§r§c ☠ ")) { if (text.endsWith(" §r§7reconnected§r§7.§r")) { val match = reconnectedRegex.find(text) ?: return @@ -510,6 +538,11 @@ object DungeonListener { } var dead = false var deaths = 0 + var secretsDone = 0 + var roomsDone = 0 + var terminalsDone = 0 + var leversDone = 0 + var lastLivingStateChange: Long? = null val mapPlayer = DungeonMapPlayer(this, skin) diff --git a/src/main/kotlin/gg/skytils/skytilsws/client/PacketHandler.kt b/src/main/kotlin/gg/skytils/skytilsws/client/PacketHandler.kt index 1a0837375..29301c1f5 100644 --- a/src/main/kotlin/gg/skytils/skytilsws/client/PacketHandler.kt +++ b/src/main/kotlin/gg/skytils/skytilsws/client/PacketHandler.kt @@ -29,6 +29,7 @@ import gg.skytils.skytilsmod.features.impl.dungeons.catlas.utils.ScanUtils import gg.skytils.skytilsmod.features.impl.mining.CHWaypoints import gg.skytils.skytilsmod.features.impl.mining.CHWaypoints.CHInstance import gg.skytils.skytilsmod.features.impl.mining.CHWaypoints.chWaypointsList +import gg.skytils.skytilsmod.listeners.DungeonListener.team import gg.skytils.skytilsmod.utils.SBInfo import gg.skytils.skytilsws.shared.IPacketHandler import gg.skytils.skytilsws.shared.SkytilsWS @@ -62,6 +63,15 @@ object PacketHandler : IPacketHandler { DungeonInfo.uniqueRooms.find { it.mainRoom.data.name == packet.roomId }?.let { if (packet.secretCount > (it.foundSecrets ?: -1)) { it.foundSecrets = packet.secretCount + val finder = team[packet.finder] + + if (finder != null) { + finder.secretsDone++ + + if (packet.secretCount == it.mainRoom.data.secrets) { + finder.roomsDone++ + } + } } } } diff --git a/src/main/resources/assets/skytils/lang/en_US.lang b/src/main/resources/assets/skytils/lang/en_US.lang index c64d5306d..13affca83 100644 --- a/src/main/resources/assets/skytils/lang/en_US.lang +++ b/src/main/resources/assets/skytils/lang/en_US.lang @@ -23,6 +23,7 @@ skytils.config.dungeons.miscellaneous.auto_copy_fails_to_clipboard=Auto Copy Fai skytils.config.dungeons.quality_of_life.autoreparty_on_dungeon_ending=Auto-Reparty on Dungeon Ending skytils.config.dungeons.miscellaneous.death_counter=Death Counter skytils.config.dungeons.party_finder.party_finder_stats=Party Finder Stats +skytils.config.dungeons.miscellaneous.run_breakdown=Run Breakdown skytils.config.dungeons.miscellaneous.dungeon_chest_profit=Dungeon Chest Profit skytils.config.dungeons.miscellaneous.dungeon_chest_profit_includes_essence=Dungeon Chest Profit Includes Essence skytils.config.dungeons.miscellaneous.highlight_unopened_croesus_chests=Highlight Unopened Croesus Chests 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 06/11] 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 From fba63878e1a42727a9a2213a27162ade80cb1134 Mon Sep 17 00:00:00 2001 From: tako <74718793+TakoTheDev@users.noreply.github.com> Date: Fri, 13 Sep 2024 22:28:28 +0200 Subject: [PATCH 07/11] push --- ws-shared | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ws-shared b/ws-shared index 82ec010db..0b9f71823 160000 --- a/ws-shared +++ b/ws-shared @@ -1 +1 @@ -Subproject commit 82ec010dbad403f6ffbd567b0cd5e0ef875e7e74 +Subproject commit 0b9f71823750b7966b6bd5dc38ce3f69525ea36a From b3550c475fcd347a7b3a5c70504b6d8b9fe66f48 Mon Sep 17 00:00:00 2001 From: tako <74718793+TakoTheDev@users.noreply.github.com> Date: Fri, 13 Sep 2024 22:33:22 +0200 Subject: [PATCH 08/11] for azoo --- src/main/kotlin/gg/skytils/skytilsmod/core/Config.kt | 5 +++-- .../gg/skytils/skytilsmod/listeners/DungeonListener.kt | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/gg/skytils/skytilsmod/core/Config.kt b/src/main/kotlin/gg/skytils/skytilsmod/core/Config.kt index 03069c5f9..03a0e82a8 100644 --- a/src/main/kotlin/gg/skytils/skytilsmod/core/Config.kt +++ b/src/main/kotlin/gg/skytils/skytilsmod/core/Config.kt @@ -314,14 +314,15 @@ object Config : Vigilant( var partyFinderStats = false @Property( - type = PropertyType.SWITCH, name = "Run Breakdown", + type = PropertyType.SELECTOR, name = "Run Breakdown", description = "§b[WIP] Shows a Breakdown on what players did in the dungeon.", + options = ["Disabled", "Enabled", "Enabled + Terminals"], category = "Dungeons", subcategory = "Miscellaneous", i18nName = "skytils.config.dungeons.miscellaneous.run_breakdown", i18nCategory = "skytils.config.dungeons", i18nSubcategory = "skytils.config.dungeons.miscellaneous" ) - var runBreakdown = false + var runBreakdown = 0 @Property( type = PropertyType.SWITCH, name = "Dungeon Chest Profit", diff --git a/src/main/kotlin/gg/skytils/skytilsmod/listeners/DungeonListener.kt b/src/main/kotlin/gg/skytils/skytilsmod/listeners/DungeonListener.kt index 6db90307b..7ed2ed14a 100644 --- a/src/main/kotlin/gg/skytils/skytilsmod/listeners/DungeonListener.kt +++ b/src/main/kotlin/gg/skytils/skytilsmod/listeners/DungeonListener.kt @@ -193,13 +193,13 @@ object DungeonListener { if (Skytils.config.autoRepartyOnDungeonEnd) { RepartyCommand.processCommand(mc.thePlayer, emptyArray()) } - if (Skytils.config.runBreakdown) { + if (Skytils.config.runBreakdown != 0) { tickTimer(6) { val output = team.map { //TODO: Maybe also save the rank color? var output = "§6${it.key}§a | Secrets: §6${it.value.secretsDone}§a | Rooms: §6${it.value.roomsDone}§a | Deaths: §6${it.value.deaths}" - if (DungeonFeatures.dungeonFloorNumber == 7) { + if (Skytils.config.runBreakdown == 2 && DungeonFeatures.dungeonFloorNumber == 7) { output += "§a | Terminals: §6${it.value.terminalsDone}§a | Levers: §6${it.value.leversDone}" } From d50554f310a656fa80c7c45e5b6c9be175cd1c5f Mon Sep 17 00:00:00 2001 From: TakoTheDev <74718793+TakoTheDev@users.noreply.github.com> Date: Sat, 14 Sep 2024 07:19:41 +0200 Subject: [PATCH 09/11] test push [no ci] --- .../skytilsmod/listeners/DungeonListener.kt | 18 +++++++--- .../skytils/skytilsws/client/PacketHandler.kt | 33 ++++++++++++++----- ws-shared | 2 +- 3 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/main/kotlin/gg/skytils/skytilsmod/listeners/DungeonListener.kt b/src/main/kotlin/gg/skytils/skytilsmod/listeners/DungeonListener.kt index 7ed2ed14a..8c24cd901 100644 --- a/src/main/kotlin/gg/skytils/skytilsmod/listeners/DungeonListener.kt +++ b/src/main/kotlin/gg/skytils/skytilsmod/listeners/DungeonListener.kt @@ -151,7 +151,7 @@ object DungeonListener { if (room.foundSecrets != sec) { room.foundSecrets = sec if (team.size > 1) - WSClient.sendPacketAsync(C2SPacketDungeonRoomSecret(SBInfo.server ?: return@setFoundSecrets, room.mainRoom.data.name, sec, mc.thePlayer.name)) + WSClient.sendPacketAsync(C2SPacketDungeonRoomSecret(SBInfo.server ?: return@setFoundSecrets, room.mainRoom.data.name, sec)) } } } @@ -196,8 +196,16 @@ object DungeonListener { if (Skytils.config.runBreakdown != 0) { tickTimer(6) { val output = team.map { + val secretsDone = "§aSecrets: §6${if (it.value.minimumSecretsDone == it.value.maximumSecretsDone) { + "${it.value.minimumSecretsDone}" + } else "${it.value.minimumSecretsDone}§a - §6${it.value.maximumSecretsDone}"}" + + val roomsDone = "§aRooms: §6${if (it.value.minimumRoomsDone == it.value.maximumRoomsDone) { + "${it.value.minimumRoomsDone}" + } else "${it.value.minimumRoomsDone}§a - §6${it.value.maximumRoomsDone}"}" + //TODO: Maybe also save the rank color? - var output = "§6${it.key}§a | Secrets: §6${it.value.secretsDone}§a | Rooms: §6${it.value.roomsDone}§a | Deaths: §6${it.value.deaths}" + var output = "§6${it.key}§a | $secretsDone§a | $roomsDone§a | Deaths: §6${it.value.deaths}" if (Skytils.config.runBreakdown == 2 && DungeonFeatures.dungeonFloorNumber == 7) { output += "§a | Terminals: §6${it.value.terminalsDone}§a | Levers: §6${it.value.leversDone}" @@ -538,8 +546,10 @@ object DungeonListener { } var dead = false var deaths = 0 - var secretsDone = 0 - var roomsDone = 0 + var minimumSecretsDone = 0 + var maximumSecretsDone = 0 + var minimumRoomsDone = 0 + var maximumRoomsDone = 0 var terminalsDone = 0 var leversDone = 0 diff --git a/src/main/kotlin/gg/skytils/skytilsws/client/PacketHandler.kt b/src/main/kotlin/gg/skytils/skytilsws/client/PacketHandler.kt index 29301c1f5..5274df14e 100644 --- a/src/main/kotlin/gg/skytils/skytilsws/client/PacketHandler.kt +++ b/src/main/kotlin/gg/skytils/skytilsws/client/PacketHandler.kt @@ -60,16 +60,33 @@ object PacketHandler : IPacketHandler { } } is S2CPacketDungeonRoomSecret -> { - DungeonInfo.uniqueRooms.find { it.mainRoom.data.name == packet.roomId }?.let { - if (packet.secretCount > (it.foundSecrets ?: -1)) { - it.foundSecrets = packet.secretCount - val finder = team[packet.finder] + DungeonInfo.uniqueRooms.find { it.mainRoom.data.name == packet.roomId }?.let { room -> + if (packet.secretCount > (room.foundSecrets ?: -1)) { + room.foundSecrets = packet.secretCount + val finders = team.filter { entry -> + val location = entry.value.player?.playerLocation ?: return@filter false + val tile = ScanUtils.getRoomFromPos(location) - if (finder != null) { - finder.secretsDone++ + tile != null && tile.data.name != "Unknown" && room.mainRoom.data.name == tile.data.name + } + + if (finders.size >= 2) { + finders.forEach { + it.value.maximumSecretsDone++ + + if (packet.secretCount == room.mainRoom.data.secrets) { + it.value.maximumRoomsDone++ + } + } + } else if (finders.size == 1) { + finders.forEach { + it.value.minimumSecretsDone++ + it.value.maximumSecretsDone++ - if (packet.secretCount == it.mainRoom.data.secrets) { - finder.roomsDone++ + if (packet.secretCount == room.mainRoom.data.secrets) { + it.value.minimumRoomsDone++ + it.value.maximumRoomsDone++ + } } } } diff --git a/ws-shared b/ws-shared index 0b9f71823..c4624708d 160000 --- a/ws-shared +++ b/ws-shared @@ -1 +1 @@ -Subproject commit 0b9f71823750b7966b6bd5dc38ce3f69525ea36a +Subproject commit c4624708d1acb01ed9e6fc366b6179db78e73749 From b9bafde7625c1211468f894773388043208ca50f Mon Sep 17 00:00:00 2001 From: TakoTheDev <74718793+TakoTheDev@users.noreply.github.com> Date: Sat, 14 Sep 2024 12:38:10 +0200 Subject: [PATCH 10/11] fix: use client info instead --- .../gg/skytils/skytilsmod/core/Config.kt | 2 +- .../dungeons/catlas/core/DungeonMapPlayer.kt | 14 +++++ .../skytilsmod/listeners/DungeonListener.kt | 63 +++++++++++++++---- .../skytils/skytilsws/client/PacketHandler.kt | 29 +-------- ws-shared | 2 +- 5 files changed, 70 insertions(+), 40 deletions(-) diff --git a/src/main/kotlin/gg/skytils/skytilsmod/core/Config.kt b/src/main/kotlin/gg/skytils/skytilsmod/core/Config.kt index 03a0e82a8..0f3800eb7 100644 --- a/src/main/kotlin/gg/skytils/skytilsmod/core/Config.kt +++ b/src/main/kotlin/gg/skytils/skytilsmod/core/Config.kt @@ -315,7 +315,7 @@ object Config : Vigilant( @Property( type = PropertyType.SELECTOR, name = "Run Breakdown", - description = "§b[WIP] Shows a Breakdown on what players did in the dungeon.", + description = "§b[WIP] Shows a Breakdown on what players did in the dungeon.\n§eNote: Requires teammates to use Skytils Websocket otherwise it's highly inaccurate.", options = ["Disabled", "Enabled", "Enabled + Terminals"], category = "Dungeons", subcategory = "Miscellaneous", i18nName = "skytils.config.dungeons.miscellaneous.run_breakdown", diff --git a/src/main/kotlin/gg/skytils/skytilsmod/features/impl/dungeons/catlas/core/DungeonMapPlayer.kt b/src/main/kotlin/gg/skytils/skytilsmod/features/impl/dungeons/catlas/core/DungeonMapPlayer.kt index 3a74ef9d5..d13f597bc 100644 --- a/src/main/kotlin/gg/skytils/skytilsmod/features/impl/dungeons/catlas/core/DungeonMapPlayer.kt +++ b/src/main/kotlin/gg/skytils/skytilsmod/features/impl/dungeons/catlas/core/DungeonMapPlayer.kt @@ -18,9 +18,12 @@ package gg.skytils.skytilsmod.features.impl.dungeons.catlas.core +import gg.skytils.skytilsmod.features.impl.dungeons.catlas.handlers.DungeonScanner +import gg.skytils.skytilsmod.features.impl.dungeons.catlas.utils.MapUtils import gg.skytils.skytilsmod.listeners.DungeonListener import net.minecraft.entity.player.EntityPlayer import net.minecraft.entity.player.EnumPlayerModelParts +import net.minecraft.util.BlockPos import net.minecraft.util.ResourceLocation data class DungeonMapPlayer(val teammate: DungeonListener.DungeonTeammate, val skin: ResourceLocation) { @@ -41,4 +44,15 @@ data class DungeonMapPlayer(val teammate: DungeonListener.DungeonTeammate, val s uuid = player.uniqueID.toString() playerLoaded = true } + + fun getBlockPos(): BlockPos { + val playerPos = this.teammate.player?.playerLocation + if (playerPos != null) return playerPos + + val x = (this.mapX.toFloat() - MapUtils.startCorner.first) / MapUtils.coordMultiplier + DungeonScanner.startX - 15 + val y = 0.0 + val z = (this.mapZ.toFloat() - MapUtils.startCorner.second) / MapUtils.coordMultiplier + DungeonScanner.startZ - 15 + + return BlockPos(x, y, z) + } } diff --git a/src/main/kotlin/gg/skytils/skytilsmod/listeners/DungeonListener.kt b/src/main/kotlin/gg/skytils/skytilsmod/listeners/DungeonListener.kt index 8c24cd901..1c3f83e1f 100644 --- a/src/main/kotlin/gg/skytils/skytilsmod/listeners/DungeonListener.kt +++ b/src/main/kotlin/gg/skytils/skytilsmod/listeners/DungeonListener.kt @@ -38,6 +38,7 @@ import gg.skytils.skytilsmod.features.impl.dungeons.ScoreCalculation import gg.skytils.skytilsmod.features.impl.dungeons.catlas.core.DungeonMapPlayer import gg.skytils.skytilsmod.features.impl.dungeons.catlas.core.map.Room import gg.skytils.skytilsmod.features.impl.dungeons.catlas.core.map.RoomType +import gg.skytils.skytilsmod.features.impl.dungeons.catlas.core.map.UniqueRoom import gg.skytils.skytilsmod.features.impl.dungeons.catlas.handlers.DungeonInfo import gg.skytils.skytilsmod.features.impl.dungeons.catlas.utils.ScanUtils import gg.skytils.skytilsmod.features.impl.handlers.CooldownTracker @@ -150,6 +151,8 @@ object DungeonListener { val room = tile.uniqueRoom ?: return@setFoundSecrets if (room.foundSecrets != sec) { room.foundSecrets = sec + updateSecrets(room) + if (team.size > 1) WSClient.sendPacketAsync(C2SPacketDungeonRoomSecret(SBInfo.server ?: return@setFoundSecrets, room.mainRoom.data.name, sec)) } @@ -172,9 +175,7 @@ object DungeonListener { } } } - if (text.stripControlCodes() - .trim() == "> EXTRA STATS <" - ) { + if (text.stripControlCodes().trim() == "> EXTRA STATS <") { if (team.size > 1) { SBInfo.server?.let { WSClient.sendPacketAsync(C2SPacketDungeonEnd(it)) @@ -196,16 +197,21 @@ object DungeonListener { if (Skytils.config.runBreakdown != 0) { tickTimer(6) { val output = team.map { - val secretsDone = "§aSecrets: §6${if (it.value.minimumSecretsDone == it.value.maximumSecretsDone) { - "${it.value.minimumSecretsDone}" - } else "${it.value.minimumSecretsDone}§a - §6${it.value.maximumSecretsDone}"}" - - val roomsDone = "§aRooms: §6${if (it.value.minimumRoomsDone == it.value.maximumRoomsDone) { - "${it.value.minimumRoomsDone}" - } else "${it.value.minimumRoomsDone}§a - §6${it.value.maximumRoomsDone}"}" + val secretsDone = "§aSecrets: §6${ + if (it.value.minimumSecretsDone == it.value.maximumSecretsDone) { + "${it.value.minimumSecretsDone}" + } else "${it.value.minimumSecretsDone}§a - §6${it.value.maximumSecretsDone}" + }" + + val roomsDone = "§aRooms: §6${ + if (it.value.minimumRoomsDone == it.value.maximumRoomsDone) { + "${it.value.minimumRoomsDone}" + } else "${it.value.minimumRoomsDone}§a - §6${it.value.maximumRoomsDone}" + }" //TODO: Maybe also save the rank color? - var output = "§6${it.key}§a | $secretsDone§a | $roomsDone§a | Deaths: §6${it.value.deaths}" + var output = + "§6${it.key}§a | $secretsDone§a | $roomsDone§a | Deaths: §6${it.value.deaths}" if (Skytils.config.runBreakdown == 2 && DungeonFeatures.dungeonFloorNumber == 7) { output += "§a | Terminals: §6${it.value.terminalsDone}§a | Levers: §6${it.value.leversDone}" @@ -530,6 +536,41 @@ object DungeonListener { } } + fun updateSecrets(room: UniqueRoom) { + if (room.mainRoom.data.secrets < 1 || (room.foundSecrets ?: -1) < 1) return + + val finders = team.filter { entry -> + val location = entry.value.mapPlayer.getBlockPos() + val playerRoom = ScanUtils.getRoomFromPos(location)?.uniqueRoom + + playerRoom != null && playerRoom.name != "Unknown" && room.mainRoom.data.name == playerRoom.name + } + + if (finders.size >= 2) { + finders.forEach { + team[it.key]?.let { member -> + member.maximumSecretsDone++ + + if (room.foundSecrets == room.mainRoom.data.secrets) { + member.maximumRoomsDone++ + } + } + } + } else if (finders.size == 1) { + finders.forEach { + team[it.key]?.let { member -> + member.minimumSecretsDone++ + member.maximumSecretsDone++ + + if (room.foundSecrets == room.mainRoom.data.secrets) { + member.minimumRoomsDone++ + member.maximumRoomsDone++ + } + } + } + } + } + data class DungeonTeammate( val playerName: String, val dungeonClass: DungeonClass, diff --git a/src/main/kotlin/gg/skytils/skytilsws/client/PacketHandler.kt b/src/main/kotlin/gg/skytils/skytilsws/client/PacketHandler.kt index 5274df14e..d7189bec8 100644 --- a/src/main/kotlin/gg/skytils/skytilsws/client/PacketHandler.kt +++ b/src/main/kotlin/gg/skytils/skytilsws/client/PacketHandler.kt @@ -29,7 +29,7 @@ import gg.skytils.skytilsmod.features.impl.dungeons.catlas.utils.ScanUtils import gg.skytils.skytilsmod.features.impl.mining.CHWaypoints import gg.skytils.skytilsmod.features.impl.mining.CHWaypoints.CHInstance import gg.skytils.skytilsmod.features.impl.mining.CHWaypoints.chWaypointsList -import gg.skytils.skytilsmod.listeners.DungeonListener.team +import gg.skytils.skytilsmod.listeners.DungeonListener.updateSecrets import gg.skytils.skytilsmod.utils.SBInfo import gg.skytils.skytilsws.shared.IPacketHandler import gg.skytils.skytilsws.shared.SkytilsWS @@ -63,32 +63,7 @@ object PacketHandler : IPacketHandler { DungeonInfo.uniqueRooms.find { it.mainRoom.data.name == packet.roomId }?.let { room -> if (packet.secretCount > (room.foundSecrets ?: -1)) { room.foundSecrets = packet.secretCount - val finders = team.filter { entry -> - val location = entry.value.player?.playerLocation ?: return@filter false - val tile = ScanUtils.getRoomFromPos(location) - - tile != null && tile.data.name != "Unknown" && room.mainRoom.data.name == tile.data.name - } - - if (finders.size >= 2) { - finders.forEach { - it.value.maximumSecretsDone++ - - if (packet.secretCount == room.mainRoom.data.secrets) { - it.value.maximumRoomsDone++ - } - } - } else if (finders.size == 1) { - finders.forEach { - it.value.minimumSecretsDone++ - it.value.maximumSecretsDone++ - - if (packet.secretCount == room.mainRoom.data.secrets) { - it.value.minimumRoomsDone++ - it.value.maximumRoomsDone++ - } - } - } + updateSecrets(room) } } } diff --git a/ws-shared b/ws-shared index c4624708d..84ab7cf1c 160000 --- a/ws-shared +++ b/ws-shared @@ -1 +1 @@ -Subproject commit c4624708d1acb01ed9e6fc366b6179db78e73749 +Subproject commit 84ab7cf1c2b59dc2c2a48ed0b5b20530d34472b0 From 9d39e626f23b7d34ac3dcb88bec2f828a9460b49 Mon Sep 17 00:00:00 2001 From: TakoTheDev <74718793+TakoTheDev@users.noreply.github.com> Date: Sat, 14 Sep 2024 12:49:06 +0200 Subject: [PATCH 11/11] hopefully fixed it? --- ws-shared | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ws-shared b/ws-shared index 84ab7cf1c..82ec010db 160000 --- a/ws-shared +++ b/ws-shared @@ -1 +1 @@ -Subproject commit 84ab7cf1c2b59dc2c2a48ed0b5b20530d34472b0 +Subproject commit 82ec010dbad403f6ffbd567b0cd5e0ef875e7e74