wip: hud tab list

This commit is contained in:
Bixilon 2021-09-17 17:17:30 +02:00
parent aa7f5ca36e
commit c143c52e22
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
25 changed files with 514 additions and 30 deletions

View File

@ -19,4 +19,8 @@ class PlayerProperty(
) {
val isSigned: Boolean
get() = signature != null // ToDo check signature
override fun toString(): String {
return "$key: $value"
}
}

View File

@ -17,7 +17,8 @@ import de.bixilon.minosoft.data.text.ChatComponent
import java.util.*
class TabList {
val tabListItems: MutableMap<UUID, TabListItem> = mutableMapOf()
val tabListItemsByUUID: MutableMap<UUID, TabListItem> = mutableMapOf()
val tabListItemsByName: MutableMap<String, TabListItem> = mutableMapOf()
var header = ChatComponent.of("")
var footer = ChatComponent.of("")
}

View File

@ -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<String, PlayerProperty> = 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 }
}
}

View File

@ -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<String, PlayerProperty>? = null,
var remove: Boolean = false, // used for legacy tab list
var team: Team? = null,
var removeFromTeam: Boolean = false,
)

View File

@ -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<String>,
)
) {
override fun toString(): String {
return name
}
}

View File

@ -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

View File

@ -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) {

View File

@ -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
}
}

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.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() {

View File

@ -23,7 +23,7 @@ class HUDAtlasElement(
val end: Vec2i,
val slots: Map<Int, Vec2Binding<Int>>, // 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
}

View File

@ -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 <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.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<TabListEntryElement> = listOf()
val PING_BARS: Array<HUDAtlasElement> = 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<TabListEntryElement> = 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
}
}

View File

@ -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 <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.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
}
}

View File

@ -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 <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.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<TabListElement>(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<TabListInfoChangeEvent> {
layout.header.text = it.header
layout.footer.text = it.footer
})
}
companion object : HUDBuilder<TabListHUDElement> {
override val RESOURCE_LOCATION: ResourceLocation = "minosoft:tab_list".toResourceLocation()
override fun build(hudRenderer: HUDRenderer): TabListHUDElement {
return TabListHUDElement(hudRenderer)
}
}
}

View File

@ -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,

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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() {

View File

@ -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() {

View File

@ -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() {

View File

@ -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) }),

View File

@ -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<Object[]> tableData = new ArrayList<>();
Iterator<TabListItem> playerListItems = connection.getTabList().getTabListItems().values().iterator();
Iterator<TabListItem> tabListItemIterator = connection.getTabList().getTabListItemsByUUID().values().iterator();
for (int row = 0; row < rows; row++) {
ArrayList<Object> 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<Object[]> 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"});

View File

@ -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
}
}

View File

@ -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!")
}

View File

@ -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",