From 4ff0ba622fa5348725df41853c5a198e07d83aaa Mon Sep 17 00:00:00 2001 From: Bixilon Date: Sat, 21 May 2022 00:08:02 +0200 Subject: [PATCH] command sending, print target --- .../minosoft/commands/nodes/ArgumentNode.kt | 2 +- .../minosoft/commands/nodes/ChatNode.kt | 38 ++++++++--- .../minosoft/commands/nodes/CommandNode.kt | 12 +++- .../minosoft/commands/nodes/LiteralNode.kt | 2 +- .../minosoft/commands/stack/CommandStack.kt | 13 +++- .../commands/stack/print/PlayerPrintTarget.kt | 23 +++++++ .../commands/stack/print/PrintTarget.kt | 19 ++++++ .../commands/stack/print/SystemPrintTarget.kt | 25 +++++++ .../elements/input/NodeTextInputElement.kt | 67 +++++++++++++++++++ .../gui/elements/input/TextInputElement.kt | 15 +++-- .../gui/hud/elements/chat/ChatElement.kt | 11 ++- .../minosoft/terminal/commands/HelpCommand.kt | 15 +++-- 12 files changed, 213 insertions(+), 29 deletions(-) create mode 100644 src/main/java/de/bixilon/minosoft/commands/stack/print/PlayerPrintTarget.kt create mode 100644 src/main/java/de/bixilon/minosoft/commands/stack/print/PrintTarget.kt create mode 100644 src/main/java/de/bixilon/minosoft/commands/stack/print/SystemPrintTarget.kt create mode 100644 src/main/java/de/bixilon/minosoft/gui/rendering/gui/gui/elements/input/NodeTextInputElement.kt diff --git a/src/main/java/de/bixilon/minosoft/commands/nodes/ArgumentNode.kt b/src/main/java/de/bixilon/minosoft/commands/nodes/ArgumentNode.kt index 254024ffb..5cdcc08c7 100644 --- a/src/main/java/de/bixilon/minosoft/commands/nodes/ArgumentNode.kt +++ b/src/main/java/de/bixilon/minosoft/commands/nodes/ArgumentNode.kt @@ -66,7 +66,7 @@ class ArgumentNode : ExecutableNode { } val suggestions = parser.getSuggestions(reader) - if (reader.canPeekNext()) { + if (reader.canPeekNext() || suggestions.isEmpty()) { throw parseError } return suggestions diff --git a/src/main/java/de/bixilon/minosoft/commands/nodes/ChatNode.kt b/src/main/java/de/bixilon/minosoft/commands/nodes/ChatNode.kt index 7599c899b..9e460ec31 100644 --- a/src/main/java/de/bixilon/minosoft/commands/nodes/ChatNode.kt +++ b/src/main/java/de/bixilon/minosoft/commands/nodes/ChatNode.kt @@ -28,23 +28,45 @@ class ChatNode( override fun execute(reader: CommandReader, stack: CommandStack) { reader.skipWhitespaces() - val peek = reader.unsafePeek() + val node = getNode(reader, stack) val string = parser.parse(reader) - val node = getNode(peek, stack) - if (node != CLI.ROOT_NODE) { - stack.connection.util.sendChatMessage(string) + if (node != CLI.ROOT_NODE && string.isNotBlank()) { + if (node == stack.connection.rootNode) { + stack.connection.util.sendChatMessage("/$string") + } else { + stack.connection.util.sendChatMessage(string) + } } node?.execute(CommandReader(string), stack) } - private fun getNode(peek: Int, stack: CommandStack): RootNode? { - return if (peek == '.'.code && allowCLI) CLI.ROOT_NODE else if (peek == '/'.code) stack.connection.rootNode else null + private fun getNode(reader: CommandReader, stack: CommandStack): RootNode? { + val peek = reader.unsafePeek() + if (peek == '.'.code) { + reader.read() + if (allowCLI) { + return CLI.ROOT_NODE + } + return null + } + + if (peek == '/'.code) { + reader.read() + return stack.connection.rootNode + } + return null } override fun getSuggestions(reader: CommandReader, stack: CommandStack): List { + if (reader.string.isEmpty()) { + return emptyList() + } reader.skipWhitespaces() - val peek = reader.unsafePeek() + val node = getNode(reader, stack) + if (!reader.canPeek()) { + return emptyList() + } val string = parser.parse(reader) - return getNode(peek, stack)?.getSuggestions(CommandReader(string), stack) ?: emptyList() + return node?.getSuggestions(CommandReader(string), stack) ?: emptyList() } } diff --git a/src/main/java/de/bixilon/minosoft/commands/nodes/CommandNode.kt b/src/main/java/de/bixilon/minosoft/commands/nodes/CommandNode.kt index 584685982..7c4f774bb 100644 --- a/src/main/java/de/bixilon/minosoft/commands/nodes/CommandNode.kt +++ b/src/main/java/de/bixilon/minosoft/commands/nodes/CommandNode.kt @@ -61,9 +61,13 @@ abstract class CommandNode( open fun getSuggestions(reader: CommandReader, stack: CommandStack): List { + if (reader.string.isEmpty()) { + return emptyList() + } val pointer = reader.pointer val stackSize = stack.size val suggestions: MutableList = mutableListOf() + var error: Throwable? = null for (child in children) { try { val childSuggestions = child.getSuggestions(reader, stack) @@ -75,12 +79,18 @@ abstract class CommandNode( continue } return childSuggestions - } catch (ignored: Throwable) { + } catch (exception: Throwable) { + if (stack.size >= stackSize || error == null) { + error = exception + } } reader.pointer = pointer stack.reset(stackSize) } + if (suggestions.isEmpty() && error != null) { + throw error + } return suggestions } diff --git a/src/main/java/de/bixilon/minosoft/commands/nodes/LiteralNode.kt b/src/main/java/de/bixilon/minosoft/commands/nodes/LiteralNode.kt index bba8a6df5..db4979e58 100644 --- a/src/main/java/de/bixilon/minosoft/commands/nodes/LiteralNode.kt +++ b/src/main/java/de/bixilon/minosoft/commands/nodes/LiteralNode.kt @@ -65,6 +65,6 @@ class LiteralNode : ExecutableNode { stack.push(name, name) return super.getSuggestions(reader, stack) } - return suggester.suggest(literalName) ?: emptyList() + return suggester.suggest(literalName) ?: throw InvalidLiteralArgumentError(reader, literalName ?: "") } } diff --git a/src/main/java/de/bixilon/minosoft/commands/stack/CommandStack.kt b/src/main/java/de/bixilon/minosoft/commands/stack/CommandStack.kt index 3f9c33770..5b336a68f 100644 --- a/src/main/java/de/bixilon/minosoft/commands/stack/CommandStack.kt +++ b/src/main/java/de/bixilon/minosoft/commands/stack/CommandStack.kt @@ -14,16 +14,27 @@ package de.bixilon.minosoft.commands.stack import de.bixilon.kutil.cast.CastUtil.nullCast +import de.bixilon.minosoft.commands.stack.print.PrintTarget +import de.bixilon.minosoft.commands.stack.print.SystemPrintTarget import de.bixilon.minosoft.data.entities.entities.Entity import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection -class CommandStack { +class CommandStack( + connection: PlayConnection? = null, + val print: PrintTarget = SystemPrintTarget, +) { private val stack: MutableList = mutableListOf() val size: Int get() = stack.size var executor: Entity? = null lateinit var connection: PlayConnection + init { + if (connection != null) { + this.connection = connection + } + } + inline operator fun get(name: String): T? { return getAny(name).nullCast() } diff --git a/src/main/java/de/bixilon/minosoft/commands/stack/print/PlayerPrintTarget.kt b/src/main/java/de/bixilon/minosoft/commands/stack/print/PlayerPrintTarget.kt new file mode 100644 index 000000000..18ddf336c --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/commands/stack/print/PlayerPrintTarget.kt @@ -0,0 +1,23 @@ +/* + * Minosoft + * Copyright (C) 2020-2022 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.commands.stack.print + +import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection + +class PlayerPrintTarget(val connection: PlayConnection) : PrintTarget { + + override fun print(message: Any) { + connection.util.sendDebugMessage(message) + } +} diff --git a/src/main/java/de/bixilon/minosoft/commands/stack/print/PrintTarget.kt b/src/main/java/de/bixilon/minosoft/commands/stack/print/PrintTarget.kt new file mode 100644 index 000000000..8b2206a1c --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/commands/stack/print/PrintTarget.kt @@ -0,0 +1,19 @@ +/* + * Minosoft + * Copyright (C) 2020-2022 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.commands.stack.print + +interface PrintTarget { + + fun print(message: Any) +} diff --git a/src/main/java/de/bixilon/minosoft/commands/stack/print/SystemPrintTarget.kt b/src/main/java/de/bixilon/minosoft/commands/stack/print/SystemPrintTarget.kt new file mode 100644 index 000000000..2fe8c4f4c --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/commands/stack/print/SystemPrintTarget.kt @@ -0,0 +1,25 @@ +/* + * Minosoft + * Copyright (C) 2020-2022 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.commands.stack.print + +import de.bixilon.minosoft.util.logging.Log +import de.bixilon.minosoft.util.logging.LogLevels +import de.bixilon.minosoft.util.logging.LogMessageType + +object SystemPrintTarget : PrintTarget { + + override fun print(message: Any) { + Log.log(LogMessageType.OTHER, LogLevels.INFO) { message } + } +} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/gui/elements/input/NodeTextInputElement.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/gui/elements/input/NodeTextInputElement.kt new file mode 100644 index 000000000..3de1a2b6f --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/gui/elements/input/NodeTextInputElement.kt @@ -0,0 +1,67 @@ +/* + * Minosoft + * Copyright (C) 2020-2022 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.gui.rendering.gui.gui.elements.input + +import de.bixilon.minosoft.commands.nodes.CommandNode +import de.bixilon.minosoft.commands.stack.CommandStack +import de.bixilon.minosoft.commands.stack.print.PlayerPrintTarget +import de.bixilon.minosoft.commands.util.CommandReader +import de.bixilon.minosoft.gui.rendering.gui.GUIRenderer +import de.bixilon.minosoft.gui.rendering.gui.elements.Element +import de.bixilon.minosoft.gui.rendering.gui.elements.text.mark.TextCursorStyles + +class NodeTextInputElement( + guiRenderer: GUIRenderer, + var node: CommandNode, + value: String = "", + maxLength: Int = Int.MAX_VALUE, + cursorStyles: TextCursorStyles = TextCursorStyles.CLICKED, + editable: Boolean = true, + onChange: () -> Unit = {}, + background: Boolean = true, + shadow: Boolean = true, + scale: Float = 1.0f, + cutAtSize: Boolean = false, + parent: Element? = null, +) : TextInputElement(guiRenderer, value, maxLength, cursorStyles, editable, onChange, background, shadow, scale, cutAtSize, parent) { + + + private fun createStack(): CommandStack { + return CommandStack( + connection = guiRenderer.connection, + print = PlayerPrintTarget(guiRenderer.connection) + ) + } + + override fun onChange() { + val value = value + if (value.isBlank()) { + return super.onChange() + } + try { + node.getSuggestions(CommandReader(value), createStack()) + } catch (exception: Throwable) { + exception.printStackTrace() + } + super.onChange() + } + + fun submit() { + try { + node.execute(CommandReader(value), createStack()) + } catch (exception: Throwable) { + exception.printStackTrace() + } + } +} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/gui/elements/input/TextInputElement.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/gui/elements/input/TextInputElement.kt index 906373eb0..9cb0bd1c0 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/gui/elements/input/TextInputElement.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/gui/elements/input/TextInputElement.kt @@ -34,13 +34,13 @@ import de.bixilon.minosoft.gui.rendering.system.window.KeyChangeTypes import de.bixilon.minosoft.gui.rendering.util.vec.vec2.Vec2iUtil.EMPTY import de.bixilon.minosoft.util.KUtil.codePointAtOrNull -class TextInputElement( +open class TextInputElement( guiRenderer: GUIRenderer, value: String = "", val maxLength: Int = Int.MAX_VALUE, val cursorStyles: TextCursorStyles = TextCursorStyles.CLICKED, var editable: Boolean = true, - var onChange: () -> Unit = {}, + var onChangeCallback: () -> Unit = {}, val background: Boolean = true, shadow: Boolean = true, scale: Float = 1.0f, @@ -99,9 +99,12 @@ class TextInputElement( } private fun _set(value: String) { - _value.replace(0, _value.length, value) + val previous = _value.toString() + val next = _value.replace(0, _value.length, value) _pointer = value.length - onChange() + if (previous != next.toString()) { + onChange() + } textUpToDate = false } @@ -375,6 +378,10 @@ class TextInputElement( cursorTick = 19 // make cursor visible } + open fun onChange() { + onChangeCallback() + } + companion object { private const val CURSOR_TICK_ON_ACTION = 10 private val WORD_SEPARATORS = charArrayOf(' ', ',', ';', '-', '\'', '`', '"', '“', '„', '.', '&', '@', '^', '/', '\\', '…', '*', '⁂', '=', '?', '!', '‽', '¡', '¿', '⸮', '#', '№', '%', '‰', '‱', '°', '⌀', '+', '−', '×', '÷', '~', '±', '∓', '–', '⁀', '|', '¦', '‖', '•', '·', '©', '©', '℗', '®', '‘', '’', '“', '”', '"', '"', '‹', '›', '«', '»', '(', ')', '[', ']', '{', '}', '⟨', '⟩', '”', '〃', '†', '‡', '❧', '☞', '◊', '¶', '⸿', '፠', '๛', '※', '§') diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/hud/elements/chat/ChatElement.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/hud/elements/chat/ChatElement.kt index 5b6184633..c4ce283ba 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/hud/elements/chat/ChatElement.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/hud/elements/chat/ChatElement.kt @@ -15,6 +15,7 @@ package de.bixilon.minosoft.gui.rendering.gui.hud.elements.chat import de.bixilon.kotlinglm.vec2.Vec2i import de.bixilon.kutil.concurrent.pool.DefaultThreadPool +import de.bixilon.minosoft.commands.nodes.ChatNode import de.bixilon.minosoft.config.key.KeyActions import de.bixilon.minosoft.config.key.KeyBinding import de.bixilon.minosoft.config.key.KeyCodes @@ -27,7 +28,7 @@ import de.bixilon.minosoft.gui.rendering.gui.elements.Element import de.bixilon.minosoft.gui.rendering.gui.elements.LayoutedElement import de.bixilon.minosoft.gui.rendering.gui.gui.GUIBuilder import de.bixilon.minosoft.gui.rendering.gui.gui.LayoutedGUIElement -import de.bixilon.minosoft.gui.rendering.gui.gui.elements.input.TextInputElement +import de.bixilon.minosoft.gui.rendering.gui.gui.elements.input.NodeTextInputElement import de.bixilon.minosoft.gui.rendering.gui.hud.elements.HUDBuilder import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexConsumer import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexOptions @@ -39,7 +40,7 @@ import de.bixilon.minosoft.util.KUtil.toResourceLocation class ChatElement(guiRenderer: GUIRenderer) : AbstractChatElement(guiRenderer), LayoutedElement { private val chatProfile = profile.chat - private val input = TextInputElement(guiRenderer, maxLength = connection.version.maxChatMessageSize).apply { parent = this@ChatElement } + private val input = NodeTextInputElement(guiRenderer, ChatNode("", allowCLI = true), maxLength = connection.version.maxChatMessageSize).apply { parent = this@ChatElement } private val internal = InternalChatElement(guiRenderer).apply { parent = this@ChatElement } private val history: MutableList = mutableListOf() private var historyIndex = -1 @@ -69,7 +70,7 @@ class ChatElement(guiRenderer: GUIRenderer) : AbstractChatElement(guiRenderer), chatProfile::width.profileWatchRendering(this, profile = profile) { messages.prefMaxSize = Vec2i(it, messages.prefMaxSize.y) } chatProfile::height.profileWatchRendering(this, profile = profile) { messages.prefMaxSize = Vec2i(messages.prefMaxSize.x, it) } forceSilentApply() - input.onChange = { + input.onChangeCallback = { while (input._value.startsWith(' ')) { input._value.deleteCharAt(0) input._pointer-- @@ -162,9 +163,7 @@ class ChatElement(guiRenderer: GUIRenderer) : AbstractChatElement(guiRenderer), private fun submit() { val value = input.value - if (value.isNotBlank()) { - connection.util.sendChatMessage(value) - } + input.submit() input.value = "" if (history.lastOrNull() != value) { // ToDo: Improve history diff --git a/src/main/java/de/bixilon/minosoft/terminal/commands/HelpCommand.kt b/src/main/java/de/bixilon/minosoft/terminal/commands/HelpCommand.kt index 0bcfbf648..6cf88051e 100644 --- a/src/main/java/de/bixilon/minosoft/terminal/commands/HelpCommand.kt +++ b/src/main/java/de/bixilon/minosoft/terminal/commands/HelpCommand.kt @@ -18,18 +18,19 @@ import de.bixilon.kutil.enums.ValuesEnum import de.bixilon.minosoft.commands.nodes.ArgumentNode import de.bixilon.minosoft.commands.nodes.LiteralNode import de.bixilon.minosoft.commands.parser.minosoft.enums.EnumParser +import de.bixilon.minosoft.commands.stack.print.PrintTarget object HelpCommand : Command { - override var node: LiteralNode = LiteralNode("help", setOf("?"), executor = { printHelp() }) - .addChild(ArgumentNode("subcommand", parser = EnumParser(HelpCommands), executor = { printHelp(it["subcommand"]!!) })) + override var node: LiteralNode = LiteralNode("help", setOf("?"), executor = { it.print.printHelp() }) + .addChild(ArgumentNode("subcommand", parser = EnumParser(HelpCommands), executor = { it.print.printHelp(it["subcommand"]!!) })) - fun printHelp() { - println("-------------- Minosoft help --------------") + fun PrintTarget.printHelp() { + print("-------------- Minosoft help --------------") } - fun printHelp(subcommand: HelpCommands) { - println("-------------- Minosoft help --------------") - println("Subcommand: $subcommand") + fun PrintTarget.printHelp(subcommand: HelpCommands) { + print("-------------- Minosoft help --------------") + print("Subcommand: $subcommand") } enum class HelpCommands {