diff options
Diffstat (limited to 'src/main/kotlin/features/macros')
| -rw-r--r-- | src/main/kotlin/features/macros/ComboProcessor.kt | 33 | ||||
| -rw-r--r-- | src/main/kotlin/features/macros/HotkeyAction.kt | 8 | ||||
| -rw-r--r-- | src/main/kotlin/features/macros/KeyComboTrie.kt | 27 | ||||
| -rw-r--r-- | src/main/kotlin/features/macros/MacroData.kt | 14 | ||||
| -rw-r--r-- | src/main/kotlin/features/macros/MacroUI.kt | 184 | ||||
| -rw-r--r-- | src/main/kotlin/features/macros/RadialMenu.kt | 153 |
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) } + } + } + } + } +} |
