hud: wip hotbar

This commit is contained in:
Bixilon 2021-09-20 00:22:22 +02:00
parent 758142d257
commit 27e36e4971
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
12 changed files with 423 additions and 10 deletions

View File

@ -563,6 +563,9 @@ class LocalPlayerEntity(
fovMultiplier.value = 1.0 + MMath.clamp(walkingSpeed * 1.9, -2.0, 2.0)
}
override val health: Double
get() = healthCondition.hp.toDouble()
companion object {
private val CLIMBABLE_TAG = "minecraft:climbable".toResourceLocation()
private val SOUL_SPEED_BLOCKS = "minecraft:soul_speed_blocks".toResourceLocation()

View File

@ -25,4 +25,6 @@ object DefaultStatusEffects {
val DOLPHINS_GRACE = "minecraft:dolphins_grace".toResourceLocation()
val NIGHT_VISION = "minecraft:night_vision".toResourceLocation()
val CONDUIT_POWER = "minecraft:conduit_power".toResourceLocation()
val WITHER = "minecraft:wither".toResourceLocation()
val POISON = "minecraft:poison".toResourceLocation()
}

View File

@ -168,7 +168,7 @@ class RenderWindow(
textureManager.staticTextures.createTexture(RenderConstants.DEBUG_TEXTURE_RESOURCE_LOCATION)
WHITE_TEXTURE = TextureLikeTexture(
texture = textureManager.staticTextures.createTexture(ResourceLocation("minosoft:textures/white.png")),
uvStart = Vec2(0, 0),
uvStart = Vec2(0.0f, 0.0f),
uvEnd = Vec2(0.001f, 0.001f),
size = Vec2i(16, 16)
)

View File

@ -71,6 +71,8 @@ open class ImageElement(
}
private var matrix: Mat4 = hudRenderer.matrix
private var lastOffset: Vec2i = Vec2i(-1, -1)
init {
this.size = size
@ -82,13 +84,14 @@ open class ImageElement(
override fun render(offset: Vec2i, z: Int, consumer: GUIVertexConsumer): Int {
val matrix = hudRenderer.matrix
if (matrix == this.matrix && cacheUpToDate) {
if (offset == lastOffset && matrix == this.matrix && cacheUpToDate) {
consumer.addCache(cache)
return 1
}
val cache = GUIMeshCache(matrix, GUIMesh.GUIMeshStruct.FLOATS_PER_VERTEX * 6)
cache.addQuad(offset, offset + size, z, texture, uvStart, uvEnd, tint)
this.lastOffset = offset
this.matrix = matrix
this.cache = cache
this.cacheUpToDate = true

View File

@ -23,6 +23,7 @@ import de.bixilon.minosoft.gui.rendering.Renderer
import de.bixilon.minosoft.gui.rendering.RendererBuilder
import de.bixilon.minosoft.gui.rendering.gui.hud.atlas.HUDAtlasManager
import de.bixilon.minosoft.gui.rendering.gui.hud.elements.*
import de.bixilon.minosoft.gui.rendering.gui.hud.elements.hotbar.HotbarHUDElement
import de.bixilon.minosoft.gui.rendering.gui.hud.elements.tab.TabListHUDElement
import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIMesh
import de.bixilon.minosoft.gui.rendering.modding.events.ResizeWindowEvent
@ -80,6 +81,7 @@ class HUDRenderer(
}
registerElement(TabListHUDElement)
registerElement(BreakProgressHUDElement)
registerElement(HotbarHUDElement)
}
override fun init() {

View File

@ -21,7 +21,7 @@ class HUDAtlasElement(
override val texture: AbstractTexture,
val start: Vec2i,
val end: Vec2i,
val slots: Map<Int, Vec2Binding<Int>>, // ToDo: Use an array?
val slots: Map<Int, Vec2iBinding>, // ToDo: Use an array?
) : TextureLike {
override val size: Vec2i = end - start
override lateinit var uvStart: Vec2

View File

@ -51,12 +51,12 @@ class HUDAtlasManager(private val hudRenderer: HUDRenderer) {
val texture = hudRenderer.renderWindow.textureManager.staticTextures.createTexture(versionData["texture"].toResourceLocation())
val start = versionData["start"].toVec2i()
val end = versionData["end"].toVec2i()
val slots: MutableMap<Int, Vec2Binding<Int>> = mutableMapOf()
val slots: MutableMap<Int, Vec2iBinding> = mutableMapOf()
versionData["slots"]?.mapCast()?.let {
for ((slotId, slotData) in it) {
val slot = slotData.mapCast()!!
slots[slotId.toInt()] = Vec2Binding(
slots[slotId.toInt()] = Vec2iBinding(
start = slot["start"].toVec2i(),
end = slot["end"].toVec2i(),
)
@ -87,6 +87,10 @@ class HUDAtlasManager(private val hudRenderer: HUDRenderer) {
return elements[resourceLocation]
}
operator fun get(resourceLocation: String): HUDAtlasElement? {
return elements[resourceLocation.toResourceLocation()]
}
companion object {
private val ATLAS_DATA = "minosoft:mapping/atlas.json".toResourceLocation()
}

View File

@ -13,9 +13,11 @@
package de.bixilon.minosoft.gui.rendering.gui.hud.atlas
import glm_.vec2.Vec2t
import glm_.vec2.Vec2i
data class Vec2Binding<T : Number>(
val start: Vec2t<T>,
val end: Vec2t<T>,
)
data class Vec2iBinding(
val start: Vec2i,
val end: Vec2i,
) {
val size = end - start
}

View File

@ -0,0 +1,52 @@
/*
* Minosoft
* Copyright (C) 2021 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 <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.gui.rendering.gui.hud.elements.hotbar
import de.bixilon.minosoft.gui.rendering.gui.elements.Element
import de.bixilon.minosoft.gui.rendering.gui.elements.primitive.ImageElement
import de.bixilon.minosoft.gui.rendering.gui.hud.HUDRenderer
import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexConsumer
import de.bixilon.minosoft.util.KUtil.toResourceLocation
import glm_.vec2.Vec2i
class HotbarBaseElement(hudRenderer: HUDRenderer) : Element(hudRenderer) {
private val baseAtlasElement = hudRenderer.atlasManager[BASE]!!
private val base = ImageElement(hudRenderer, baseAtlasElement)
private val frame = ImageElement(hudRenderer, hudRenderer.atlasManager[FRAME]!!, size = Vec2i(FRAME_SIZE))
override fun render(offset: Vec2i, z: Int, consumer: GUIVertexConsumer): Int {
base.render(offset + HORIZONTAL_MARGIN, z, consumer)
baseAtlasElement.slots[hudRenderer.connection.player.selectedHotbarSlot]?.let {
frame.render(offset + it.start - HORIZONTAL_MARGIN + FRAME_OFFSET, z + 1, consumer)
}
return 2 // bar + frame ToDo: Item rendering
}
override fun silentApply() {
size = HOTBAR_BASE_SIZE + Vec2i(HORIZONTAL_MARGIN * 2, 1) // offset left and right; offset for the frame is just on top, not on the bottom
}
companion object {
private val BASE = "minecraft:hotbar_base".toResourceLocation()
private val FRAME = "minecraft:hotbar_frame".toResourceLocation()
private val HOTBAR_BASE_SIZE = Vec2i(182, 22)
private const val FRAME_SIZE = 24
private const val HORIZONTAL_MARGIN = 1
private const val FRAME_OFFSET = -2 // FRAME_SIZE - HOTBAR_BASE_SIZE.y
}
}

View File

@ -0,0 +1,48 @@
/*
* Minosoft
* Copyright (C) 2021 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 <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.gui.rendering.gui.hud.elements.hotbar
import de.bixilon.minosoft.gui.rendering.gui.elements.Element
import de.bixilon.minosoft.gui.rendering.gui.hud.HUDRenderer
import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexConsumer
import glm_.vec2.Vec2i
import java.lang.Integer.max
class HotbarElement(hudRenderer: HUDRenderer) : Element(hudRenderer) {
private val base = HotbarBaseElement(hudRenderer)
private val health = HotbarHealthElement(hudRenderer)
init {
silentApply()
}
override fun render(offset: Vec2i, z: Int, consumer: GUIVertexConsumer): Int {
silentApply()
val initialOffset = Vec2i(offset)
var maxZ = 0
maxZ = max(maxZ, health.render(offset, z, consumer))
offset.y += health.size.y
maxZ = max(maxZ, base.render(offset, z, consumer))
return maxZ
}
override fun silentApply() {
base.silentApply()
health.silentApply()
size = base.size + Vec2i(0, health.size.y)
}
}

View File

@ -0,0 +1,48 @@
/*
* Minosoft
* Copyright (C) 2021 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 <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.gui.rendering.gui.hud.elements.hotbar
import de.bixilon.minosoft.data.registries.ResourceLocation
import de.bixilon.minosoft.gui.rendering.gui.hud.HUDRenderer
import de.bixilon.minosoft.gui.rendering.gui.hud.elements.HUDBuilder
import de.bixilon.minosoft.gui.rendering.gui.hud.elements.HUDElement
import de.bixilon.minosoft.util.KUtil.toResourceLocation
import glm_.vec2.Vec2i
class HotbarHUDElement(hudRenderer: HUDRenderer) : HUDElement<HotbarElement>(hudRenderer) {
private val connection = renderWindow.connection
override lateinit var layout: HotbarElement
override val layoutOffset: Vec2i
get() = Vec2i((hudRenderer.scaledSize.x - layout.size.x) / 2, hudRenderer.scaledSize.y - layout.size.y)
override fun init() {
}
override fun postInit() {
layout = HotbarElement(hudRenderer)
layout.prefMaxSize = Vec2i(-1, -1)
}
companion object : HUDBuilder<HotbarHUDElement> {
override val RESOURCE_LOCATION: ResourceLocation = "minosoft:hotbar".toResourceLocation()
override fun build(hudRenderer: HUDRenderer): HotbarHUDElement {
return HotbarHUDElement(hudRenderer)
}
}
}

View File

@ -0,0 +1,249 @@
/*
* Minosoft
* Copyright (C) 2021 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 <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.gui.rendering.gui.hud.elements.hotbar
import de.bixilon.minosoft.data.registries.effects.DefaultStatusEffects
import de.bixilon.minosoft.data.registries.effects.attributes.DefaultStatusEffectAttributeNames
import de.bixilon.minosoft.gui.rendering.gui.elements.Element
import de.bixilon.minosoft.gui.rendering.gui.elements.primitive.ImageElement
import de.bixilon.minosoft.gui.rendering.gui.hud.HUDRenderer
import de.bixilon.minosoft.gui.rendering.gui.hud.atlas.HUDAtlasElement
import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexConsumer
import de.bixilon.minosoft.util.KUtil.decide
import de.bixilon.minosoft.util.KUtil.unsafeCast
import de.bixilon.minosoft.util.MMath.ceil
import glm_.vec2.Vec2i
class HotbarHealthElement(hudRenderer: HUDRenderer) : Element(hudRenderer) {
private val witherStatusEffect = hudRenderer.connection.registries.statusEffectRegistry[DefaultStatusEffects.WITHER]
private val poisonStatusEffect = hudRenderer.connection.registries.statusEffectRegistry[DefaultStatusEffects.POISON]
private val atlasManager = hudRenderer.atlasManager
/**
* [normal|hardcore][normal|poison|wither][normal|damage][full|half]
*/
private val hearts = arrayOf(
arrayOf(
arrayOf(
arrayOf(
atlasManager["minecraft:normal_heart"],
atlasManager["minecraft:half_normal_heart"],
),
arrayOf(
atlasManager["minecraft:normal_damage_heart"],
atlasManager["minecraft:half_normal_damage_heart"],
),
),
arrayOf(
arrayOf(
atlasManager["minecraft:poison_heart"],
atlasManager["minecraft:half_poison_heart"],
),
arrayOf(
atlasManager["minecraft:poison_damage_heart"],
atlasManager["minecraft:half_poison_damage_heart"],
),
),
arrayOf(
arrayOf(
atlasManager["minecraft:wither_heart"],
atlasManager["minecraft:half_wither_heart"],
),
arrayOf(
atlasManager["minecraft:wither_damage_heart"],
atlasManager["minecraft:half_wither_damage_heart"],
),
),
),
arrayOf(
arrayOf(
arrayOf(
atlasManager["minecraft:hardcore_normal_heart"],
atlasManager["minecraft:hardcore_half_normal_heart"],
),
arrayOf(
atlasManager["minecraft:hardcore_normal_damage_heart"],
atlasManager["minecraft:hardcore_half_normal_damage_heart"],
),
),
arrayOf(
arrayOf(
atlasManager["minecraft:hardcore_poison_heart"],
atlasManager["minecraft:hardcore_half_poison_heart"],
),
arrayOf(
atlasManager["minecraft:hardcore_poison_damage_heart"],
atlasManager["minecraft:hardcore_half_poison_damage_heart"],
),
),
arrayOf(
arrayOf(
atlasManager["minecraft:hardcore_wither_heart"],
atlasManager["minecraft:hardcore_half_wither_heart"],
),
arrayOf(
atlasManager["minecraft:hardcore_wither_damage_heart"],
atlasManager["minecraft:hardcore_half_wither_damage_heart"],
),
),
),
)
/**
* [normal|hardcore][full|half]
*/
private val absorptionHearts = arrayOf(
arrayOf(
atlasManager["minecraft:absorption_heart"],
atlasManager["minecraft:half_absorption_heart"],
),
arrayOf(
atlasManager["minecraft:hardcore_absorption_heart"],
atlasManager["minecraft:hardcore_half_absorption_heart"],
),
)
/**
* [normal|hardcore][full|half]
*/
private val frozenHearts = arrayOf(
arrayOf(
atlasManager["minecraft:frozen_heart"],
atlasManager["minecraft:half_frozen_heart"],
),
arrayOf(
atlasManager["minecraft:hardcore_frozen_heart"],
atlasManager["minecraft:hardcore_half_frozen_heart"],
),
)
private val whiteHeartContainer = atlasManager["minecraft:white_heart_container"]
private val blackHeartContainer = atlasManager["minecraft:black_heart_container"]
private var hardcode = false
private var poison = false
private var wither = false
private var frozen = false
private var health = 0.0f
private var absorptionsAmount = 0.0f
private var totalHealth = 0.0f
private var maxHealth = 0.0f
private var totalMaxHealth = 0.0f
private var totalMaxHearts = 0
private var rows = 0
override fun render(offset: Vec2i, z: Int, consumer: GUIVertexConsumer): Int {
blackHeartContainer ?: return 0
whiteHeartContainer ?: return 0
// ToDo: Damage animation, regeneration, caching
for (heart in 0 until totalMaxHearts) {
val row = heart / HEARTS_PER_ROW
val column = heart % HEARTS_PER_ROW
val image = ImageElement(hudRenderer, blackHeartContainer)
image.render(offset + Vec2i(column, (rows - 1) - row) * HEART_SIZE, z, consumer)
}
val hardcoreIndex = hardcode.decide(1, 0)
var healthLeft = totalHealth
var heart = 0
while (healthLeft > 0.5f) {
val row = heart / HEARTS_PER_ROW
val column = heart % HEARTS_PER_ROW
var selectArray: Array<*>? = null
var normalHeart = false
if (healthLeft <= absorptionsAmount) {
selectArray = absorptionHearts[hardcoreIndex]
} else {
selectArray = hearts[hardcoreIndex]
// heart type
selectArray = selectArray[when {
poison -> 1
wither -> 2
else -> {
normalHeart = true
0
}
}]
// ToDo: damage heart
selectArray = selectArray[0]
}
if (frozen && normalHeart) {
selectArray = frozenHearts[hardcoreIndex]
}
val halfHeart = healthLeft < 1.5f
val image = when {
halfHeart -> selectArray.unsafeCast<Array<HUDAtlasElement?>>()[1]?.let { ImageElement(hudRenderer, it) }
else -> selectArray.unsafeCast<Array<HUDAtlasElement?>>()[0]?.let { ImageElement(hudRenderer, it) }
}
image?.render(offset + Vec2i(column, (rows - 1) - row) * HEART_SIZE, z + 1, consumer)
heart++
healthLeft -= halfHeart.decide(1.0f, 2.0f)
}
return 2
}
override fun silentApply() {
blackHeartContainer ?: return
whiteHeartContainer ?: return
val player = hudRenderer.connection.player
hardcode = hudRenderer.connection.world.hardcore
poison = poisonStatusEffect?.let { player.activeStatusEffects[it] != null } ?: false
wither = witherStatusEffect?.let { player.activeStatusEffects[it] != null } ?: false
frozen = player.ticksFrozen > 0
health = player.healthCondition.hp
absorptionsAmount = player.playerAbsorptionHearts // ToDo: This is (probably) calculated as effect instance
totalHealth = health + absorptionsAmount
maxHealth = player.getAttributeValue(DefaultStatusEffectAttributeNames.GENERIC_MAX_HEALTH).toFloat()
totalMaxHealth = maxHealth + absorptionsAmount
totalMaxHearts = (totalMaxHealth / 2).ceil
rows = totalMaxHearts / HEARTS_PER_ROW
if (totalMaxHearts % HP_PER_ROW != 0) {
rows++
}
size = Vec2i(HEARTS_PER_ROW, rows) * HEART_SIZE
}
companion object {
private const val HP_PER_ROW = 20
private const val HEARTS_PER_ROW = HP_PER_ROW / 2
private val HEART_SIZE = Vec2i(8, 9)
}
}