aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/features/macros
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/kotlin/features/macros')
-rw-r--r--src/main/kotlin/features/macros/ComboProcessor.kt33
-rw-r--r--src/main/kotlin/features/macros/HotkeyAction.kt8
-rw-r--r--src/main/kotlin/features/macros/KeyComboTrie.kt27
-rw-r--r--src/main/kotlin/features/macros/MacroData.kt14
-rw-r--r--src/main/kotlin/features/macros/MacroUI.kt184
-rw-r--r--src/main/kotlin/features/macros/RadialMenu.kt153
6 files changed, 353 insertions, 66 deletions
diff --git a/src/main/kotlin/features/macros/ComboProcessor.kt b/src/main/kotlin/features/macros/ComboProcessor.kt
index 5c5ac0e..9dadb80 100644
--- a/src/main/kotlin/features/macros/ComboProcessor.kt
+++ b/src/main/kotlin/features/macros/ComboProcessor.kt
@@ -1,8 +1,7 @@
package moe.nea.firmament.features.macros
import kotlin.time.Duration.Companion.seconds
-import net.minecraft.client.util.InputUtil
-import net.minecraft.text.Text
+import net.minecraft.network.chat.Component
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.HudRenderEvent
import moe.nea.firmament.events.TickEvent
@@ -24,15 +23,6 @@ object ComboProcessor {
var lastInput = TimeMark.farPast()
val breadCrumbs = mutableListOf<SavedKeyBinding>()
- init {
- val f = SavedKeyBinding(InputUtil.GLFW_KEY_F)
- val one = SavedKeyBinding(InputUtil.GLFW_KEY_1)
- val two = SavedKeyBinding(InputUtil.GLFW_KEY_2)
- setActions(
- MacroData.DConfig.data.comboActions
- )
- }
-
fun setActions(actions: List<ComboKeyAction>) {
rootTrie = KeyComboTrie.fromComboList(actions)
reset()
@@ -56,15 +46,14 @@ object ComboProcessor {
fun onRender(event: HudRenderEvent) {
if (!isInputting) return
if (!event.isRenderingHud) return
- event.context.matrices.push()
+ event.context.pose().pushMatrix()
val width = 120
- event.context.matrices.translate(
- (MC.window.scaledWidth - width) / 2F,
- (MC.window.scaledHeight) / 2F + 8,
- 0F
+ event.context.pose().translate(
+ (MC.window.guiScaledWidth - width) / 2F,
+ (MC.window.guiScaledHeight) / 2F + 8
)
val breadCrumbText = breadCrumbs.joinToString(" > ")
- event.context.drawText(
+ event.context.drawString(
MC.font,
tr("firmament.combo.active", "Current Combo: ").append(breadCrumbText),
0,
@@ -72,19 +61,19 @@ object ComboProcessor {
-1,
true
)
- event.context.matrices.translate(0F, MC.font.fontHeight + 2F, 0F)
+ event.context.pose().translate(0F, MC.font.lineHeight + 2F)
for ((key, value) in activeTrie.nodes) {
- event.context.drawText(
+ event.context.drawString(
MC.font,
- Text.literal("$breadCrumbText > $key: ").append(value.label),
+ Component.literal("$breadCrumbText > $key: ").append(value.label),
0,
0,
-1,
true
)
- event.context.matrices.translate(0F, MC.font.fontHeight + 1F, 0F)
+ event.context.pose().translate(0F, MC.font.lineHeight + 1F)
}
- event.context.matrices.pop()
+ event.context.pose().popMatrix()
}
@Subscribe
diff --git a/src/main/kotlin/features/macros/HotkeyAction.kt b/src/main/kotlin/features/macros/HotkeyAction.kt
index 011f797..18c95bc 100644
--- a/src/main/kotlin/features/macros/HotkeyAction.kt
+++ b/src/main/kotlin/features/macros/HotkeyAction.kt
@@ -2,21 +2,21 @@ package moe.nea.firmament.features.macros
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
-import net.minecraft.text.Text
+import net.minecraft.network.chat.Component
import moe.nea.firmament.util.MC
@Serializable
sealed interface HotkeyAction {
// TODO: execute
- val label: Text
+ val label: Component
fun execute()
}
@Serializable
@SerialName("command")
data class CommandAction(val command: String) : HotkeyAction {
- override val label: Text
- get() = Text.literal("/$command")
+ override val label: Component
+ get() = Component.literal("/$command")
override fun execute() {
MC.sendCommand(command)
diff --git a/src/main/kotlin/features/macros/KeyComboTrie.kt b/src/main/kotlin/features/macros/KeyComboTrie.kt
index 57ff289..2701ac1 100644
--- a/src/main/kotlin/features/macros/KeyComboTrie.kt
+++ b/src/main/kotlin/features/macros/KeyComboTrie.kt
@@ -1,12 +1,12 @@
package moe.nea.firmament.features.macros
import kotlinx.serialization.Serializable
-import net.minecraft.text.Text
+import net.minecraft.network.chat.Component
import moe.nea.firmament.keybindings.SavedKeyBinding
import moe.nea.firmament.util.ErrorUtil
sealed interface KeyComboTrie {
- val label: Text
+ val label: Component
companion object {
fun fromComboList(
@@ -15,15 +15,15 @@ sealed interface KeyComboTrie {
val root = Branch(mutableMapOf())
for (combo in combos) {
var p = root
- if (combo.keys.isEmpty()) {
+ if (combo.keySequence.isEmpty()) {
ErrorUtil.softUserError("Key Combo for ${combo.action.label.string} is empty")
continue
}
- for ((index, key) in combo.keys.withIndex()) {
+ for ((index, key) in combo.keySequence.withIndex()) {
val m = (p.nodes as MutableMap)
- if (index == combo.keys.lastIndex) {
+ if (index == combo.keySequence.lastIndex) {
if (key in m) {
- ErrorUtil.softUserError("Overlapping actions found for ${combo.keys.joinToString(" > ")} (another action ${m[key]} already exists).")
+ ErrorUtil.softUserError("Overlapping actions found for ${combo.keySequence.joinToString(" > ")} (another action ${m[key]} already exists).")
break
}
@@ -31,7 +31,7 @@ sealed interface KeyComboTrie {
} else {
val c = m.getOrPut(key) { Branch(mutableMapOf()) }
if (c !is Branch) {
- ErrorUtil.softUserError("Overlapping actions found for ${combo.keys} (final node exists at index $index) through another action already")
+ ErrorUtil.softUserError("Overlapping actions found for ${combo.keySequence} (final node exists at index $index) through another action already")
break
} else {
p = c
@@ -44,15 +44,20 @@ sealed interface KeyComboTrie {
}
}
+@Serializable
+data class MacroWheel(
+ val keyBinding: SavedKeyBinding = SavedKeyBinding.unbound(),
+ val options: List<HotkeyAction>
+)
@Serializable
data class ComboKeyAction(
val action: HotkeyAction,
- val keys: List<SavedKeyBinding>,
+ val keySequence: List<SavedKeyBinding> = listOf(),
)
data class Leaf(val action: HotkeyAction) : KeyComboTrie {
- override val label: Text
+ override val label: Component
get() = action.label
fun execute() {
@@ -63,6 +68,6 @@ data class Leaf(val action: HotkeyAction) : KeyComboTrie {
data class Branch(
val nodes: Map<SavedKeyBinding, KeyComboTrie>
) : KeyComboTrie {
- override val label: Text
- get() = Text.literal("...") // TODO: better labels
+ override val label: Component
+ get() = Component.literal("...") // TODO: better labels
}
diff --git a/src/main/kotlin/features/macros/MacroData.kt b/src/main/kotlin/features/macros/MacroData.kt
index 78a5948..af1b0e8 100644
--- a/src/main/kotlin/features/macros/MacroData.kt
+++ b/src/main/kotlin/features/macros/MacroData.kt
@@ -1,11 +1,19 @@
package moe.nea.firmament.features.macros
import kotlinx.serialization.Serializable
+import moe.nea.firmament.util.data.Config
import moe.nea.firmament.util.data.DataHolder
@Serializable
data class MacroData(
- var comboActions: List<ComboKeyAction> = listOf(),
-){
- object DConfig : DataHolder<MacroData>(kotlinx.serialization.serializer(), "macros", ::MacroData)
+ var comboActions: List<ComboKeyAction> = listOf(),
+ var wheels: List<MacroWheel> = listOf(),
+) {
+ @Config
+ object DConfig : DataHolder<MacroData>(kotlinx.serialization.serializer(), "macros", ::MacroData) {
+ override fun onLoad() {
+ ComboProcessor.setActions(data.comboActions)
+ RadialMacros.setWheels(data.wheels)
+ }
+ }
}
diff --git a/src/main/kotlin/features/macros/MacroUI.kt b/src/main/kotlin/features/macros/MacroUI.kt
index 17fdd0a..e73f076 100644
--- a/src/main/kotlin/features/macros/MacroUI.kt
+++ b/src/main/kotlin/features/macros/MacroUI.kt
@@ -1,7 +1,9 @@
package moe.nea.firmament.features.macros
+import io.github.notenoughupdates.moulconfig.common.text.StructuredText
import io.github.notenoughupdates.moulconfig.gui.CloseEventListener
import io.github.notenoughupdates.moulconfig.observer.ObservableList
+import io.github.notenoughupdates.moulconfig.platform.MoulConfigPlatform
import io.github.notenoughupdates.moulconfig.xml.Bind
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.commands.thenExecute
@@ -32,7 +34,145 @@ class MacroUI {
@field:Bind("combos")
val combos = Combos()
- class Combos {
+ @field:Bind("wheels")
+ val wheels = Wheels()
+ var dontSave = false
+
+ @Bind
+ fun beforeClose(): CloseEventListener.CloseAction {
+ if (!dontSave)
+ save()
+ return CloseEventListener.CloseAction.NO_OBJECTIONS_TO_CLOSE
+ }
+
+ fun save() {
+ MacroData.DConfig.data.comboActions = combos.actions.map { it.asSaveable() }
+ MacroData.DConfig.data.wheels = wheels.wheels.map { it.asSaveable() }
+ MacroData.DConfig.markDirty()
+ RadialMacros.setWheels(MacroData.DConfig.data.wheels)
+ ComboProcessor.setActions(MacroData.DConfig.data.comboActions)
+ }
+
+ fun discard() {
+ dontSave = true
+ MC.screen?.onClose()
+ }
+
+ class Command(
+ @field:Bind("text")
+ var text: String,
+ val parent: Wheel,
+ ) {
+ @Bind
+ fun textR() = StructuredText.of(text)
+
+ @Bind
+ fun delete() {
+ parent.editableCommands.removeIf { it === this }
+ parent.editableCommands.update()
+ parent.commands.update()
+ }
+
+ fun asCommandAction() = CommandAction(text)
+ }
+
+ inner class Wheel(
+ val parent: Wheels,
+ var binding: SavedKeyBinding,
+ commands: List<CommandAction>,
+ ) {
+
+ fun asSaveable(): MacroWheel {
+ return MacroWheel(binding, commands.map { it.asCommandAction() })
+ }
+
+ @Bind("keyCombo")
+ fun text() = MoulConfigPlatform.wrap(binding.format())
+
+ @field:Bind("commands")
+ val commands = commands.mapTo(ObservableList(mutableListOf())) { Command(it.command, this) }
+
+ @field:Bind("editableCommands")
+ val editableCommands = this.commands.toObservableList()
+
+ @Bind
+ fun addOption() {
+ editableCommands.add(Command("", this))
+ }
+
+ @Bind
+ fun back() {
+ MC.screen?.onClose()
+ }
+
+ @Bind
+ fun edit() {
+ MC.screen = MoulConfigUtils.loadScreen("config/macros/editor_wheel", this, MC.screen)
+ }
+
+ @Bind
+ fun delete() {
+ parent.wheels.removeIf { it === this }
+ parent.wheels.update()
+ }
+
+ val sm = KeyBindingStateManager(
+ { binding },
+ { binding = it },
+ ::blur,
+ ::requestFocus
+ )
+
+ @field:Bind
+ val button = sm.createButton()
+
+ init {
+ sm.updateLabel()
+ }
+
+ fun blur() {
+ button.blur()
+ }
+
+
+ fun requestFocus() {
+ button.requestFocus()
+ }
+ }
+
+ inner class Wheels {
+ @field:Bind("wheels")
+ val wheels: ObservableList<Wheel> = MacroData.DConfig.data.wheels.mapTo(ObservableList(mutableListOf())) {
+ Wheel(this, it.keyBinding, it.options.map { CommandAction((it as CommandAction).command) })
+ }
+
+ @Bind
+ fun discard() {
+ this@MacroUI.discard()
+ }
+
+ @Bind
+ fun saveAndClose() {
+ this@MacroUI.saveAndClose()
+ }
+
+ @Bind
+ fun save() {
+ this@MacroUI.save()
+ }
+
+ @Bind
+ fun addWheel() {
+ wheels.add(Wheel(this, SavedKeyBinding.unbound(), listOf()))
+ }
+ }
+
+ fun saveAndClose() {
+ save()
+ MC.screen?.onClose()
+ }
+
+ inner class Combos {
@field:Bind("actions")
val actions: ObservableList<ActionEditor> = ObservableList(
MacroData.DConfig.data.comboActions.mapTo(mutableListOf()) {
@@ -40,15 +180,6 @@ class MacroUI {
}
)
- var dontSave = false
-
- @Bind
- fun beforeClose(): CloseEventListener.CloseAction {
- if (!dontSave)
- save()
- return CloseEventListener.CloseAction.NO_OBJECTIONS_TO_CLOSE
- }
-
@Bind
fun addCommand() {
actions.add(
@@ -64,21 +195,17 @@ class MacroUI {
@Bind
fun discard() {
- dontSave = true
- MC.screen?.close()
+ this@MacroUI.discard()
}
@Bind
fun saveAndClose() {
- save()
- MC.screen?.close()
+ this@MacroUI.saveAndClose()
}
@Bind
fun save() {
- MacroData.DConfig.data.comboActions = actions.map { it.asSaveable() }
- MacroData.DConfig.markDirty()
- ComboProcessor.setActions(MacroData.DConfig.data.comboActions) // TODO: automatically reload those from the config on startup
+ this@MacroUI.save()
}
}
@@ -101,18 +228,19 @@ class MacroUI {
button.blur()
}
+
+ fun requestFocus() {
+ button.requestFocus()
+ }
+
@Bind
fun delete() {
parent.combo.removeIf { it === this }
parent.combo.update()
}
-
- fun requestFocus() {
- button.requestFocus()
- }
}
- class ActionEditor(val action: ComboKeyAction, val parent: MacroUI.Combos) {
+ class ActionEditor(val action: ComboKeyAction, val parent: Combos) {
fun asSaveable(): ComboKeyAction {
return ComboKeyAction(
CommandAction(command),
@@ -123,12 +251,15 @@ class MacroUI {
@field:Bind("command")
var command: String = (action.action as CommandAction).command
+ @Bind
+ fun commandR() = StructuredText.of(command)
+
@field:Bind("combo")
- val combo = action.keys.map { KeyBindingEditor(it, this) }.toObservableList()
+ val combo = action.keySequence.map { KeyBindingEditor(it, this) }.toObservableList()
@Bind
fun formattedCombo() =
- combo.joinToString(" > ") { it.binding.toString() }
+ StructuredText.of(combo.joinToString(" > ") { it.binding.toString() }) // TODO: this can be joined without .toString()
@Bind
fun addStep() {
@@ -137,7 +268,7 @@ class MacroUI {
@Bind
fun back() {
- MC.screen?.close()
+ MC.screen?.onClose()
}
@Bind
@@ -145,9 +276,10 @@ class MacroUI {
parent.actions.removeIf { it === this }
parent.actions.update()
}
+
@Bind
fun edit() {
- MC.screen = MoulConfigUtils.loadScreen("config/macros/editor", this, MC.screen)
+ MC.screen = MoulConfigUtils.loadScreen("config/macros/editor_combo", this, MC.screen)
}
}
}
diff --git a/src/main/kotlin/features/macros/RadialMenu.kt b/src/main/kotlin/features/macros/RadialMenu.kt
new file mode 100644
index 0000000..2519123
--- /dev/null
+++ b/src/main/kotlin/features/macros/RadialMenu.kt
@@ -0,0 +1,153 @@
+package moe.nea.firmament.features.macros
+
+import me.shedaniel.math.Color
+import org.joml.Vector2f
+import util.render.CustomRenderLayers
+import kotlin.math.atan2
+import kotlin.math.cos
+import kotlin.math.sin
+import kotlin.math.sqrt
+import net.minecraft.client.gui.GuiGraphics
+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.events.WorldMouseMoveEvent
+import moe.nea.firmament.features.macros.RadialMenuViewer.RadialMenu
+import moe.nea.firmament.features.macros.RadialMenuViewer.RadialMenuOption
+import moe.nea.firmament.keybindings.SavedKeyBinding
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.render.RenderCircleProgress
+import moe.nea.firmament.util.render.drawLine
+import moe.nea.firmament.util.render.lerpAngle
+import moe.nea.firmament.util.render.wrapAngle
+import moe.nea.firmament.util.render.τ
+
+object RadialMenuViewer {
+ interface RadialMenu {
+ val key: SavedKeyBinding
+ val options: List<RadialMenuOption>
+ }
+
+ interface RadialMenuOption {
+ val isEnabled: Boolean
+ fun resolve()
+ fun renderSlice(drawContext: GuiGraphics)
+ }
+
+ var activeMenu: RadialMenu? = null
+ set(value) {
+ if (value?.options.isNullOrEmpty()) {
+ field = null
+ } else {
+ field = value
+ }
+ delta = Vector2f(0F, 0F)
+ }
+ var delta = Vector2f(0F, 0F)
+ val maxSelectionSize = 100F
+
+ @Subscribe
+ fun onMouseMotion(event: WorldMouseMoveEvent) {
+ val menu = activeMenu ?: return
+ event.cancel()
+ delta.add(event.deltaX.toFloat(), event.deltaY.toFloat())
+ val m = delta.lengthSquared()
+ if (m > maxSelectionSize * maxSelectionSize) {
+ delta.mul(maxSelectionSize / sqrt(m))
+ }
+ }
+
+ val INNER_CIRCLE_RADIUS = 16
+
+ @Subscribe
+ fun onRender(event: HudRenderEvent) {
+ val menu = activeMenu ?: return
+ val mat = event.context.pose()
+ mat.pushMatrix()
+ mat.translate(
+ (MC.window.guiScaledWidth) / 2F,
+ (MC.window.guiScaledHeight) / 2F,
+ )
+ val sliceWidth = (τ / menu.options.size).toFloat()
+ var selectedAngle = wrapAngle(atan2(delta.y, delta.x))
+ if (delta.lengthSquared() < INNER_CIRCLE_RADIUS * INNER_CIRCLE_RADIUS)
+ selectedAngle = Float.NaN
+ for ((idx, option) in menu.options.withIndex()) {
+ val range = (sliceWidth * idx)..(sliceWidth * (idx + 1))
+ mat.pushMatrix()
+ mat.scale(64F, 64F)
+ val cutout = INNER_CIRCLE_RADIUS / 64F / 2
+ RenderCircleProgress.renderCircularSlice(
+ event.context,
+ CustomRenderLayers.TRANSLUCENT_CIRCLE_GUI,
+ 0F, 1F, 0F, 1F,
+ range,
+ color = if (selectedAngle in range) 0x70A0A0A0 else 0x70FFFFFF,
+ innerCutoutRadius = cutout
+ )
+ mat.popMatrix()
+ mat.pushMatrix()
+ val centreAngle = lerpAngle(range.start, range.endInclusive, 0.5F)
+ val vec = Vector2f(cos(centreAngle), sin(centreAngle)).mul(40F)
+ mat.translate(vec.x, vec.y)
+ option.renderSlice(event.context)
+ mat.popMatrix()
+ }
+ event.context.drawLine(1, 1, delta.x.toInt(), delta.y.toInt(), Color.ofOpaque(0x00FF00))
+ mat.popMatrix()
+ }
+
+ @Subscribe
+ fun onTick(event: TickEvent) {
+ val menu = activeMenu ?: return
+ if (!menu.key.isPressed(true)) {
+ val angle = atan2(delta.y, delta.x)
+
+ val choiceIndex = (wrapAngle(angle) * menu.options.size / τ).toInt()
+ val choice = menu.options[choiceIndex]
+ val selectedAny = delta.lengthSquared() > INNER_CIRCLE_RADIUS * INNER_CIRCLE_RADIUS
+ activeMenu = null
+ if (selectedAny)
+ choice.resolve()
+ }
+ }
+
+}
+
+object RadialMacros {
+ lateinit var wheels: List<MacroWheel>
+ private set
+
+ fun setWheels(wheels: List<MacroWheel>) {
+ this.wheels = wheels
+ RadialMenuViewer.activeMenu = null
+ }
+
+ @Subscribe
+ fun onOpen(event: WorldKeyboardEvent) {
+ if (RadialMenuViewer.activeMenu != null) return
+ wheels.forEach { wheel ->
+ if (event.matches(wheel.keyBinding, atLeast = true)) {
+ class R(val action: HotkeyAction) : RadialMenuOption {
+ override val isEnabled: Boolean
+ get() = true
+
+ override fun resolve() {
+ action.execute()
+ }
+
+ override fun renderSlice(drawContext: GuiGraphics) {
+ drawContext.drawCenteredString(MC.font, action.label, 0, 0, -1)
+ }
+ }
+ RadialMenuViewer.activeMenu = object : RadialMenu {
+ override val key: SavedKeyBinding
+ get() = wheel.keyBinding
+ override val options: List<RadialMenuOption> =
+ wheel.options.map { R(it) }
+ }
+ }
+ }
+ }
+}