mirror of
https://gitlab.bixilon.de/bixilon/minosoft.git
synced 2025-09-15 02:15:34 -04:00
hud: add support for dynamic text
This commit is contained in:
parent
ad9f8cade5
commit
b2178e2cfa
@ -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<String, *>) {
|
||||
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<RGBColor>()
|
||||
}
|
||||
} ?: 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<String, *>?)?.let { click -> ClickEvent(click) }
|
||||
val hoverEvent = (json["hoverEvent"] as Map<String, *>?)?.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<Any>()
|
||||
(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()
|
||||
|
@ -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<String, *>)
|
||||
}
|
||||
is JsonPrimitive -> raw.asString
|
||||
else -> raw.toString()
|
||||
}
|
||||
|
@ -35,6 +35,16 @@ class ClickEvent {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
constructor(json: Map<String, *>) {
|
||||
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,
|
||||
|
@ -45,6 +45,22 @@ class HoverEvent {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
constructor(json: Map<String, *>) {
|
||||
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,
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<ResourceLocation, HUDElement>
|
||||
private val enabledHUDElements: MutableMap<ResourceLocation, HUDElement> = mutableMapOf()
|
||||
private val enabledHUDElements = mutableMapOf<ResourceLocation, HUDElement>()
|
||||
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<ScreenResizeEvent> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -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() {}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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<String, Any>? = 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<Int>()
|
||||
addChatComponent(chatComponent, newLinePositions)
|
||||
newLinePositions += elements.size
|
||||
@ -72,7 +76,6 @@ class HUDTextElement : HUDElement {
|
||||
}
|
||||
|
||||
private fun addChars(chars: CharArray, color: RGBColor?, newLinePositions: MutableList<Int>): MutableList<Int> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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<ResourceLocation, (PlayConnection) -> String>()
|
||||
|
||||
init {
|
||||
insertRunnables[ResourceLocation("minosoft:playerPosition_x")] = { connection ->
|
||||
connection.player.position.x.round(2).toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user