From bdfe30c49f6812f8c1b09ab3cc32cd64836f736e Mon Sep 17 00:00:00 2001 From: Cam Walter Date: Mon, 8 Jul 2024 22:03:47 -0600 Subject: [PATCH] TabList: Change api to be similar to Scoreboard This allows for mutating the tab list entries. --- api/ctjs.api | 25 ++ docs/MIGRATION.md | 5 + .../ClientPlayNetworkHandlerAccessor.java | 16 + .../mixins/MinecraftClientAccessor.java | 12 + .../internal/mixins/MinecraftClientMixin.java | 3 + .../mixins/PlayerListEntryAccessor.java | 11 + .../internal/mixins/PlayerListHudMixin.java | 16 + .../chattriggers/ctjs/api/world/Scoreboard.kt | 2 +- .../chattriggers/ctjs/api/world/TabList.kt | 371 +++++++++++++++--- .../ctjs/internal/listeners/ClientListener.kt | 2 + src/main/resources/ctjs.mixins.json | 3 + 11 files changed, 420 insertions(+), 46 deletions(-) create mode 100644 src/main/java/com/chattriggers/ctjs/internal/mixins/ClientPlayNetworkHandlerAccessor.java create mode 100644 src/main/java/com/chattriggers/ctjs/internal/mixins/MinecraftClientAccessor.java create mode 100644 src/main/java/com/chattriggers/ctjs/internal/mixins/PlayerListEntryAccessor.java diff --git a/api/ctjs.api b/api/ctjs.api index 435b9332..e74e34cd 100644 --- a/api/ctjs.api +++ b/api/ctjs.api @@ -2370,6 +2370,12 @@ public final class com/chattriggers/ctjs/api/world/Server { public final class com/chattriggers/ctjs/api/world/TabList { public static final field INSTANCE Lcom/chattriggers/ctjs/api/world/TabList; + public static final fun addName (Lcom/chattriggers/ctjs/api/message/TextComponent;)V + public static final fun addName (Lcom/chattriggers/ctjs/api/message/TextComponent;Z)V + public static final fun addName (Ljava/lang/String;)V + public static final fun addName (Ljava/lang/String;Z)V + public static synthetic fun addName$default (Lcom/chattriggers/ctjs/api/message/TextComponent;ZILjava/lang/Object;)V + public static synthetic fun addName$default (Ljava/lang/String;ZILjava/lang/Object;)V public static final fun clearFooter ()V public static final fun clearHeader ()V public static final fun getFooter ()Ljava/lang/String; @@ -2378,12 +2384,31 @@ public final class com/chattriggers/ctjs/api/world/TabList { public static final fun getHeaderComponent ()Lcom/chattriggers/ctjs/api/message/TextComponent; public static final fun getNames ()Ljava/util/List; public static final fun getNamesByObjectives ()Ljava/util/List; + public static final fun getObjective ()Lnet/minecraft/scoreboard/ScoreboardObjective; public static final fun getUnformattedNames ()Ljava/util/List; + public static final fun removeNames (Lcom/chattriggers/ctjs/api/message/TextComponent;)V + public static final fun removeNames (Ljava/lang/String;)V public static final fun setFooter (Ljava/lang/Object;)V public static final fun setHeader (Ljava/lang/Object;)V public static final fun toMC ()Lnet/minecraft/client/gui/hud/PlayerListHud; } +public final class com/chattriggers/ctjs/api/world/TabList$Name : com/chattriggers/ctjs/api/CTWrapper { + public fun (Lnet/minecraft/client/network/PlayerListEntry;)V + public final fun getLatency ()I + public synthetic fun getMcValue ()Ljava/lang/Object; + public fun getMcValue ()Lnet/minecraft/client/network/PlayerListEntry; + public final fun getName ()Lcom/chattriggers/ctjs/api/message/TextComponent; + public final fun getTeam ()Lcom/chattriggers/ctjs/api/entity/Team; + public final fun remove ()V + public final fun setLatency (I)Lcom/chattriggers/ctjs/api/world/TabList$Name; + public final fun setName (Lcom/chattriggers/ctjs/api/message/TextComponent;)Lcom/chattriggers/ctjs/api/world/TabList$Name; + public final fun setTeam (Lcom/chattriggers/ctjs/api/entity/Team;)Lcom/chattriggers/ctjs/api/world/TabList$Name; + public synthetic fun toMC ()Ljava/lang/Object; + public fun toMC ()Lnet/minecraft/client/network/PlayerListEntry; + public fun toString ()Ljava/lang/String; +} + public final class com/chattriggers/ctjs/api/world/World { public static final field INSTANCE Lcom/chattriggers/ctjs/api/world/World; public static final field border Lcom/chattriggers/ctjs/api/world/World$BorderWrapper; diff --git a/docs/MIGRATION.md b/docs/MIGRATION.md index 5c743f32..9c207445 100644 --- a/docs/MIGRATION.md +++ b/docs/MIGRATION.md @@ -199,6 +199,11 @@ Here is a list of targeted changes for various different APIs: - `TabList` - Renamed `getHeaderMessage()` to `getHeaderComponent()`, and it now returns a `TextComponent` instead of a `Message` - Renamed `getFooterMessage()` to `getFooterComponent()`, and it now returns a `TextComponent` instead of a `Message` + - Added `addName()`, `getList()`, and `removeNames()` + - `getNames()` now returns a list of `Name` + - Added `Name` + - acts similarly to `Scoreboard.Score`, with the following methods + - `getLatency()`, `setLatency()`, `getName()`, `setName()`, `getTeam()`, `setTeam()`, and `remove()` - `Team` - `Team.getNameTagVisibility()` and `Team.getDeathMessageVisibility()` now return a `Team.Visibility` instead of a string - Added `setColor()` diff --git a/src/main/java/com/chattriggers/ctjs/internal/mixins/ClientPlayNetworkHandlerAccessor.java b/src/main/java/com/chattriggers/ctjs/internal/mixins/ClientPlayNetworkHandlerAccessor.java new file mode 100644 index 00000000..90e010ff --- /dev/null +++ b/src/main/java/com/chattriggers/ctjs/internal/mixins/ClientPlayNetworkHandlerAccessor.java @@ -0,0 +1,16 @@ +package com.chattriggers.ctjs.internal.mixins; + +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.client.network.PlayerListEntry; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.Collection; +import java.util.Map; +import java.util.UUID; + +@Mixin(ClientPlayNetworkHandler.class) +public interface ClientPlayNetworkHandlerAccessor { + @Accessor + Map getPlayerListEntries(); +} diff --git a/src/main/java/com/chattriggers/ctjs/internal/mixins/MinecraftClientAccessor.java b/src/main/java/com/chattriggers/ctjs/internal/mixins/MinecraftClientAccessor.java new file mode 100644 index 00000000..cdd03cc4 --- /dev/null +++ b/src/main/java/com/chattriggers/ctjs/internal/mixins/MinecraftClientAccessor.java @@ -0,0 +1,12 @@ +package com.chattriggers.ctjs.internal.mixins; + +import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; +import net.minecraft.client.MinecraftClient; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(MinecraftClient.class) +public interface MinecraftClientAccessor { + @Accessor + YggdrasilAuthenticationService getAuthenticationService(); +} diff --git a/src/main/java/com/chattriggers/ctjs/internal/mixins/MinecraftClientMixin.java b/src/main/java/com/chattriggers/ctjs/internal/mixins/MinecraftClientMixin.java index 03bbdc66..014ddfd0 100644 --- a/src/main/java/com/chattriggers/ctjs/internal/mixins/MinecraftClientMixin.java +++ b/src/main/java/com/chattriggers/ctjs/internal/mixins/MinecraftClientMixin.java @@ -1,6 +1,7 @@ package com.chattriggers.ctjs.internal.mixins; import com.chattriggers.ctjs.api.world.Scoreboard; +import com.chattriggers.ctjs.api.world.TabList; import com.chattriggers.ctjs.internal.engine.CTEvents; import com.chattriggers.ctjs.api.triggers.TriggerType; import com.chattriggers.ctjs.internal.engine.module.ModuleManager; @@ -34,6 +35,7 @@ private void injectWorldUnload(ClientWorld world, DownloadingTerrainScreen.World if (this.world != null) { TriggerType.WORLD_UNLOAD.triggerAll(); Scoreboard.INSTANCE.clearCustom$ctjs(); + TabList.INSTANCE.clearCustom$ctjs(); } } @@ -51,6 +53,7 @@ private void injectDisconnect(Screen screen, CallbackInfo ci) { TriggerType.WORLD_UNLOAD.triggerAll(); TriggerType.SERVER_DISCONNECT.triggerAll(); Scoreboard.INSTANCE.clearCustom$ctjs(); + TabList.INSTANCE.clearCustom$ctjs(); } } diff --git a/src/main/java/com/chattriggers/ctjs/internal/mixins/PlayerListEntryAccessor.java b/src/main/java/com/chattriggers/ctjs/internal/mixins/PlayerListEntryAccessor.java new file mode 100644 index 00000000..1e23edcd --- /dev/null +++ b/src/main/java/com/chattriggers/ctjs/internal/mixins/PlayerListEntryAccessor.java @@ -0,0 +1,11 @@ +package com.chattriggers.ctjs.internal.mixins; + +import net.minecraft.client.network.PlayerListEntry; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(PlayerListEntry.class) +public interface PlayerListEntryAccessor { + @Invoker + void invokeSetLatency(int latency); +} diff --git a/src/main/java/com/chattriggers/ctjs/internal/mixins/PlayerListHudMixin.java b/src/main/java/com/chattriggers/ctjs/internal/mixins/PlayerListHudMixin.java index e0cde8b1..521e2963 100644 --- a/src/main/java/com/chattriggers/ctjs/internal/mixins/PlayerListHudMixin.java +++ b/src/main/java/com/chattriggers/ctjs/internal/mixins/PlayerListHudMixin.java @@ -1,10 +1,12 @@ package com.chattriggers.ctjs.internal.mixins; import com.chattriggers.ctjs.api.triggers.TriggerType; +import com.chattriggers.ctjs.api.world.TabList; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.hud.PlayerListHud; import net.minecraft.scoreboard.Scoreboard; import net.minecraft.scoreboard.ScoreboardObjective; +import net.minecraft.text.Text; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -16,4 +18,18 @@ public class PlayerListHudMixin { private void injectRenderPlayerList(DrawContext context, int scaledWindowWidth, Scoreboard scoreboard, ScoreboardObjective objective, CallbackInfo ci) { TriggerType.RENDER_PLAYER_LIST.triggerAll(ci); } + + @Inject(method = "setHeader", at = @At("HEAD"), cancellable = true) + private void ctjs$keepCustomHeader(Text header, CallbackInfo ci) { + if (TabList.INSTANCE.getCustomHeader$ctjs()) { + ci.cancel(); + } + } + + @Inject(method = "setFooter", at = @At("HEAD"), cancellable = true) + private void ctjs$keepCustomFooter(Text footer, CallbackInfo ci) { + if (TabList.INSTANCE.getCustomFooter$ctjs()) { + ci.cancel(); + } + } } diff --git a/src/main/kotlin/com/chattriggers/ctjs/api/world/Scoreboard.kt b/src/main/kotlin/com/chattriggers/ctjs/api/world/Scoreboard.kt index be866833..d8c57188 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/api/world/Scoreboard.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/api/world/Scoreboard.kt @@ -289,7 +289,7 @@ object Scoreboard { } /** - * Gets the display string of this score + * Gets the display text of this score * * @return the display name */ diff --git a/src/main/kotlin/com/chattriggers/ctjs/api/world/TabList.kt b/src/main/kotlin/com/chattriggers/ctjs/api/world/TabList.kt index f8c0ae1a..9f000c9a 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/api/world/TabList.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/api/world/TabList.kt @@ -1,71 +1,66 @@ package com.chattriggers.ctjs.api.world import com.chattriggers.ctjs.MCTeam +import com.chattriggers.ctjs.api.CTWrapper import com.chattriggers.ctjs.api.client.Client import com.chattriggers.ctjs.api.client.Player +import com.chattriggers.ctjs.api.entity.Team import com.chattriggers.ctjs.api.message.TextComponent +import com.chattriggers.ctjs.internal.mixins.ClientPlayNetworkHandlerAccessor +import com.chattriggers.ctjs.internal.mixins.MinecraftClientAccessor +import com.chattriggers.ctjs.internal.mixins.PlayerListEntryAccessor import com.chattriggers.ctjs.internal.mixins.PlayerListHudAccessor import com.chattriggers.ctjs.internal.utils.asMixin import com.google.common.collect.ComparisonChain import com.google.common.collect.Ordering +import com.mojang.authlib.GameProfile +import gg.essential.elementa.state.BasicState import net.minecraft.client.network.PlayerListEntry import net.minecraft.scoreboard.ScoreboardDisplaySlot +import net.minecraft.scoreboard.ScoreboardObjective import net.minecraft.text.Text +import net.minecraft.util.ApiServices import net.minecraft.world.GameMode +import java.util.* object TabList { + private var needsUpdate = true + private var tabListNames = mutableListOf() private val playerComparator = Ordering.from(PlayerComparator()) + internal var customHeader = false + internal var customFooter = false + private var tabListHeader: TextComponent? = null + private var tabListFooter: TextComponent? = null @JvmStatic fun toMC() = Client.getTabGui() /** - * Gets names set in scoreboard objectives - * - * @return The formatted names + * Gets the scoreboard objective corresponding to the tab list, or null if it doesn't exist */ @JvmStatic - fun getNamesByObjectives(): List { - val scoreboard = Scoreboard.toMC() ?: return emptyList() - val sidebarObjective = - scoreboard.getObjectiveForSlot(ScoreboardDisplaySlot.LIST) ?: return emptyList() - - val scores = scoreboard.getScoreboardEntries(sidebarObjective) - - return scores.map { - val team = scoreboard.getTeam(it.owner) - TextComponent(MCTeam.decorateName(team, TextComponent(it.owner))).formattedText - } - } - - @JvmStatic - fun getNames(): List { - if (toMC() == null) return listOf() - - return playerComparator - .sortedCopy(Player.toMC()!!.networkHandler.playerList) - .map { TextComponent(toMC()!!.getPlayerName(it)).formattedText } - } + fun getObjective(): ScoreboardObjective? = Scoreboard.toMC()?.getObjectiveForSlot(ScoreboardDisplaySlot.LIST) /** - * Gets all names in tabs without formatting + * Gets the tab list header as a [TextComponent] * - * @return the unformatted names + * @return the header */ @JvmStatic - fun getUnformattedNames(): List { - if (Player.toMC() == null) return listOf() + fun getHeaderComponent(): TextComponent? { + if (needsUpdate) { + updateNames() + needsUpdate = false + } - return Client.getConnection()?.playerList?.let { - playerComparator.sortedCopy(it) - }?.map { - it.profile.name - } ?: emptyList() + return tabListHeader } - @JvmStatic - fun getHeaderComponent() = toMC()?.asMixin()?.header?.let { TextComponent(it) } - + /** + * Gets the tab list header as a formatted string. + * + * @return the header + */ @JvmStatic fun getHeader() = getHeaderComponent()?.formattedText @@ -77,20 +72,44 @@ object TabList { */ @JvmStatic fun setHeader(header: Any?) { + customHeader = false when (header) { - is CharSequence -> toMC()?.setHeader(TextComponent(header)) - is TextComponent -> toMC()?.setHeader(header) - is Text -> toMC()?.setHeader(header) - null -> toMC()?.setHeader(null) + is TextComponent? -> { + tabListHeader = header + toMC()?.setHeader(header) + } + is CharSequence, is Text -> { + tabListHeader = TextComponent(header) + toMC()?.setHeader(tabListHeader) + } } + customHeader = true } @JvmStatic fun clearHeader() = setHeader(null) + /** + * Gets the tab list footer as a [TextComponent] + * + * @return the footer + */ @JvmStatic - fun getFooterComponent() = toMC()?.asMixin()?.footer?.let { TextComponent(it) } + fun getFooterComponent(): TextComponent? { + if (needsUpdate) { + updateNames() + needsUpdate = false + } + return tabListFooter + } + + /** + * Gets the tab list footer as a string. + * Be aware that this can contain color codes. + * + * @return the footer + */ @JvmStatic fun getFooter() = getFooterComponent()?.formattedText @@ -102,17 +121,279 @@ object TabList { */ @JvmStatic fun setFooter(footer: Any?) { + customFooter = false when (footer) { - is CharSequence -> toMC()?.setFooter(TextComponent(footer)) - is TextComponent -> toMC()?.setFooter(footer) - is Text -> toMC()?.setFooter(footer) - null -> toMC()?.setFooter(null) + is TextComponent? -> { + tabListHeader = footer + toMC()?.setFooter(footer) + } + is CharSequence, is Text -> { + tabListHeader = TextComponent(footer) + toMC()?.setFooter(tabListHeader) + } } + customFooter = true } @JvmStatic fun clearFooter() = setFooter(null) + /** + * Gets names set in scoreboard objectives + * + * @return The formatted names + */ + @JvmStatic + fun getNamesByObjectives(): List { + val scoreboard = Scoreboard.toMC() ?: return emptyList() + val tabListObjective = getObjective() ?: return emptyList() + + val scores = scoreboard.getScoreboardEntries(tabListObjective) + + return scores.map { + val team = scoreboard.getTeam(it.owner) + TextComponent(MCTeam.decorateName(team, TextComponent(it.owner))).formattedText + } + } + + /** + * Get all names on the tab list + * + * @return the list of names + */ + @JvmStatic + fun getNames(): List { + if (needsUpdate) { + updateNames() + needsUpdate = false + } + + return tabListNames + } + + /** + * Gets all names in tabs without formatting + * + * @return the unformatted names + */ + @JvmStatic + fun getUnformattedNames(): List { + if (needsUpdate) { + updateNames() + needsUpdate = false + } + + return tabListNames.map { it.toMC().profile.name } + } + + /** + * Adds a new name to the tab list + * + * @param name the formatted name to add + * @param useExistingSkin whether to use the skin of the associated Minecraft account using [name]. + * If false, will use a random default skin (Steve, Alex, etc) + */ + @JvmStatic + @JvmOverloads + fun addName(name: TextComponent, useExistingSkin: Boolean = true) { + val connection = Client.getConnection() ?: return + val listedPlayerListEntries = connection.listedPlayerListEntries + val playerListEntries = connection.asMixin().playerListEntries + + val username = name.unformattedText + + val uuid = UUID.randomUUID() + val fakeEntry = PlayerListEntry(GameProfile(uuid, name.unformattedText), false) + fakeEntry.displayName = name + + listedPlayerListEntries += fakeEntry + playerListEntries[uuid] = fakeEntry + + if (!useExistingSkin) { + updateNames() + return + } + + val mc = Client.getMinecraft() + val apiServices = + ApiServices.create(mc.asMixin().authenticationService, mc.runDirectory) + apiServices.userCache.setExecutor(mc) + + apiServices.userCache.findByNameAsync(username).thenAcceptAsync { + if (it.isPresent) { + val result = apiServices.sessionService.fetchProfile(it.get().id, true) ?: return@thenAcceptAsync + + val entry = PlayerListEntry(result.profile, true) + entry.displayName = name + + listedPlayerListEntries += entry + playerListEntries[result.profile.id] = entry + + listedPlayerListEntries -= fakeEntry + playerListEntries.remove(uuid) + + updateNames() + } + } + } + + @JvmStatic + @JvmOverloads + fun addName(name: String, useExistingSkin: Boolean = true) = addName(TextComponent(name), useExistingSkin) + + /** + * Removes all names from the tab list with a certain name + * + * @param name the name of the entry to remove + */ + @JvmStatic + fun removeNames(name: TextComponent) { + tabListNames.filter { + it.getName().style == name.style && it.getName().string == name.string + }.forEach(Name::remove) + } + + @JvmStatic + fun removeNames(name: String) { + tabListNames.filter { + it.getName().string == name + }.forEach(Name::remove) + } + + private fun updateNames() { + tabListNames.clear() + + if (!customHeader) + tabListHeader = null + + if (!customFooter) + tabListFooter = null + + val hud = toMC()?.asMixin() ?: return + val player = Player.toMC() ?: return + + if (!customHeader) + tabListHeader = hud.header?.let { TextComponent(it) } + + if (!customFooter) + tabListFooter = hud.footer?.let { TextComponent(it) } + + tabListNames = playerComparator + .sortedCopy(player.networkHandler.playerList) + .mapTo(mutableListOf(), ::Name) + } + + internal fun resetCache() { + needsUpdate = true + } + + internal fun clearCustom() { + tabListNames.clear() + customHeader = false + customFooter = false + tabListHeader = null + tabListFooter = null + } + + class Name(override val mcValue: PlayerListEntry) : CTWrapper { + private val latencyState = BasicState(mcValue.latency) + private val teamState = BasicState(mcValue.scoreboardTeam) + private val nameState = BasicState(mcValue.displayName) + + /** + * Gets the latency associated with this name + * + * @return the latency + */ + fun getLatency(): Int = latencyState.get() + + /** + * Sets the latency associated with this name. + * - latency between 0 and 149 represents all 5 bars + * - latency between 150 and 299 represents 4 bars + * - latency between 300 and 599 represents 3 bars + * - latency between 600 and 999 represents 2 bars + * - latency between 1000 and more represents 1 bar + * + * @param latency the latency to set + * @return the name to allow for method chaining + */ + fun setLatency(latency: Int) = apply { + latencyState.set(latency) + mcValue.asMixin().invokeSetLatency(latency) + } + + /** + * Gets the team associated with this name, if it exists + * + * @return the team, or null if it does not exist + */ + fun getTeam(): Team? = teamState.get()?.let(::Team) + + /** + * Sets the team associated with this name + * + * @param team the new team to set for this name. Custom teams can be created + * using [Scoreboard.createTeam] + * @return the score to allow for method chaining + */ + fun setTeam(team: Team?) = apply { + val scoreboard = Scoreboard.toMC()!! + val name = mcValue.profile.name + + if (team == null) { + scoreboard.clearTeam(name) + } else { + scoreboard.addScoreHolderToTeam(name, team.toMC()) + } + + teamState.set(team?.toMC()) + } + + /** + * Gets the display text of this name + * + * @return the display name + */ + fun getName(): TextComponent { + val name = mcValue.profile.name + + return TextComponent( + MCTeam.decorateName( + getTeam()?.mcValue, + TextComponent(nameState.get() ?: name), + ) + ) + } + + /** + * Sets the display name of this name + * + * @param name the new name + * @return the name to allow for method chaining + */ + fun setName(name: TextComponent?) = apply { + nameState.set(name) + mcValue.displayName = name + } + + /** + * Removes this name from the tab list + */ + fun remove() { + val connection = Client.getConnection() ?: return + val listedPlayerListEntries = connection.listedPlayerListEntries + val playerListEntries = connection.asMixin().playerListEntries + + listedPlayerListEntries.remove(mcValue) + playerListEntries.remove(mcValue.profile.id) + + updateNames() + } + + override fun toString(): String = getName().formattedText + } + internal class PlayerComparator internal constructor() : Comparator { override fun compare(playerOne: PlayerListEntry, playerTwo: PlayerListEntry): Int { val teamOne = playerOne.scoreboardTeam diff --git a/src/main/kotlin/com/chattriggers/ctjs/internal/listeners/ClientListener.kt b/src/main/kotlin/com/chattriggers/ctjs/internal/listeners/ClientListener.kt index 72ee569d..c54fc4d3 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/internal/listeners/ClientListener.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/internal/listeners/ClientListener.kt @@ -10,6 +10,7 @@ import com.chattriggers.ctjs.api.triggers.CancellableEvent import com.chattriggers.ctjs.api.triggers.ChatTrigger import com.chattriggers.ctjs.api.triggers.TriggerType import com.chattriggers.ctjs.api.world.Scoreboard +import com.chattriggers.ctjs.api.world.TabList import com.chattriggers.ctjs.api.world.World import com.chattriggers.ctjs.api.world.block.BlockFace import com.chattriggers.ctjs.api.world.block.BlockPos @@ -66,6 +67,7 @@ object ClientListener : Initializer { ticksPassed++ Scoreboard.resetCache() + TabList.resetCache() } } diff --git a/src/main/resources/ctjs.mixins.json b/src/main/resources/ctjs.mixins.json index 6cf6aa4d..e0e9f916 100644 --- a/src/main/resources/ctjs.mixins.json +++ b/src/main/resources/ctjs.mixins.json @@ -16,6 +16,7 @@ "ClientChunkMapAccessor", "ClientPlayerEntityMixin", "ClientPlayerInteractionManagerMixin", + "ClientPlayNetworkHandlerAccessor", "ClientWorldAccessor", "CreativeInventoryScreenMixin", "EntityRenderDispatcherAccessor", @@ -30,6 +31,7 @@ "MouseMixin", "ParticleAccessor", "ParticleManagerMixin", + "PlayerListEntryAccessor", "PlayerListHudAccessor", "PlayerListHudMixin", "RenderTickCounterMixin", @@ -50,6 +52,7 @@ "ClientConnectionMixin", "ItemStackMixin", "LivingEntityMixin", + "MinecraftClientAccessor", "NbtCompoundAccessor", "PlayerEntityMixin", "PlayerScreenHandlerMixin",