From b2178e2cfa55c74c0a9fe35ca782f127a5aa1d04 Mon Sep 17 00:00:00 2001 From: Lukas Date: Sun, 4 Jul 2021 18:14:00 +0200 Subject: [PATCH] hud: add support for dynamic text --- .../minosoft/data/text/BaseComponent.kt | 66 +++++++++++++++++++ .../minosoft/data/text/ChatComponent.kt | 13 +++- .../minosoft/data/text/events/ClickEvent.kt | 10 +++ .../minosoft/data/text/events/HoverEvent.kt | 16 +++++ .../data/text/events/data/EntityHoverData.kt | 5 ++ .../minosoft/gui/rendering/hud/HUDRenderer.kt | 34 ++++++---- .../hud/elements/primitive/HUDElement.kt | 12 +++- .../hud/elements/primitive/HUDImageElement.kt | 7 +- .../hud/elements/text/HUDTextElement.kt | 35 ++++++++-- .../hud/elements/text/HUDTextTranslator.kt | 47 +++++++++++++ .../java/de/bixilon/minosoft/util/MMath.kt | 12 ++++ 11 files changed, 231 insertions(+), 26 deletions(-) create mode 100644 src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/text/HUDTextTranslator.kt diff --git a/src/main/java/de/bixilon/minosoft/data/text/BaseComponent.kt b/src/main/java/de/bixilon/minosoft/data/text/BaseComponent.kt index 57f35be0a..b6e2cd828 100644 --- a/src/main/java/de/bixilon/minosoft/data/text/BaseComponent.kt +++ b/src/main/java/de/bixilon/minosoft/data/text/BaseComponent.kt @@ -19,6 +19,7 @@ import de.bixilon.minosoft.data.locale.minecraft.Translator import de.bixilon.minosoft.data.text.RGBColor.Companion.asColor import de.bixilon.minosoft.data.text.events.ClickEvent import de.bixilon.minosoft.data.text.events.HoverEvent +import de.bixilon.minosoft.gui.rendering.hud.elements.text.HUDTextTranslator import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition import de.bixilon.minosoft.util.KUtil.nullCast import javafx.collections.ObservableList @@ -141,6 +142,71 @@ class BaseComponent : ChatComponent { } } + constructor(translator: Translator?, hudTextTranslator: HUDTextTranslator, parent: TextComponent?, json: Map) { + val currentParent: TextComponent? + var currentText = "" + (json["text"] as String?)?.let { + if (it.indexOf(ProtocolDefinition.TEXT_COMPONENT_SPECIAL_PREFIX_CHAR) != -1) { + parts.add(ChatComponent.of(it, translator, parent, hudTextTranslator = hudTextTranslator)) + return + } + currentText = it + } + + val color = (json["color"] as String?)?.let { colorName -> + if (colorName.startsWith("#")) { + colorName.asColor() + } else { + ChatCode.FORMATTING_CODES[colorName]?.nullCast() + } + } ?: parent?.color + + val formatting = parent?.formatting?.toMutableSet() ?: mutableSetOf() + + formatting.addOrRemove(PreChatFormattingCodes.BOLD, (json["bold"] as Boolean?)) + formatting.addOrRemove(PreChatFormattingCodes.ITALIC, (json["italic"] as Boolean?)) + formatting.addOrRemove(PreChatFormattingCodes.UNDERLINED, (json["underlined"] as Boolean?)) + formatting.addOrRemove(PreChatFormattingCodes.STRIKETHROUGH, (json["strikethrough"] as Boolean?)) + formatting.addOrRemove(PreChatFormattingCodes.OBFUSCATED, (json["obfuscated"] as Boolean?)) + + val clickEvent = (json["clickEvent"] as Map?)?.let { click -> ClickEvent(click) } + val hoverEvent = (json["hoverEvent"] as Map?)?.let { click -> HoverEvent(click) } + + val textComponent = MultiChatComponent( + message = currentText, + color = color, + formatting = formatting, + clickEvent = clickEvent, + hoverEvent = hoverEvent, + ) + if (currentText.isNotEmpty()) { + parts.add(textComponent) + } + currentParent = textComponent + + + (json["extra"] as Array<*>?)?.let { + for (data in it) { + parts.add(ChatComponent.of(data, translator, currentParent, hudTextTranslator = hudTextTranslator)) + } + } + + + (json["translate"] as String?)?.let { + val with = mutableListOf() + (json["with"] as List<*>?)?.let { withArray -> + for (part in withArray) { + with.add(part!!) + } + } + parts.add(translator?.translate(it, currentParent, *with.toTypedArray()) ?: ChatComponent.of(json["with"], translator, currentParent, hudTextTranslator = hudTextTranslator)) + } + + (json["data"] as String?)?.let { + parts.add(hudTextTranslator.translate(it, currentParent)) + } + } + override val ansiColoredMessage: String get() { val stringBuilder = StringBuilder() diff --git a/src/main/java/de/bixilon/minosoft/data/text/ChatComponent.kt b/src/main/java/de/bixilon/minosoft/data/text/ChatComponent.kt index dd7b84f0d..b0f6b2c95 100644 --- a/src/main/java/de/bixilon/minosoft/data/text/ChatComponent.kt +++ b/src/main/java/de/bixilon/minosoft/data/text/ChatComponent.kt @@ -17,6 +17,7 @@ import com.google.gson.JsonObject import com.google.gson.JsonParser import com.google.gson.JsonPrimitive import de.bixilon.minosoft.data.locale.minecraft.Translator +import de.bixilon.minosoft.gui.rendering.hud.elements.text.HUDTextTranslator import javafx.collections.FXCollections import javafx.collections.ObservableList import javafx.scene.Node @@ -57,7 +58,7 @@ interface ChatComponent { companion object { @JvmOverloads - fun of(raw: Any?, translator: Translator? = null, parent: TextComponent? = null, ignoreJson: Boolean = false): ChatComponent { + fun of(raw: Any?, translator: Translator? = null, parent: TextComponent? = null, ignoreJson: Boolean = false, hudTextTranslator: HUDTextTranslator? = null): ChatComponent { if (raw == null) { return BaseComponent() } @@ -75,6 +76,16 @@ interface ChatComponent { } return component } + is List<*> -> { + val component = BaseComponent() + for (part in raw) { + component.parts.add(of(part, translator, parent, hudTextTranslator = hudTextTranslator)) + } + return component + } + is Map<*, *> -> { + return BaseComponent(translator, hudTextTranslator!!, parent, raw as Map) + } is JsonPrimitive -> raw.asString else -> raw.toString() } diff --git a/src/main/java/de/bixilon/minosoft/data/text/events/ClickEvent.kt b/src/main/java/de/bixilon/minosoft/data/text/events/ClickEvent.kt index 2c2d9ea73..07e961bc0 100644 --- a/src/main/java/de/bixilon/minosoft/data/text/events/ClickEvent.kt +++ b/src/main/java/de/bixilon/minosoft/data/text/events/ClickEvent.kt @@ -35,6 +35,16 @@ class ClickEvent { this.value = value } + constructor(json: Map) { + action = ClickEventActions[(json["action"] as String).lowercase()] + val primitive = json["value"] + value = if (primitive is Number) { + primitive + } else { + primitive.toString() + } + } + enum class ClickEventActions { OPEN_URL, RUN_COMMAND, diff --git a/src/main/java/de/bixilon/minosoft/data/text/events/HoverEvent.kt b/src/main/java/de/bixilon/minosoft/data/text/events/HoverEvent.kt index 416da5e64..734d2a4f6 100644 --- a/src/main/java/de/bixilon/minosoft/data/text/events/HoverEvent.kt +++ b/src/main/java/de/bixilon/minosoft/data/text/events/HoverEvent.kt @@ -45,6 +45,22 @@ class HoverEvent { this.value = value } + constructor(json: Map) { + action = HoverEventActions.valueOf((json["action"] as String).uppercase(Locale.getDefault())) + var data: Any = json + json["value"]?.let { + data = it + } + json["contents"]?.let { + data = it + } + this.value = when (action) { + HoverEventActions.SHOW_TEXT -> ChatComponent.of(data) + HoverEventActions.SHOW_ENTITY -> EntityHoverData.deserialize(data) + else -> TODO("Don't know what todo with $action: $json") + } + } + enum class HoverEventActions { SHOW_TEXT, SHOW_ITEM, diff --git a/src/main/java/de/bixilon/minosoft/data/text/events/data/EntityHoverData.kt b/src/main/java/de/bixilon/minosoft/data/text/events/data/EntityHoverData.kt index 3a127048a..13d01594f 100644 --- a/src/main/java/de/bixilon/minosoft/data/text/events/data/EntityHoverData.kt +++ b/src/main/java/de/bixilon/minosoft/data/text/events/data/EntityHoverData.kt @@ -46,5 +46,10 @@ class EntityHoverData( return EntityHoverData(json["id"].asString.asUUID(), type, ChatComponent.of(json["name"])) } + + fun deserialize(data: Any): EntityHoverData { + val json = JsonParser.parseString(data as String).asJsonObject + return deserialize(json) + } } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/hud/HUDRenderer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/hud/HUDRenderer.kt index f521de242..319bbc7d1 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/hud/HUDRenderer.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/hud/HUDRenderer.kt @@ -21,19 +21,22 @@ import de.bixilon.minosoft.gui.rendering.Renderer import de.bixilon.minosoft.gui.rendering.RendererBuilder import de.bixilon.minosoft.gui.rendering.hud.atlas.HUDAtlasElement import de.bixilon.minosoft.gui.rendering.hud.elements.primitive.HUDElement +import de.bixilon.minosoft.gui.rendering.hud.elements.text.HUDTextTranslator import de.bixilon.minosoft.gui.rendering.modding.events.ScreenResizeEvent import de.bixilon.minosoft.gui.rendering.shader.Shader import de.bixilon.minosoft.modding.event.CallbackEventInvoker import de.bixilon.minosoft.protocol.network.connection.PlayConnection import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition import de.bixilon.minosoft.util.json.ResourceLocationJsonMap.toResourceLocationMap +import de.bixilon.minosoft.util.task.time.TimeWorker +import de.bixilon.minosoft.util.task.time.TimeWorkerTask import glm_.glm import glm_.mat4x4.Mat4 class HUDRenderer(val connection: PlayConnection, val renderWindow: RenderWindow) : Renderer { - private var prepareNext: Boolean = true + private lateinit var updateTask: TimeWorkerTask private lateinit var hudElements: MutableMap - private val enabledHUDElements: MutableMap = mutableMapOf() + private val enabledHUDElements = mutableMapOf() private val hudShader = Shader( renderWindow = renderWindow, resourceLocation = ResourceLocation(ProtocolDefinition.MINOSOFT_NAMESPACE, "hud"), @@ -42,13 +45,17 @@ class HUDRenderer(val connection: PlayConnection, val renderWindow: RenderWindow var orthographicMatrix: Mat4 = Mat4() private set + val hudTextTranslator = HUDTextTranslator(connection) + var hudEnabled = true var currentMenu: HUDMenus = HUDMenus.IN_GAME set(value) { if (field == value) { return } - prepareNext = true + for (element in hudElements.values) { + element.prepareNext = true + } field = value } @@ -63,7 +70,15 @@ class HUDRenderer(val connection: PlayConnection, val renderWindow: RenderWindow connection.registerEvent(CallbackEventInvoker.of { orthographicMatrix = glm.ortho(0f, renderWindow.screenDimensions.x.toFloat(), renderWindow.screenDimensions.y.toFloat(), 0f) - prepareNext = true + for (element in hudElements.values) { + element.prepareNext = true + } + }) + + updateTask = TimeWorker.addTask(TimeWorkerTask(ProtocolDefinition.TICK_TIME*2) { + for (hudElement in hudElements.values) { + hudElement.update() + } }) } @@ -89,17 +104,12 @@ class HUDRenderer(val connection: PlayConnection, val renderWindow: RenderWindow override fun draw() { if (RenderConstants.RENDER_HUD && hudEnabled) { - if (prepareNext) { - for (hudElement in hudElements.values) { - if (hudElement.isEnabled) { - hudElement.prepare(orthographicMatrix) - } - } - prepareNext = false - } hudShader.use() for (hudElement in enabledHUDElements.values) { if (hudElement.isEnabled) { + if (hudElement.prepareNext) { + hudElement.prepare() + } hudElement.draw() } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/primitive/HUDElement.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/primitive/HUDElement.kt index 8960cafcc..16aeac722 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/primitive/HUDElement.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/primitive/HUDElement.kt @@ -18,7 +18,6 @@ import de.bixilon.minosoft.gui.rendering.hud.HUDRenderer import de.bixilon.minosoft.gui.rendering.hud.elements.position.HUDElementPositionAnchors import de.bixilon.minosoft.gui.rendering.hud.elements.position.HUDElementVec2 import de.bixilon.minosoft.gui.rendering.util.VecUtil.toVec2 -import glm_.mat4x4.Mat4 import glm_.vec2.Vec2i abstract class HUDElement( @@ -30,8 +29,11 @@ abstract class HUDElement( val isEnabled: Boolean get() = activeOnMenu?.let { hudRenderer.currentMenu == it } ?: true + lateinit var hudRenderer: HUDRenderer + var prepareNext = true + open fun init(hudRenderer: HUDRenderer) { this.hudRenderer = hudRenderer position.init() @@ -41,7 +43,9 @@ abstract class HUDElement( open fun draw() {} - open fun prepare(matrix: Mat4) {} + open fun prepare() { + prepareNext = false + } fun getPositionAtAnchor(anchor: HUDElementPositionAnchors): Vec2i { val realSize = size.getRealVector(hudRenderer).toVec2 @@ -49,6 +53,8 @@ abstract class HUDElement( return realPosition - realSize * position.anchor.positionTransform + realSize * anchor.positionTransform } + open fun checkToPrepare() {} + override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is HUDElement) return false @@ -68,4 +74,6 @@ abstract class HUDElement( result = 31 * result + hudRenderer.hashCode() return result } + + open fun update() {} } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/primitive/HUDImageElement.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/primitive/HUDImageElement.kt index 20220a989..8d7816f90 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/primitive/HUDImageElement.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/primitive/HUDImageElement.kt @@ -11,7 +11,6 @@ import de.bixilon.minosoft.gui.rendering.hud.elements.position.HUDElementVec2 import de.bixilon.minosoft.gui.rendering.util.mesh.Mesh import de.bixilon.minosoft.gui.rendering.util.mesh.SimpleTextureMesh import de.bixilon.minosoft.util.json.RGBColorSerializer -import glm_.mat4x4.Mat4 import glm_.vec2.Vec2 import glm_.vec3.Vec3 import glm_.vec4.Vec4 @@ -52,8 +51,8 @@ class HUDImageElement: HUDElement { mesh.draw() } - override fun prepare(matrix: Mat4) { - super.prepare(matrix) + override fun prepare() { + super.prepare() if (mesh.state == Mesh.MeshStates.LOADED) { mesh.unload() mesh = SimpleTextureMesh() @@ -69,7 +68,7 @@ class HUDImageElement: HUDElement { ), texture.texture, textureUV, tint) } for (position in DRAW_ORDER) { - addVertex((matrix * Vec4(positions[position], 1f, 1f)).xy, uvs[position]) + addVertex((hudRenderer.orthographicMatrix * Vec4(positions[position], 1f, 1f)).xy, uvs[position]) } mesh.load() } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/text/HUDTextElement.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/text/HUDTextElement.kt index 61c0f2c39..0d074a2d4 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/text/HUDTextElement.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/text/HUDTextElement.kt @@ -31,7 +31,6 @@ import de.bixilon.minosoft.gui.rendering.hud.elements.primitive.HUDSpacerElement import de.bixilon.minosoft.gui.rendering.util.VecUtil.toVec2 import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition import glm_.glm -import glm_.mat4x4.Mat4 import glm_.vec2.Vec2 class HUDTextElement : HUDElement { @@ -39,11 +38,15 @@ class HUDTextElement : HUDElement { private val alignment: HUDElementPositionAnchors - private var contents: String = "" + private lateinit var contents: List<*> + + private var currentText: ChatComponent? = null + + var lastElement: HUDElement? = null constructor(position: HUDElementPosition, size: HUDElementVec2, json: Map? = null, activeOnMenu: HUDMenus?) : super(position, size, (json?.get("z") as Double?)?.toInt() ?: 0, activeOnMenu) { alignment = json?.get("alignment")?.let { HUDElementPositionAnchors.of(it as String) } ?: HUDElementPositionAnchors.TOP_LEFT - (json?.get("static_text") as String?)?.let { contents = it } + contents = json?.get("content") as List<*> } constructor(position: HUDElementPosition, size: HUDElementVec2, z: Int) : super(position, size, z) { @@ -52,6 +55,7 @@ class HUDTextElement : HUDElement { private fun renderChatComponent(chatComponent: ChatComponent) { elements.clear() + lastElement = null val newLinePositions = mutableListOf() addChatComponent(chatComponent, newLinePositions) newLinePositions += elements.size @@ -72,7 +76,6 @@ class HUDTextElement : HUDElement { } private fun addChars(chars: CharArray, color: RGBColor?, newLinePositions: MutableList): MutableList { - var lastElement: HUDElement? = null val maxRight = getPositionAtAnchor(HUDElementPositionAnchors.BOTTOM_RIGHT).x val bottomLeftPosition = getPositionAtAnchor(HUDElementPositionAnchors.BOTTOM_LEFT).toVec2 for (char in chars) { @@ -145,10 +148,28 @@ class HUDTextElement : HUDElement { } } - override fun prepare(matrix: Mat4) { - renderChatComponent(BaseComponent(null, contents)) + override fun prepare() { + if (currentText == null) { + return + } + renderChatComponent(currentText!!) for (element in elements) { - element.prepare(matrix) + element.prepare() + } + } + + fun getTextComponent(): ChatComponent { + return ChatComponent.of(contents, translator = hudRenderer.connection.version.localeManager.language, hudTextTranslator = hudRenderer.hudTextTranslator) + } + + override fun update() { + if (!isEnabled) { + return + } + val newText = getTextComponent() + if (currentText != newText) { + currentText = newText + prepareNext = true } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/text/HUDTextTranslator.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/text/HUDTextTranslator.kt new file mode 100644 index 000000000..7421076ee --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/text/HUDTextTranslator.kt @@ -0,0 +1,47 @@ +/* + * Minosoft + * + * Copyright (C) 2021 Lukas Eisenhauer + * + * 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.hud.elements.text + +import de.bixilon.minosoft.data.locale.minecraft.Translator +import de.bixilon.minosoft.data.registries.ResourceLocation +import de.bixilon.minosoft.data.text.ChatComponent +import de.bixilon.minosoft.data.text.TextComponent +import de.bixilon.minosoft.protocol.network.connection.PlayConnection +import de.bixilon.minosoft.util.MMath.round + + +class HUDTextTranslator(val connection: PlayConnection) : Translator { + override fun translate(key: String?, parent: TextComponent?, vararg data: Any?): ChatComponent { + if (key == null) { + return ChatComponent.of(key) + } + val resourceLocation = ResourceLocation(key) + if (resourceLocation !in insertRunnables) { + return ChatComponent.of(key) + } + val runnable = insertRunnables[resourceLocation]!! // checked before + return ChatComponent.of(runnable.invoke(connection)) + } + + companion object { + private val insertRunnables = mutableMapOf String>() + + init { + insertRunnables[ResourceLocation("minosoft:playerPosition_x")] = { connection -> + connection.player.position.x.round(2).toString() + } + } + } +} diff --git a/src/main/java/de/bixilon/minosoft/util/MMath.kt b/src/main/java/de/bixilon/minosoft/util/MMath.kt index 2cdb64c8a..2dc3e362c 100644 --- a/src/main/java/de/bixilon/minosoft/util/MMath.kt +++ b/src/main/java/de/bixilon/minosoft/util/MMath.kt @@ -13,6 +13,8 @@ package de.bixilon.minosoft.util +import glm_.func.common.floor +import glm_.glm import glm_.vec2.Vec2i import kotlin.math.floor @@ -99,4 +101,14 @@ object MMath { } else { -1 } + + fun Float.round(digits: Int): Float { + val multiplicationFactor = glm.pow(10, digits) + return (this * multiplicationFactor).floor / multiplicationFactor + } + + fun Double.round(digits: Int): Double { + val multiplicationFactor = glm.pow(10, digits) + return (this * multiplicationFactor).floor / multiplicationFactor + } }