diff options
| author | Thunderblade73 <85900443+Thunderblade73@users.noreply.github.com> | 2024-06-10 03:11:42 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-06-10 03:11:42 +0200 |
| commit | 1fe45f026afeff9d87107380bf8c4cf58051b29f (patch) | |
| tree | 362a2d3cf9f0e1c4dbfabb710cd51ab0c7c11ee3 /src/main/java/at/hannibal2/skyhanni/test | |
| parent | b1ff611e4f764c5fba338571540f0ad2389994ac (diff) | |
| download | skyhanni-1fe45f026afeff9d87107380bf8c4cf58051b29f.tar.gz skyhanni-1fe45f026afeff9d87107380bf8c4cf58051b29f.tar.bz2 skyhanni-1fe45f026afeff9d87107380bf8c4cf58051b29f.zip | |
Backend: Graph Editor (#1454)
Co-authored-by: Cal <cwolfson58@gmail.com>
Co-authored-by: CalMWolfs <94038482+CalMWolfs@users.noreply.github.com>
Co-authored-by: hannibal2 <24389977+hannibal00212@users.noreply.github.com>
Diffstat (limited to 'src/main/java/at/hannibal2/skyhanni/test')
3 files changed, 490 insertions, 4 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/test/GraphEditor.kt b/src/main/java/at/hannibal2/skyhanni/test/GraphEditor.kt new file mode 100644 index 000000000..618e2d861 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/test/GraphEditor.kt @@ -0,0 +1,480 @@ +package at.hannibal2.skyhanni.test + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.data.model.Graph +import at.hannibal2.skyhanni.data.model.GraphNode +import at.hannibal2.skyhanni.data.model.TextInput +import at.hannibal2.skyhanni.data.model.findShortestPathAsGraph +import at.hannibal2.skyhanni.data.model.toJson +import at.hannibal2.skyhanni.events.GuiRenderEvent +import at.hannibal2.skyhanni.events.LorenzRenderWorldEvent +import at.hannibal2.skyhanni.events.LorenzTickEvent +import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule +import at.hannibal2.skyhanni.test.GriffinUtils.drawWaypointFilled +import at.hannibal2.skyhanni.test.command.ErrorManager +import at.hannibal2.skyhanni.utils.ChatUtils +import at.hannibal2.skyhanni.utils.ColorUtils +import at.hannibal2.skyhanni.utils.KeyboardManager +import at.hannibal2.skyhanni.utils.KeyboardManager.isKeyClicked +import at.hannibal2.skyhanni.utils.KeyboardManager.isKeyHeld +import at.hannibal2.skyhanni.utils.LocationUtils +import at.hannibal2.skyhanni.utils.LocationUtils.distanceSqToPlayer +import at.hannibal2.skyhanni.utils.LorenzColor +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzVec +import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators +import at.hannibal2.skyhanni.utils.OSUtils +import at.hannibal2.skyhanni.utils.RenderUtils.draw3DLine_nea +import at.hannibal2.skyhanni.utils.RenderUtils.drawDynamicText +import at.hannibal2.skyhanni.utils.RenderUtils.renderStrings +import kotlinx.coroutines.runBlocking +import net.minecraft.client.settings.KeyBinding +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable + +@SkyHanniModule +object GraphEditor { + + private val config get() = SkyHanniMod.feature.dev.devTool.graph + + private fun isEnabled() = config != null && config.enabled + + private var id = 0 + + private val nodes = mutableListOf<GraphingNode>() + private val edges = mutableListOf<GraphingEdge>() + + private var activeNode: GraphingNode? = null + private var closedNode: GraphingNode? = null + + private var seeThroughBlocks = true + + private var inEditMode = false + private var inTextMode = false + set(value) { + field = value + if (value) { + activeNode?.name?.let { + textBox.textBox = it + } + textBox.makeActive() + } else { + textBox.clear() + textBox.disable() + } + } + + private var inTutorialMode = false + + private val textBox = TextInput() + + private val nodeColor = LorenzColor.BLUE.addOpacity(200) + private val activeColor = LorenzColor.GREEN.addOpacity(200) + private val closedColor = LorenzColor.YELLOW.addOpacity(200) + private val dijkstraColor = LorenzColor.LIGHT_PURPLE.addOpacity(200) + + private val edgeColor = LorenzColor.GOLD.addOpacity(150) + private val edgeDijkstraColor = LorenzColor.DARK_BLUE.addOpacity(150) + + @SubscribeEvent + fun onRender(event: LorenzRenderWorldEvent) { + if (!isEnabled()) return + nodes.forEach { event.drawNode(it) } + edges.forEach { event.drawEdge(it) } + } + + @SubscribeEvent + fun onOverlay(event: GuiRenderEvent.GuiOverlayRenderEvent) { + if (!isEnabled()) return + config.infoDisplay.renderStrings(buildDisplay(), posLabel = "Graph Info") + } + + private fun buildDisplay(): List<String> = buildList { + add("§eExit: §6${KeyboardManager.getKeyName(config.exitKey)}") + if (!inEditMode && !inTextMode) { + add("§ePlace: §6${KeyboardManager.getKeyName(config.placeKey)}") + add("§eSelect: §6${KeyboardManager.getKeyName(config.selectKey)}") + add("§eConnect: §6${KeyboardManager.getKeyName(config.connectKey)}") + add("§eTest: §6${KeyboardManager.getKeyName(config.dijkstraKey)}") + add("§eVision: §6${KeyboardManager.getKeyName(config.throughBlocksKey)}") + add("§eSave: §6${KeyboardManager.getKeyName(config.saveKey)}") + add("§eLoad: §6${KeyboardManager.getKeyName(config.loadKey)}") + add("§eClear: §6${KeyboardManager.getKeyName(config.clearKey)}") + add("§eTutorial: §6${KeyboardManager.getKeyName(config.tutorialKey)}") + add(" ") + if (activeNode != null) add("§eText: §6${KeyboardManager.getKeyName(config.textKey)}") + } + if (!inTextMode && activeNode != null) { + add("§eEdit: §6${KeyboardManager.getKeyName(config.editKey)}") + } + if (inEditMode) { + add("§ex+ §6${KeyboardManager.getKeyName(KeyboardManager.WasdInputMatrix.w.keyCode)}") + add("§ex- §6${KeyboardManager.getKeyName(KeyboardManager.WasdInputMatrix.s.keyCode)}") + add("§ez+ §6${KeyboardManager.getKeyName(KeyboardManager.WasdInputMatrix.a.keyCode)}") + add("§ez- §6${KeyboardManager.getKeyName(KeyboardManager.WasdInputMatrix.d.keyCode)}") + add("§ey+ §6${KeyboardManager.getKeyName(KeyboardManager.WasdInputMatrix.up.keyCode)}") + add("§ey- §6${KeyboardManager.getKeyName(KeyboardManager.WasdInputMatrix.down.keyCode)}") + } + if (inTextMode) { + add("§eFormat: ${textBox.finalText()}") + add("§eRaw: ${textBox.editText()}") + } + } + + private fun feedBackInTutorial(text: String) { + if (inTutorialMode) { + ChatUtils.chat(text) + } + } + + @SubscribeEvent + fun onTick(event: LorenzTickEvent) { + if (!isEnabled()) return + input() + if (nodes.isEmpty()) return + closedNode = nodes.minBy { it.position.distanceSqToPlayer() } + } + + private fun LorenzRenderWorldEvent.drawNode(node: GraphingNode) { + this.drawWaypointFilled( + node.position, + node.getNodeColor(), + seeThroughBlocks = seeThroughBlocks, + minimumAlpha = 0.2f, + inverseAlphaScale = true, + ) + if (node.name == null) return + this.drawDynamicText( + node.position, + node.name!!, + 0.8, + ignoreBlocks = seeThroughBlocks || node.position.distanceSqToPlayer() < 100, + smallestDistanceVew = 12.0, + ignoreY = true, + yOff = -15f, + ) + } + + private fun LorenzRenderWorldEvent.drawEdge(edge: GraphingEdge) = this.draw3DLine_nea( + edge.node1.position.add(0.5, 0.5, 0.5), + edge.node2.position.add(0.5, 0.5, 0.5), + if (edge !in highlightedEdges) edgeColor else edgeDijkstraColor, + 7, + !seeThroughBlocks, + ) + + private fun GraphingNode.getNodeColor() = when (this) { + activeNode -> if (this == closedNode) ColorUtils.blendRGB(activeColor, closedColor, 0.5) else activeColor + closedNode -> closedColor + in highlightedNodes -> dijkstraColor + else -> nodeColor + } + + fun commandIn() { + config.enabled = !config.enabled + if (config.enabled) { + ChatUtils.chat("Graph Editor is now active.") + } else { + chatAtDisable() + } + } + + private fun chatAtDisable() = + ChatUtils.clickableChat("Graph Editor is now inactive. §lClick to activate.", ::commandIn) + + private fun input() { + if (LorenzUtils.isAnyGuiActive()) return + if (config.exitKey.isKeyClicked()) { + if (inTextMode) { + inTextMode = false + feedBackInTutorial("Exited Text Mode.") + return + } + if (inEditMode) { + inEditMode = false + feedBackInTutorial("Exited Edit Mode.") + return + } + config.enabled = false + chatAtDisable() + } + if (inTextMode) { + textBox.handle() + val text = textBox.finalText() + if (text.isEmpty()) { + activeNode?.name = null + } else { + activeNode?.name = text + } + return + } + if (activeNode != null && config.textKey.isKeyClicked()) { + inTextMode = true + feedBackInTutorial("Entered Text Mode.") + return + } + if (inEditMode) { + editModeClicks() + inEditMode = false + } + if (activeNode != null && config.editKey.isKeyHeld()) { + inEditMode = true + return + } + if (config.saveKey.isKeyClicked()) { + save() + return + } + if (config.loadKey.isKeyClicked()) { + runBlocking { + OSUtils.readFromClipboard()?.let { + try { + Graph.fromJson(it) + } catch (e: Exception) { + ErrorManager.logErrorWithData( + e, + "Import of graph failed.", + "json" to it, + ignoreErrorCache = true, + ) + null + } + }?.let { + import(it) + ChatUtils.chat("Loaded Graph from clip board.") + } + } + return + } + if (config.clearKey.isKeyClicked()) { + val json = compileGraph().toJson() + OSUtils.copyToClipboard(json) + ChatUtils.chat("Copied Graph to Clipboard and cleared the graph.") + clear() + } + if (config.placeKey.isKeyClicked()) { + addNode() + } + if (config.selectKey.isKeyClicked()) { + activeNode = if (activeNode == closedNode) { + feedBackInTutorial("De selected active node.") + null + } else { + feedBackInTutorial("Selected new active node.") + closedNode + } + } + if (activeNode != closedNode && config.connectKey.isKeyClicked()) { + val edge = getEdgeIndex(activeNode, closedNode) + if (edge == null) { + addEdge(activeNode, closedNode) + feedBackInTutorial("Added new edge.") + } else { + this.edges.removeAt(edge) + feedBackInTutorial("Removed edge.") + } + } + if (config.throughBlocksKey.isKeyClicked()) { + seeThroughBlocks = !seeThroughBlocks + feedBackInTutorial( + if (seeThroughBlocks) "Graph is visible though walls." else "Graph is invisible behind walls.", + ) + } + if (config.dijkstraKey.isKeyClicked()) { + feedBackInTutorial("Calculated shortest route and cleared active node.") + testDijkstra() + } + if (config.tutorialKey.isKeyClicked()) { + inTutorialMode = !inTutorialMode + ChatUtils.chat("Tutorial mode is now ${if (inTutorialMode) "active" else "inactive"}.") + } + } + + private fun save() { + if (nodes.isEmpty()) { + ChatUtils.chat("Copied nothing since the graph is empty.") + return + } + val json = compileGraph().toJson() + OSUtils.copyToClipboard(json) + ChatUtils.chat("Copied Graph to Clipboard.") + if (config.showsStats) { + val length = edges.sumOf { it.node1.position.distance(it.node2.position) }.toInt().addSeparators() + ChatUtils.chat( + "§lStats\n" + + "§eNodes: ${nodes.size}\n" + + "§eEdges: ${edges.size}\n" + + "§eLength: $length", + ) + } + } + + private fun editModeClicks() { + KeyboardManager.WasdInputMatrix.w.handleEditClicks(x = 1) + KeyboardManager.WasdInputMatrix.s.handleEditClicks(x = -1) + KeyboardManager.WasdInputMatrix.a.handleEditClicks(z = 1) + KeyboardManager.WasdInputMatrix.d.handleEditClicks(z = -1) + KeyboardManager.WasdInputMatrix.up.handleEditClicks(y = 1) + KeyboardManager.WasdInputMatrix.down.handleEditClicks(y = -1) + } + + private fun KeyBinding.handleEditClicks(x: Int = 0, y: Int = 0, z: Int = 0) { + if (this.keyCode.isKeyClicked()) { + activeNode?.position = activeNode?.position?.add(x, y, z) ?: return + } + } + + fun onMinecraftInput(keyBinding: KeyBinding, cir: CallbackInfoReturnable<Boolean>) { + if (!isEnabled()) return + if (!inEditMode) return + if (keyBinding !in KeyboardManager.WasdInputMatrix) return + cir.returnValue = false + } + + private fun addNode() { + val closedNode = closedNode + if (closedNode != null && closedNode.position.distanceSqToPlayer() < 9.0) { + feedBackInTutorial("Removed node, since you where closer than 3 blocks from a node.") + nodes.remove(closedNode) + edges.removeIf { it.isInEdge(closedNode) } + if (closedNode == activeNode) activeNode = null + this.closedNode = null + return + } + val position = LocationUtils.playerEyeLocation().roundLocationToBlock() + val node = GraphingNode(id++, position) + nodes.add(node) + feedBackInTutorial("Added graph node.") + if (activeNode == null) return + addEdge(activeNode, node) + } + + private fun getEdgeIndex(node1: GraphingNode?, node2: GraphingNode?) = + if (node1 != null && node2 != null && node1 != node2) GraphingEdge( + node1, + node2, + ).let { e -> edges.indexOfFirst { it == e }.takeIf { it != -1 } } + else null + + private fun addEdge(node1: GraphingNode?, node2: GraphingNode?) = + if (node1 != null && node2 != null && node1 != node2) edges.add(GraphingEdge(node1, node2)) else false + + /** Has a side effect on the graphing graph, since it runs [prune] on the graphing graph*/ + private fun compileGraph(): Graph { + prune() + val indexedTable = nodes.mapIndexed { index, node -> node.id to index }.toMap() + val nodes = nodes.mapIndexed { index, it -> GraphNode(index, it.position, it.name) } + val neighbours = this.nodes.map { node -> + edges.filter { it.isInEdge(node) }.map { edge -> + val otherNode = if (node == edge.node1) edge.node2 else edge.node1 + nodes[indexedTable[otherNode.id]!!] to node.position.distance(otherNode.position) + }.sortedBy { it.second } + } + nodes.forEachIndexed { index, it -> it.neighbours = neighbours[index].toMap() } + return Graph(nodes) + } + + fun import(graph: Graph) { + clear() + nodes.addAll(graph.map { GraphingNode(it.id, it.position, it.name) }) + val translation = graph.mapIndexed { index, it -> it to nodes[index] }.toMap() + edges.addAll( + graph.map { node -> + node.neighbours.map { GraphingEdge(translation[node]!!, translation[it.key]!!) } + }.flatten().distinct(), + ) + id = nodes.lastOrNull()?.id?.plus(1) ?: 0 + } + + private val highlightedNodes = mutableSetOf<GraphingNode>() + private val highlightedEdges = mutableSetOf<GraphingEdge>() + + private fun testDijkstra() { + + val savedCurrent = closedNode ?: return + val savedActive = activeNode ?: return + + val compiled = compileGraph() + import(compiled) + highlightedEdges.clear() + highlightedNodes.clear() + + val current = compiled.firstOrNull { it.position == savedCurrent.position } ?: return + val goal = compiled.firstOrNull { it.position == savedActive.position } ?: return + + val path = compiled.findShortestPathAsGraph(current, goal) + + val inGraph = path.map { nodes[it.id] } + highlightedNodes.addAll(inGraph) + + val edge = edges.filter { highlightedNodes.contains(it.node1) && highlightedNodes.contains(it.node2) } + highlightedEdges.addAll(edge) + } + + private fun clear() { + id = 0 + nodes.clear() + edges.clear() + activeNode = null + closedNode = null + } + + private fun prune() { //TODO fix + val hasNeighbours = nodes.associateWith { false }.toMutableMap() + edges.forEach { + hasNeighbours[it.node1] = true + hasNeighbours[it.node2] = true + } + nodes.removeIf { hasNeighbours[it] == false } + } +} + +private class GraphingNode(val id: Int, var position: LorenzVec, var name: String? = null) { + + override fun hashCode(): Int { + return id + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as GraphingNode + + if (id != other.id) return false + + return true + } +} + +private class GraphingEdge(val node1: GraphingNode, val node2: GraphingNode) { + + fun isInEdge(node: GraphingNode) = node1 == node || node2 == node + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as GraphingEdge + + return (this.node1 == other.node1 && this.node2 == other.node2) || + (this.node1 == other.node2 && this.node2 == other.node1) + } + + override fun hashCode(): Int { + val hash1 = node1.hashCode() + val hash2 = node2.hashCode() + + var result: Int + if (hash1 <= hash2) { + result = hash1 + result = 31 * result + hash2 + } else { + result = hash2 + result = 31 * result + hash1 + } + return result + } + +} + diff --git a/src/main/java/at/hannibal2/skyhanni/test/GriffinUtils.kt b/src/main/java/at/hannibal2/skyhanni/test/GriffinUtils.kt index b957129cc..195f06989 100644 --- a/src/main/java/at/hannibal2/skyhanni/test/GriffinUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/test/GriffinUtils.kt @@ -18,6 +18,8 @@ object GriffinUtils { extraSize: Double = 0.0, extraSizeTopY: Double = extraSize, extraSizeBottomY: Double = extraSize, + minimumAlpha: Float = 0.2f, + inverseAlphaScale: Boolean = false, ) { val (viewerX, viewerY, viewerZ) = RenderUtils.getViewerPos(partialTicks) val x = location.x - viewerX @@ -27,24 +29,27 @@ object GriffinUtils { if (seeThroughBlocks) { GlStateManager.disableDepth() - GlStateManager.disableCull() } + + GlStateManager.disableCull() RenderUtils.drawFilledBoundingBox( + @Suppress("ktlint:standard:argument-list-wrapping") AxisAlignedBB( x - extraSize, y - extraSizeBottomY, z - extraSize, - x + 1 + extraSize, y + 1 + extraSizeTopY, z + 1 + extraSize + x + 1 + extraSize, y + 1 + extraSizeTopY, z + 1 + extraSize, ).expandBlock(), color, - (0.1f + 0.005f * distSq.toFloat()).coerceAtLeast(0.2f) + if (inverseAlphaScale) (1.0f - 0.005f * distSq.toFloat()).coerceAtLeast(minimumAlpha) + else (0.1f + 0.005f * distSq.toFloat()).coerceAtLeast(minimumAlpha), ) GlStateManager.disableTexture2D() if (distSq > 5 * 5 && beacon) RenderUtils.renderBeaconBeam(x, y + 1, z, color.rgb, 1.0f, partialTicks) GlStateManager.disableLighting() GlStateManager.enableTexture2D() + GlStateManager.enableCull() if (seeThroughBlocks) { GlStateManager.enableDepth() - GlStateManager.enableCull() } } } diff --git a/src/main/java/at/hannibal2/skyhanni/test/ParkourWaypointSaver.kt b/src/main/java/at/hannibal2/skyhanni/test/ParkourWaypointSaver.kt index 15ef4ac5c..d56bf7a66 100644 --- a/src/main/java/at/hannibal2/skyhanni/test/ParkourWaypointSaver.kt +++ b/src/main/java/at/hannibal2/skyhanni/test/ParkourWaypointSaver.kt @@ -30,6 +30,7 @@ object ParkourWaypointSaver { if (!LorenzUtils.inSkyBlock && !config.parkourOutsideSB) return if (Minecraft.getMinecraft().currentScreen != null) return if (NEUItems.neuHasFocus()) return + if (SkyHanniMod.feature.dev.devTool.graph.enabled) return if (timeLastSaved.passedSince() < 250.milliseconds) return when (event.keyCode) { |
