diff --git a/src/main/java/de/bixilon/minosoft/data/player/PlayerProperty.kt b/src/main/java/de/bixilon/minosoft/data/player/PlayerProperty.kt index bedc30974..5b37de058 100644 --- a/src/main/java/de/bixilon/minosoft/data/player/PlayerProperty.kt +++ b/src/main/java/de/bixilon/minosoft/data/player/PlayerProperty.kt @@ -19,4 +19,8 @@ class PlayerProperty( ) { val isSigned: Boolean get() = signature != null // ToDo check signature + + override fun toString(): String { + return "$key: $value" + } } diff --git a/src/main/java/de/bixilon/minosoft/data/player/tab/TabList.kt b/src/main/java/de/bixilon/minosoft/data/player/tab/TabList.kt index e512d23bb..29354be84 100644 --- a/src/main/java/de/bixilon/minosoft/data/player/tab/TabList.kt +++ b/src/main/java/de/bixilon/minosoft/data/player/tab/TabList.kt @@ -17,7 +17,8 @@ import de.bixilon.minosoft.data.text.ChatComponent import java.util.* class TabList { - val tabListItems: MutableMap = mutableMapOf() + val tabListItemsByUUID: MutableMap = mutableMapOf() + val tabListItemsByName: MutableMap = mutableMapOf() var header = ChatComponent.of("") var footer = ChatComponent.of("") } diff --git a/src/main/java/de/bixilon/minosoft/data/player/tab/TabListItem.kt b/src/main/java/de/bixilon/minosoft/data/player/tab/TabListItem.kt index 2e512c264..7feb228dc 100644 --- a/src/main/java/de/bixilon/minosoft/data/player/tab/TabListItem.kt +++ b/src/main/java/de/bixilon/minosoft/data/player/tab/TabListItem.kt @@ -15,6 +15,7 @@ package de.bixilon.minosoft.data.player.tab import de.bixilon.minosoft.data.abilities.Gamemodes import de.bixilon.minosoft.data.player.PlayerProperty +import de.bixilon.minosoft.data.scoreboard.Team import de.bixilon.minosoft.data.text.ChatComponent data class TabListItem( @@ -23,6 +24,7 @@ data class TabListItem( var gamemode: Gamemodes = Gamemodes.SURVIVAL, var displayName: ChatComponent = ChatComponent.of(name), var properties: Map = mutableMapOf(), + var team: Team? = null, ) { fun merge(data: TabListItemData) { @@ -42,5 +44,10 @@ data class TabListItem( } } data.properties?.let { properties = it } + + if (data.removeFromTeam) { + this.team = null + } + data.team?.let { team = it } } } diff --git a/src/main/java/de/bixilon/minosoft/data/player/tab/TabListItemData.kt b/src/main/java/de/bixilon/minosoft/data/player/tab/TabListItemData.kt index c84c8bd51..4607b1dc6 100644 --- a/src/main/java/de/bixilon/minosoft/data/player/tab/TabListItemData.kt +++ b/src/main/java/de/bixilon/minosoft/data/player/tab/TabListItemData.kt @@ -15,6 +15,7 @@ package de.bixilon.minosoft.data.player.tab import de.bixilon.minosoft.data.abilities.Gamemodes import de.bixilon.minosoft.data.player.PlayerProperty +import de.bixilon.minosoft.data.scoreboard.Team import de.bixilon.minosoft.data.text.ChatComponent data class TabListItemData( @@ -25,4 +26,6 @@ data class TabListItemData( var displayName: ChatComponent? = null, val properties: Map? = null, var remove: Boolean = false, // used for legacy tab list + var team: Team? = null, + var removeFromTeam: Boolean = false, ) diff --git a/src/main/java/de/bixilon/minosoft/data/scoreboard/Team.kt b/src/main/java/de/bixilon/minosoft/data/scoreboard/Team.kt index 0126e42c3..050b5e264 100644 --- a/src/main/java/de/bixilon/minosoft/data/scoreboard/Team.kt +++ b/src/main/java/de/bixilon/minosoft/data/scoreboard/Team.kt @@ -15,7 +15,7 @@ package de.bixilon.minosoft.data.scoreboard import de.bixilon.minosoft.data.text.ChatCode import de.bixilon.minosoft.data.text.ChatComponent -class Team( +data class Team( val name: String, var displayName: ChatComponent, var prefix: ChatComponent, @@ -26,4 +26,8 @@ class Team( var nameTagVisibility: NameTagVisibilities, var formattingCode: ChatCode?, val members: MutableSet, -) +) { + override fun toString(): String { + return name + } +} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/elements/primitive/ImageElement.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/elements/primitive/ImageElement.kt index 294b331de..6cce33524 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/elements/primitive/ImageElement.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/elements/primitive/ImageElement.kt @@ -57,6 +57,13 @@ open class ImageElement( super.size = value cacheUpToDate = false } + + override var prefSize: Vec2i + get() = size + set(value) { + size = value + } + var tint: RGBColor = tint set(value) { field = value diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/elements/text/TextElement.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/elements/text/TextElement.kt index 3a08286b2..6797f132f 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/elements/text/TextElement.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/elements/text/TextElement.kt @@ -41,6 +41,7 @@ open class TextElement( private var previousOffset = Vec2i.EMPTY private var previousMatrix = hudRenderer.matrix + private var previousMaxSize = Vec2i.EMPTY private var preparedSize = Vec2i.EMPTY var renderInfo = TextRenderInfo() @@ -73,6 +74,7 @@ open class TextElement( } override fun silentApply() { + val maxSize = maxSize val size = Vec2i.EMPTY if (!emptyMessage) { val renderInfo = TextRenderInfo() @@ -81,6 +83,8 @@ open class TextElement( this.renderInfo = renderInfo } + + this.previousMaxSize = maxSize this.cacheUpToDate = false this.size = size preparedSize = size @@ -90,6 +94,10 @@ open class TextElement( override fun onParentChange() { val maxSize = maxSize + if (previousMaxSize == maxSize) { + // no change in size + return + } val prefSize = prefSize if (preparedSize.x < prefSize.x || preparedSize.x > maxSize.x) { diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/elements/text/TextFlowElement.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/elements/text/TextFlowElement.kt index 43a1ebba1..9c790f1a8 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/elements/text/TextFlowElement.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/elements/text/TextFlowElement.kt @@ -56,7 +56,7 @@ class TextFlowElement( yOffset += Font.TOTAL_CHAR_HEIGHT } - background.render(offset, z, consumer) + background.render(Vec2i(offset), z, consumer) return LAYERS } @@ -159,6 +159,6 @@ class TextFlowElement( companion object { const val LAYERS = TextElement.LAYERS - const val MAX_TOTAL_MESSAGES = 500 // ToDo: Used for scrolling + const val MAX_TOTAL_MESSAGES = 500 } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/hud/HUDRenderer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/hud/HUDRenderer.kt index ce9839a9f..aad7598fd 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/hud/HUDRenderer.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/hud/HUDRenderer.kt @@ -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.tab.TabListHUDElement import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIMesh import de.bixilon.minosoft.gui.rendering.modding.events.ResizeWindowEvent import de.bixilon.minosoft.gui.rendering.util.vec.Vec2Util.EMPTY @@ -77,6 +78,7 @@ class HUDRenderer( if (Minosoft.config.config.game.hud.internalMessages.enabled) { registerElement(InternalMessagesHUDElement) } + registerElement(TabListHUDElement) } override fun init() { diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/hud/atlas/HUDAtlasElement.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/hud/atlas/HUDAtlasElement.kt index 5823d0ac0..28c4967db 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/hud/atlas/HUDAtlasElement.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/hud/atlas/HUDAtlasElement.kt @@ -23,7 +23,7 @@ class HUDAtlasElement( val end: Vec2i, val slots: Map>, // ToDo: Use an array? ) : TextureLike { - override val size: Vec2i = start - end + override val size: Vec2i = end - start override lateinit var uvStart: Vec2 override lateinit var uvEnd: Vec2 } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/hud/elements/tab/TabListElement.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/hud/elements/tab/TabListElement.kt new file mode 100644 index 000000000..ab23ebedd --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/hud/elements/tab/TabListElement.kt @@ -0,0 +1,185 @@ +/* + * 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 . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.gui.rendering.gui.hud.elements.tab + +import de.bixilon.minosoft.data.abilities.Gamemodes +import de.bixilon.minosoft.data.text.RGBColor +import de.bixilon.minosoft.gui.rendering.gui.elements.Element +import de.bixilon.minosoft.gui.rendering.gui.elements.ElementAlignments +import de.bixilon.minosoft.gui.rendering.gui.elements.ElementAlignments.Companion.getOffset +import de.bixilon.minosoft.gui.rendering.gui.elements.primitive.ColorElement +import de.bixilon.minosoft.gui.rendering.gui.elements.text.TextElement +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.gui.rendering.util.vec.Vec2Util.EMPTY +import de.bixilon.minosoft.util.KUtil.decide +import de.bixilon.minosoft.util.KUtil.nullCompare +import de.bixilon.minosoft.util.KUtil.toResourceLocation +import de.bixilon.minosoft.util.KUtil.toSynchronizedMap +import glm_.vec2.Vec2i +import java.lang.Integer.max + +class TabListElement(hudRenderer: HUDRenderer) : Element(hudRenderer) { + val header = TextElement(hudRenderer, "", background = false, fontAlignment = ElementAlignments.CENTER, parent = this) + val footer = TextElement(hudRenderer, "", background = false, fontAlignment = ElementAlignments.CENTER, parent = this) + + private val background = ColorElement(hudRenderer, Vec2i.EMPTY, color = RGBColor(0, 0, 0, 120)) + private var entriesSize = Vec2i.EMPTY + private var entries: List = listOf() + + val PING_BARS: Array = arrayOf( + hudRenderer.atlasManager["minecraft:tab_list_ping_0".toResourceLocation()]!!, + hudRenderer.atlasManager["minecraft:tab_list_ping_1".toResourceLocation()]!!, + hudRenderer.atlasManager["minecraft:tab_list_ping_2".toResourceLocation()]!!, + hudRenderer.atlasManager["minecraft:tab_list_ping_3".toResourceLocation()]!!, + hudRenderer.atlasManager["minecraft:tab_list_ping_4".toResourceLocation()]!!, + hudRenderer.atlasManager["minecraft:tab_list_ping_5".toResourceLocation()]!!, + ) + + override fun render(offset: Vec2i, z: Int, consumer: GUIVertexConsumer): Int { + silentApply() + background.render(Vec2i(offset), z, consumer) + val size = size + + header.size.let { + header.onParentChange() + header.render(offset + Vec2i(ElementAlignments.CENTER.getOffset(size.x, it.x), 0), z, consumer) + offset.y += it.y + } + + val offsetBefore = Vec2i(offset) + offset.x += ElementAlignments.CENTER.getOffset(size.x, entriesSize.x) + + var columns = entries.size / ENTRIES_PER_COLUMN + if (entries.size % ENTRIES_PER_COLUMN > 0) { + columns++ + } + + for ((index, entry) in entries.withIndex()) { + entry.render(Vec2i(offset), z + 1, consumer) + offset.y += TabListEntryElement.HEIGHT + ENTRY_VERTICAL_SPACING + if ((index + 1) % ENTRIES_PER_COLUMN == 0) { + offset.x += entry.width + ENTRY_HORIZONTAL_SPACING + offset.y = offsetBefore.y + } + } + offset.x = offsetBefore.x + offset.y = offsetBefore.y + (columns > 1).decide(ENTRIES_PER_COLUMN, entries.size) * (TabListEntryElement.HEIGHT + ENTRY_VERTICAL_SPACING) + + + footer.size.let { + footer.render(offset + Vec2i(ElementAlignments.CENTER.getOffset(size.x, it.x), 0), z, consumer) + offset.y += it.y + } + + return TextElement.LAYERS + 1 // ToDo + } + + override fun silentApply() { + val maxSize = maxSize + val size = Vec2i.EMPTY + + + header.onParentChange() + footer.onParentChange() + + size.y += header.size.y + + val entries: MutableList = mutableListOf() + + val tabListItems = hudRenderer.connection.tabList.tabListItemsByUUID.toSynchronizedMap().values.sortedWith { a, b -> + if (a.gamemode != b.gamemode) { + if (a.gamemode == Gamemodes.SPECTATOR) { + return@sortedWith -1 + } + if (b.gamemode == Gamemodes.SPECTATOR) { + return@sortedWith 1 + } + } + + a.team?.name?.nullCompare(b.team?.name)?.let { return@sortedWith it } + + a.name.nullCompare(b?.name)?.let { return@sortedWith it } // ToDo: Case? + + return@sortedWith 0 + } + + val previousSize = Vec2i(size) + var columns = tabListItems.size / ENTRIES_PER_COLUMN + if (tabListItems.size % ENTRIES_PER_COLUMN > 0) { + columns++ + } + + var column = 0 + val widths = IntArray(columns) + var currentMaxPrefWidth = 0 + var totalEntriesWidth = 0 + + // Check width + for ((index, item) in tabListItems.withIndex()) { + val entry = TabListEntryElement(hudRenderer, this, item, 0) + entries += entry + val prefWidth = entry.prefSize + + currentMaxPrefWidth = max(currentMaxPrefWidth, prefWidth.x) + if ((index + 1) % ENTRIES_PER_COLUMN == 0) { + widths[column] = currentMaxPrefWidth + totalEntriesWidth += currentMaxPrefWidth + currentMaxPrefWidth = 0 + column++ + } + } + if (currentMaxPrefWidth != 0) { + widths[column] = currentMaxPrefWidth + totalEntriesWidth += currentMaxPrefWidth + } + size.x = max(size.x, totalEntriesWidth) + size.y += (columns > 1).decide(ENTRIES_PER_COLUMN, entries.size) * (TabListEntryElement.HEIGHT + ENTRY_VERTICAL_SPACING) + + this.entriesSize = Vec2i(totalEntriesWidth, size.y - previousSize.y) + + + // apply width to every cell + column = 0 + for ((index, entry) in entries.withIndex()) { + entry.width = widths[column] + if ((index + 1) % ENTRIES_PER_COLUMN == 0) { + column++ + } + } + + if (columns >= 2) { + size.x += (columns - 1) * ENTRY_HORIZONTAL_SPACING + } + + this.entries = entries + + size.y += footer.size.y + + size.x = max(max(size.x, header.size.x), footer.size.x) + + + this.size = size + + background.size = size + } + + + companion object { + private const val ENTRIES_PER_COLUMN = 20 + private const val ENTRY_HORIZONTAL_SPACING = 5 + private const val ENTRY_VERTICAL_SPACING = 1 + } +} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/hud/elements/tab/TabListEntryElement.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/hud/elements/tab/TabListEntryElement.kt new file mode 100644 index 000000000..64b42df2d --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/hud/elements/tab/TabListEntryElement.kt @@ -0,0 +1,122 @@ +/* + * 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 . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.gui.rendering.gui.hud.elements.tab + +import de.bixilon.minosoft.data.player.tab.TabListItem +import de.bixilon.minosoft.data.text.BaseComponent +import de.bixilon.minosoft.data.text.ChatComponent +import de.bixilon.minosoft.data.text.RGBColor +import de.bixilon.minosoft.gui.rendering.gui.elements.Element +import de.bixilon.minosoft.gui.rendering.gui.elements.ElementAlignments +import de.bixilon.minosoft.gui.rendering.gui.elements.ElementAlignments.Companion.getOffset +import de.bixilon.minosoft.gui.rendering.gui.elements.primitive.ColorElement +import de.bixilon.minosoft.gui.rendering.gui.elements.primitive.ImageElement +import de.bixilon.minosoft.gui.rendering.gui.elements.text.TextElement +import de.bixilon.minosoft.gui.rendering.gui.hud.HUDRenderer +import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexConsumer +import de.bixilon.minosoft.gui.rendering.util.vec.Vec2Util.EMPTY +import glm_.vec2.Vec2i +import java.lang.Integer.max + +class TabListEntryElement( + hudRenderer: HUDRenderer, + val tabList: TabListElement, + val item: TabListItem, + width: Int, +) : Element(hudRenderer) { + // ToDo: Skin + private val background: ColorElement + + private val nameElement = TextElement(hudRenderer, "", background = false, parent = this) + private lateinit var pingElement: ImageElement + + private var lastDisplayName: ChatComponent? = null + private var lastPing = -1 + + override var prefSize: Vec2i = Vec2i.EMPTY + override var prefMaxSize: Vec2i + get() = Vec2i(width, HEIGHT) + set(value) {} + override var size: Vec2i + get() = maxSize + set(value) {} + + private var forcePrepare = true + var width: Int = width + set(value) { + field = value + forcePrepare = true + apply() + } + + init { + background = ColorElement(hudRenderer, size, RGBColor(80, 80, 80, 130)) + silentApply() + } + + override fun render(offset: Vec2i, z: Int, consumer: GUIVertexConsumer): Int { + background.render(Vec2i(offset), z, consumer) + nameElement.render(Vec2i(offset), z, consumer) + pingElement.render(offset + Vec2i(ElementAlignments.RIGHT.getOffset(maxSize.x, pingElement.size.x + PADDING), PADDING), z + 1, consumer) + + return TextElement.LAYERS + } + + override fun silentApply() { + val ping = item.ping + + if (forcePrepare || ping != lastPing) { + pingElement = ImageElement(hudRenderer, tabList.PING_BARS[when { + ping < 0 -> 0 + ping < 150 -> 5 + ping < 300 -> 4 + ping < 600 -> 3 + ping < 1000 -> 2 + else -> 1 + }]) + nameElement.prefMaxSize = Vec2i(max(0, maxSize.x - pingElement.size.x), HEIGHT) + lastPing = ping + } + val displayName = BaseComponent() + item.team?.prefix?.let { + displayName += it + } + displayName += item.displayName.apply { + // ToDo: Set correct formatting code + val color = item.team?.formattingCode + if (color is RGBColor) { + applyDefaultColor(color) + } + } + item.team?.suffix?.let { + displayName += it + } + + if (forcePrepare || displayName !== lastDisplayName) { + nameElement.text = displayName + lastDisplayName = displayName + } + + this.prefSize = Vec2i((PADDING * 2) + nameElement.prefSize.x + INNER_MARGIN + pingElement.prefSize.x, HEIGHT) + background.size = size + forcePrepare = false + } + + + companion object { + const val HEIGHT = 10 + const val INNER_MARGIN = 5 + const val PADDING = 1 + } +} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/hud/elements/tab/TabListHUDElement.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/hud/elements/tab/TabListHUDElement.kt new file mode 100644 index 000000000..eeca8702e --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/hud/elements/tab/TabListHUDElement.kt @@ -0,0 +1,53 @@ +/* + * 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 . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.gui.rendering.gui.hud.elements.tab + +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.modding.event.events.TabListInfoChangeEvent +import de.bixilon.minosoft.modding.event.invoker.CallbackEventInvoker +import de.bixilon.minosoft.util.KUtil.toResourceLocation +import glm_.vec2.Vec2i + +class TabListHUDElement(hudRenderer: HUDRenderer) : HUDElement(hudRenderer) { + private val connection = renderWindow.connection + override val layout = TabListElement(hudRenderer) + + + override val layoutOffset: Vec2i + get() = Vec2i((hudRenderer.scaledSize.x - layout.size.x) / 2, 20) + + init { + layout.prefMaxSize = Vec2i(-1, -1) + } + + + override fun init() { + connection.registerEvent(CallbackEventInvoker.of { + layout.header.text = it.header + layout.footer.text = it.footer + }) + } + + + companion object : HUDBuilder { + override val RESOURCE_LOCATION: ResourceLocation = "minosoft:tab_list".toResourceLocation() + + override fun build(hudRenderer: HUDRenderer): TabListHUDElement { + return TabListHUDElement(hudRenderer) + } + } +} diff --git a/src/main/java/de/bixilon/minosoft/modding/event/events/PlayerListInfoChangeEvent.kt b/src/main/java/de/bixilon/minosoft/modding/event/events/TabListInfoChangeEvent.kt similarity index 97% rename from src/main/java/de/bixilon/minosoft/modding/event/events/PlayerListInfoChangeEvent.kt rename to src/main/java/de/bixilon/minosoft/modding/event/events/TabListInfoChangeEvent.kt index d22019558..2893f5ccb 100644 --- a/src/main/java/de/bixilon/minosoft/modding/event/events/PlayerListInfoChangeEvent.kt +++ b/src/main/java/de/bixilon/minosoft/modding/event/events/TabListInfoChangeEvent.kt @@ -18,7 +18,7 @@ import de.bixilon.minosoft.modding.event.events.connection.play.PlayConnectionEv import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection import de.bixilon.minosoft.protocol.packets.s2c.play.TabListTextSetS2CP -class PlayerListInfoChangeEvent( +class TabListInfoChangeEvent( connection: PlayConnection, initiator: EventInitiators, val header: ChatComponent, diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/PlayerEntitySpawnS2CP.kt b/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/PlayerEntitySpawnS2CP.kt index 1dc85c501..476b8342c 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/PlayerEntitySpawnS2CP.kt +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/PlayerEntitySpawnS2CP.kt @@ -86,7 +86,7 @@ class PlayerEntitySpawnS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket() { } override fun handle(connection: PlayConnection) { - connection.tabList.tabListItems[entityUUID]?.let { entity.tabListItem = it } + // connection.tabList.tabListItemsByUUID[entityUUID]?.let { entity.tabListItem = it } connection.fireEvent(EntitySpawnEvent(connection, this)) connection.world.entities.add(entityId, entityUUID, entity) diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/TabListDataS2CP.kt b/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/TabListDataS2CP.kt index 240d38044..82460101b 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/TabListDataS2CP.kt +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/TabListDataS2CP.kt @@ -24,6 +24,7 @@ import de.bixilon.minosoft.protocol.packets.s2c.PlayS2CPacket import de.bixilon.minosoft.protocol.protocol.PlayInByteBuffer import de.bixilon.minosoft.protocol.protocol.ProtocolVersions import de.bixilon.minosoft.util.KUtil +import de.bixilon.minosoft.util.KUtil.toSynchronizedMap import de.bixilon.minosoft.util.enum.ValuesEnum import de.bixilon.minosoft.util.logging.Log import de.bixilon.minosoft.util.logging.LogLevels @@ -123,36 +124,47 @@ class TabListDataS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket() { if (connection.version.versionId < ProtocolVersions.V_14W19A) { // ToDo: 19? val item: TabListItem = if (data.remove) { // add or remove - connection.tabList.tabListItems[uuid]?.apply { - connection.tabList.tabListItems.remove(uuid) - } ?: let { + connection.tabList.tabListItemsByUUID[uuid]?.apply { + connection.tabList.tabListItemsByUUID.remove(uuid) + connection.tabList.tabListItemsByName.remove(data.name) + } ?: TabListItem(name = data.name!!).apply { // add - val itemToAdd = TabListItem(name = data.name!!) - connection.tabList.tabListItems[uuid] = itemToAdd - itemToAdd + connection.tabList.tabListItemsByUUID[uuid] = this + connection.tabList.tabListItemsByName[data.name] = this } } else { - connection.tabList.tabListItems[uuid]!! + connection.tabList.tabListItemsByUUID[uuid]!! } item.merge(data) continue } if (data.remove) { - connection.tabList.tabListItems.remove(uuid) + val item = connection.tabList.tabListItemsByUUID.remove(uuid) ?: continue + connection.tabList.tabListItemsByName.remove(item.name) continue } val entity = connection.world.entities[uuid] - val tabListItem = connection.tabList.tabListItems[uuid] ?: run { + val tabListItem = connection.tabList.tabListItemsByUUID[uuid] ?: run { if (data.name == null) { // item not yet created return@run null } val item = TabListItem(name = data.name) - connection.tabList.tabListItems[uuid] = item + connection.tabList.tabListItemsByUUID[uuid] = item + connection.tabList.tabListItemsByName[data.name] = item + + // set team + + for (team in connection.scoreboardManager.teams.toSynchronizedMap().values) { + if (team.members.contains(data.name)) { + item.team = team + break + } + } item } ?: continue diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/TabListTextSetS2CP.kt b/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/TabListTextSetS2CP.kt index cc5661fa1..bc032499c 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/TabListTextSetS2CP.kt +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/TabListTextSetS2CP.kt @@ -13,7 +13,7 @@ package de.bixilon.minosoft.protocol.packets.s2c.play import de.bixilon.minosoft.data.text.ChatComponent -import de.bixilon.minosoft.modding.event.events.PlayerListInfoChangeEvent +import de.bixilon.minosoft.modding.event.events.TabListInfoChangeEvent import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection import de.bixilon.minosoft.protocol.packets.s2c.PlayS2CPacket import de.bixilon.minosoft.protocol.protocol.PlayInByteBuffer @@ -26,7 +26,7 @@ class TabListTextSetS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket() { val footer: ChatComponent = buffer.readChatComponent() override fun handle(connection: PlayConnection) { - if (connection.fireEvent(PlayerListInfoChangeEvent(connection, this))) { + if (connection.fireEvent(TabListInfoChangeEvent(connection, this))) { return } connection.tabList.header = header diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/scoreboard/teams/TeamCreateS2CP.kt b/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/scoreboard/teams/TeamCreateS2CP.kt index 365170aa5..d2cb0f6a8 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/scoreboard/teams/TeamCreateS2CP.kt +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/scoreboard/teams/TeamCreateS2CP.kt @@ -101,7 +101,7 @@ class TeamCreateS2CP(val name: String, buffer: PlayInByteBuffer) : PlayS2CPacket override fun handle(connection: PlayConnection) { - connection.scoreboardManager.teams[name] = Team( + val team = Team( name = name, displayName = displayName, prefix = prefix, @@ -113,6 +113,11 @@ class TeamCreateS2CP(val name: String, buffer: PlayInByteBuffer) : PlayS2CPacket formattingCode = formattingCode, members = members.toMutableSet(), ) + connection.scoreboardManager.teams[name] = team + + for (member in members) { + connection.tabList.tabListItemsByName[member]?.team = team + } } override fun log() { diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/scoreboard/teams/TeamMemberAddS2CP.kt b/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/scoreboard/teams/TeamMemberAddS2CP.kt index 1cf2ed93f..8546c20d1 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/scoreboard/teams/TeamMemberAddS2CP.kt +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/scoreboard/teams/TeamMemberAddS2CP.kt @@ -32,7 +32,12 @@ class TeamMemberAddS2CP(val name: String, buffer: PlayInByteBuffer) : PlayS2CPac override fun handle(connection: PlayConnection) { - connection.scoreboardManager.teams[name]?.members?.addAll(members) + val team = connection.scoreboardManager.teams[name] ?: return + team.members += members + + for (member in members) { + connection.tabList.tabListItemsByName[member]?.team = team + } } override fun log() { diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/scoreboard/teams/TeamMemberRemoveS2CP.kt b/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/scoreboard/teams/TeamMemberRemoveS2CP.kt index 01e717591..8a0cf57bc 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/scoreboard/teams/TeamMemberRemoveS2CP.kt +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/scoreboard/teams/TeamMemberRemoveS2CP.kt @@ -31,7 +31,16 @@ class TeamMemberRemoveS2CP(val name: String, buffer: PlayInByteBuffer) : PlayS2C override fun handle(connection: PlayConnection) { - connection.scoreboardManager.teams[name]?.members?.removeAll(members) + val team = connection.scoreboardManager.teams[name] ?: return + team.members -= members + + for (member in members) { + val item = connection.tabList.tabListItemsByName[member] ?: continue + if (item.team != team) { + continue + } + item.team = team + } } override fun log() { diff --git a/src/main/java/de/bixilon/minosoft/protocol/protocol/PacketTypes.kt b/src/main/java/de/bixilon/minosoft/protocol/protocol/PacketTypes.kt index fca0cb2a5..f194d9c13 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/protocol/PacketTypes.kt +++ b/src/main/java/de/bixilon/minosoft/protocol/protocol/PacketTypes.kt @@ -211,14 +211,14 @@ class PacketTypes { PLAY_COMBAT_EVENT_END({ CombatEventEndS2CP(it) }), PLAY_COMBAT_EVENT_ENTER({ CombatEventEnterS2CP() }), PLAY_COMBAT_EVENT_KILL({ CombatEventKillS2CP(it) }), - PLAY_TAB_LIST_DATA({ TabListDataS2CP(it) }), + PLAY_TAB_LIST_DATA({ TabListDataS2CP(it) }, isThreadSafe = false), PLAY_PLAYER_FACE({ PlayerFaceS2CP(it) }), PLAY_POSITION_AND_ROTATION({ PositionAndRotationS2CP(it) }), PLAY_UNLOCK_RECIPES({ PacketUnlockRecipes(it) }), PLAY_ENTITY_DESTROY({ EntityDestroyS2CP(it) }), PLAY_ENTITY_STATUS_EFFECT_REMOVE({ EntityStatusEffectRemoveS2CP(it) }), PLAY_RESOURCEPACK_REQUEST({ ResourcepackRequestS2CP(it) }), - PLAY_RESPAWN({ RespawnS2CP(it) }, isThreadSafe = false), + PLAY_RESPAWN({ RespawnS2CP(it) }), PLAY_ENTITY_HEAD_ROTATION({ EntityHeadRotationS2CP(it) }), PLAY_SELECT_ADVANCEMENT_TAB({ PacketSelectAdvancementTab(it) }), PLAY_WORLD_BORDER({ WorldBorderS2CF.createPacket(it) }), diff --git a/src/main/java/de/bixilon/minosoft/terminal/commands/commands/CommandTabList.java b/src/main/java/de/bixilon/minosoft/terminal/commands/commands/CommandTabList.java index 787098edf..216a7dd48 100644 --- a/src/main/java/de/bixilon/minosoft/terminal/commands/commands/CommandTabList.java +++ b/src/main/java/de/bixilon/minosoft/terminal/commands/commands/CommandTabList.java @@ -31,7 +31,7 @@ public class CommandTabList extends Command { new CommandLiteralNode("list", (connection, stack) -> { print(connection.getTabList().getHeader().getAnsiColoredMessage()); - int entries = connection.getTabList().getTabListItems().size(); + int entries = connection.getTabList().getTabListItemsByUUID().size(); int columns = (entries / 20) + 1; if (columns > 4) { columns = 4; @@ -43,12 +43,12 @@ public class CommandTabList extends Command { ArrayList tableData = new ArrayList<>(); - Iterator playerListItems = connection.getTabList().getTabListItems().values().iterator(); + Iterator tabListItemIterator = connection.getTabList().getTabListItemsByUUID().values().iterator(); for (int row = 0; row < rows; row++) { ArrayList current = new ArrayList<>(); for (int column = 0; column < columns; column++) { - if (playerListItems.hasNext()) { - current.add(playerListItems.next().getDisplayName()); + if (tabListItemIterator.hasNext()) { + current.add(tabListItemIterator.next().getDisplayName()); } else { current.add(null); } @@ -68,7 +68,7 @@ public class CommandTabList extends Command { ArrayList tableData = new ArrayList<>(); - for (var entry : connection.getTabList().getTabListItems().entrySet()) { + for (var entry : connection.getTabList().getTabListItemsByUUID().entrySet()) { PlayerEntity playerEntity = (PlayerEntity) connection.getWorld().getEntities().get(entry.getKey()); Integer entityId = playerEntity != null ? connection.getWorld().getEntities().getId(playerEntity) : null; tableData.add(new Object[]{entry.getKey(), entityId, entry.getValue().getName(), entry.getValue().getDisplayName(), entry.getValue().getGamemode(), entry.getValue().getPing() + "ms"}); diff --git a/src/main/java/de/bixilon/minosoft/util/KUtil.kt b/src/main/java/de/bixilon/minosoft/util/KUtil.kt index b1cda1d98..bfcfe5d6d 100644 --- a/src/main/java/de/bixilon/minosoft/util/KUtil.kt +++ b/src/main/java/de/bixilon/minosoft/util/KUtil.kt @@ -409,4 +409,14 @@ object KUtil { fun ByteArray.toBase64(): String { return Base64.getEncoder().encodeToString(this) } + + + fun String?.nullCompare(other: String?): Int? { + (this ?: "").compareTo(other ?: "").let { + if (it != 0) { + return it + } + } + return null + } } diff --git a/src/main/java/de/bixilon/minosoft/util/logging/Log.kt b/src/main/java/de/bixilon/minosoft/util/logging/Log.kt index 9e47d9cc7..205d5f46e 100644 --- a/src/main/java/de/bixilon/minosoft/util/logging/Log.kt +++ b/src/main/java/de/bixilon/minosoft/util/logging/Log.kt @@ -18,6 +18,8 @@ import de.bixilon.minosoft.config.StaticConfiguration import de.bixilon.minosoft.data.text.BaseComponent import de.bixilon.minosoft.data.text.ChatComponent import de.bixilon.minosoft.data.text.TextComponent +import de.bixilon.minosoft.modding.event.events.InternalMessageReceiveEvent +import de.bixilon.minosoft.terminal.CLI import de.bixilon.minosoft.terminal.RunConfiguration import java.io.PrintStream import java.io.PrintWriter @@ -84,6 +86,9 @@ object Log { } stream.println(message.ansiColoredMessage) + + val cliConnection = CLI.getCurrentConnection() + cliConnection?.fireEvent(InternalMessageReceiveEvent(cliConnection, messageToSend.message)) } catch (exception: Throwable) { SYSTEM_ERR_STREAM.println("Can not send log message $messageToSend!") } diff --git a/src/main/resources/assets/minosoft/mapping/atlas.json b/src/main/resources/assets/minosoft/mapping/atlas.json index afa337b53..59f127c6f 100644 --- a/src/main/resources/assets/minosoft/mapping/atlas.json +++ b/src/main/resources/assets/minosoft/mapping/atlas.json @@ -64,6 +64,48 @@ "end": [16, 16] } }, + "minecraft:tab_list_ping_5": { + "0": { + "texture": "minecraft:textures/gui/icons.png", + "start": [0, 16], + "end": [10, 23] + } + }, + "minecraft:tab_list_ping_4": { + "0": { + "texture": "minecraft:textures/gui/icons.png", + "start": [0, 24], + "end": [10, 31] + } + }, + "minecraft:tab_list_ping_3": { + "0": { + "texture": "minecraft:textures/gui/icons.png", + "start": [0, 32], + "end": [10, 39] + } + }, + "minecraft:tab_list_ping_2": { + "0": { + "texture": "minecraft:textures/gui/icons.png", + "start": [0, 40], + "end": [10, 47] + } + }, + "minecraft:tab_list_ping_1": { + "0": { + "texture": "minecraft:textures/gui/icons.png", + "start": [0, 48], + "end": [10, 55] + } + }, + "minecraft:tab_list_ping_0": { + "0": { + "texture": "minecraft:textures/gui/icons.png", + "start": [0, 56], + "end": [10, 63] + } + }, "minecraft:experience_bar_empty": { "0": { "texture": "minecraft:textures/gui/icons.png",