feat: Add basic combo buttons (without editor for now)

This commit is contained in:
Linnea Gräf
2025-06-04 01:06:51 +02:00
parent 9ad691bc1b
commit 2a1631dadf
6 changed files with 310 additions and 6 deletions

View File

@@ -0,0 +1,104 @@
package moe.nea.firmament.features.macros
import kotlin.time.Duration.Companion.seconds
import net.minecraft.client.util.InputUtil
import net.minecraft.text.Text
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.HudRenderEvent
import moe.nea.firmament.events.TickEvent
import moe.nea.firmament.events.WorldKeyboardEvent
import moe.nea.firmament.keybindings.SavedKeyBinding
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.TimeMark
object ComboProcessor {
var rootTrie: Branch = Branch(mapOf())
private set
var activeTrie: Branch = rootTrie
private set
var isInputting = false
var lastInput = TimeMark.farPast()
val breadCrumbs = mutableListOf<SavedKeyBinding>()
// TODO: keep breadcrumbs
init {
val f = SavedKeyBinding(InputUtil.GLFW_KEY_F)
val one = SavedKeyBinding(InputUtil.GLFW_KEY_1)
val two = SavedKeyBinding(InputUtil.GLFW_KEY_2)
setActions(
listOf(
ComboKeyAction(CommandAction("wardrobe"), listOf(f, one)),
ComboKeyAction(CommandAction("equipment"), listOf(f, two)),
)
)
}
fun setActions(actions: List<ComboKeyAction>) {
rootTrie = KeyComboTrie.fromComboList(actions)
reset()
}
fun reset() {
activeTrie = rootTrie
lastInput = TimeMark.now()
isInputting = false
breadCrumbs.clear()
}
@Subscribe
fun onTick(event: TickEvent) {
if (isInputting && lastInput.passedTime() > 3.seconds)
reset()
}
@Subscribe
fun onRender(event: HudRenderEvent) {
if (!isInputting) return
if (!event.isRenderingHud) return
event.context.matrices.push()
val width = 120
event.context.matrices.translate(
(MC.window.scaledWidth - width) / 2F,
(MC.window.scaledHeight) / 2F + 8,
0F
)
val breadCrumbText = breadCrumbs.joinToString(" > ")
event.context.drawText(MC.font, breadCrumbText, 0, 0, -1, true)
event.context.matrices.translate(0F, MC.font.fontHeight + 2F, 0F)
for ((key, value) in activeTrie.nodes) {
event.context.drawText(MC.font, Text.literal("$breadCrumbText > $key: ").append(value.label), 0, 0, -1, true)
event.context.matrices.translate(0F, MC.font.fontHeight + 1F, 0F)
}
event.context.matrices.pop()
}
@Subscribe
fun onKeyBinding(event: WorldKeyboardEvent) {
val nextEntry = activeTrie.nodes.entries
.find { event.matches(it.key) }
if (nextEntry == null) {
reset()
return
}
event.cancel()
breadCrumbs.add(nextEntry.key)
lastInput = TimeMark.now()
isInputting = true
val value = nextEntry.value
when (value) {
is Branch -> {
activeTrie = value
}
is Leaf -> {
value.execute()
reset()
}
}.let { }
}
}

View File

@@ -0,0 +1,35 @@
package moe.nea.firmament.features.macros
import net.minecraft.text.Text
import moe.nea.firmament.util.MC
interface HotkeyAction {
// TODO: execute
val label: Text
fun execute()
}
data class CommandAction(val command: String) : HotkeyAction {
override val label: Text
get() = Text.literal("/$command")
override fun execute() {
MC.sendCommand(command)
}
}
// Mit onscreen anzeige:
// F -> 1 /equipment
// F -> 2 /wardrobe
// Bei Combos: Keys buffern! (für wardrobe hotkeys beispielsweiße)
// Radial menu
// Hold F
// Weight (mach eins doppelt so groß)
// /equipment
// /wardrobe
// Bei allen: Filter!
// - Nur in Dungeons / andere Insel
// - Nur wenn ich Item X im inventar habe (fishing rod)

View File

@@ -0,0 +1,57 @@
package moe.nea.firmament.features.macros
import net.minecraft.text.Text
import moe.nea.firmament.keybindings.SavedKeyBinding
sealed interface KeyComboTrie {
val label: Text
companion object {
fun fromComboList(
combos: List<ComboKeyAction>,
): Branch {
val root = Branch(mutableMapOf())
for (combo in combos) {
var p = root
require(combo.keys.isNotEmpty())
for ((index, key) in combo.keys.withIndex()) {
val m = (p.nodes as MutableMap)
if (index == combo.keys.lastIndex) {
if (key in m)
error("Overlapping actions found for ${combo.keys} (another action ${m[key]} already exists).")
m[key] = Leaf(combo.action)
} else {
val c = m.getOrPut(key) { Branch(mutableMapOf()) }
if (c !is Branch)
error("Overlapping actions found for ${combo.keys} (final node exists at index $index) through another action already")
p = c
}
}
}
return root
}
}
}
data class ComboKeyAction(
val action: HotkeyAction,
val keys: List<SavedKeyBinding>,
)
data class Leaf(val action: HotkeyAction) : KeyComboTrie {
override val label: Text
get() = action.label
fun execute() {
action.execute()
}
}
data class Branch(
val nodes: Map<SavedKeyBinding, KeyComboTrie>
) : KeyComboTrie {
override val label: Text
get() = Text.literal("...") // TODO: better labels
}