Skip to content

Commit

Permalink
feat: Better Tab (CCBlueX#5077)
Browse files Browse the repository at this point in the history
Features (in tablist):
* More limits control:
  * 1 <= TabSize <= 1000
  * 1 <= ColumnHeight <= 1000
* Highlight  (with color customization)
  * Self
  * Friends
* Accurate latency (numerical latency)
* Visibility
  * Header
  * Footer
* Sorting
  * Vanilla
  * Ping (less -> more)
  * NameLength (min -> max)
  * Alphabetical
  * ReverseAlphabetical
  * None
  • Loading branch information
sqlerrorthing authored and commandblock2 committed Dec 31, 2024
1 parent 496e6be commit 2821033
Show file tree
Hide file tree
Showing 11 changed files with 245 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,180 @@
*/
package net.ccbluex.liquidbounce.injection.mixins.minecraft.gui;

import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import com.llamalad7.mixinextras.sugar.ref.LocalIntRef;
import net.ccbluex.liquidbounce.features.misc.FriendManager;
import net.ccbluex.liquidbounce.features.module.modules.misc.ModuleAntiStaff;
import net.ccbluex.liquidbounce.features.module.modules.misc.ModuleBetterTab;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.hud.PlayerListHud;
import net.minecraft.client.network.PlayerListEntry;
import net.minecraft.text.Text;
import net.minecraft.util.Colors;
import net.minecraft.util.math.MathHelper;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.*;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.invoke.arg.Args;

import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;

@Mixin(PlayerListHud.class)
public class MixinPlayerListHud {
public abstract class MixinPlayerListHud {

@Shadow
protected abstract List<PlayerListEntry> collectPlayerEntries();

@ModifyConstant(constant = @Constant(longValue = 80L), method = "collectPlayerEntries")
private long hookTabSize(long count) {
return ModuleBetterTab.INSTANCE.getRunning() ?
ModuleBetterTab.Limits.INSTANCE.getTabSize() : count;
}

@WrapOperation(method = "collectPlayerEntries", at = @At(value = "INVOKE", target = "Ljava/util/stream/Stream;sorted(Ljava/util/Comparator;)Ljava/util/stream/Stream;"))
private Stream<PlayerListEntry> hookSort(Stream<PlayerListEntry> instance, Comparator<PlayerListEntry> defaultComparator, Operation<Stream<PlayerListEntry>> original) {
var sorting = ModuleBetterTab.INSTANCE.getSorting();

boolean running = ModuleBetterTab.INSTANCE.getRunning();
var customComparator = sorting.getComparator();

var comparator = running
? (customComparator != null ? customComparator : defaultComparator)
: defaultComparator;

return original.call(instance, comparator);
}

@ModifyExpressionValue(method = "render", at = @At(
value = "FIELD",
target = "Lnet/minecraft/client/gui/hud/PlayerListHud;header:Lnet/minecraft/text/Text;",
ordinal = 0
))
private Text hookHeader(Text original) {
if (!ModuleBetterTab.INSTANCE.getRunning()) {
return original;
}

return ModuleBetterTab.Visibility.INSTANCE.getHeader() ?
original : null;
}

@ModifyExpressionValue(method = "render", at = @At(
value = "FIELD",
target = "Lnet/minecraft/client/gui/hud/PlayerListHud;footer:Lnet/minecraft/text/Text;",
ordinal = 0
))
private Text hookFooter(Text original) {
if (!ModuleBetterTab.INSTANCE.getRunning()) {
return original;
}

return ModuleBetterTab.Visibility.INSTANCE.getFooter() ?
original : null;
}

@ModifyExpressionValue(method = "render", at = @At(
value = "FIELD",
target = "Lnet/minecraft/client/gui/hud/PlayerListHud$ScoreDisplayEntry;name:Lnet/minecraft/text/Text;"
))
private Text hookVisibilityName(Text original, @Local(ordinal = 0) PlayerListEntry entry) {
if (!ModuleBetterTab.INSTANCE.getRunning()) {
return original;
}

return ModuleBetterTab.Visibility.INSTANCE.getNameOnly() ?
Text.of(entry.getProfile().getName()) : original;

}

@ModifyExpressionValue(method = "render", at = @At(
value = "INVOKE",
target = "Lnet/minecraft/client/gui/hud/PlayerListHud;getPlayerName(Lnet/minecraft/client/network/PlayerListEntry;)Lnet/minecraft/text/Text;"
))
private Text hookWidthVisibilityName(Text original, @Local(ordinal = 0) PlayerListEntry entry) {
if (!ModuleBetterTab.INSTANCE.getRunning()) {
return original;
}

return ModuleBetterTab.Visibility.INSTANCE.getNameOnly() ?
Text.of(entry.getProfile().getName()) : original;
}

@Inject(method = "render", at = @At(value = "INVOKE", target = "Ljava/lang/Math;min(II)I", shift = At.Shift.BEFORE))
private void hookTabColumnHeight(CallbackInfo ci, @Local(ordinal = 5) LocalIntRef o, @Local(ordinal = 6)LocalIntRef p) {
if (!ModuleBetterTab.INSTANCE.getRunning()) {
return;
}

int totalPlayers = collectPlayerEntries().size();
int columns = 1;
int rows = totalPlayers;

while (rows > ModuleBetterTab.Limits.INSTANCE.getHeight()) {
columns++;
rows = (totalPlayers + columns - 1) / columns;
}

o.set(rows);
p.set(columns);
}

@ModifyArg(method = "render", at = @At(value = "INVOKE", target = "Ljava/lang/Math;min(II)I"), index = 0)
private int hookWidth(int width) {
return ModuleBetterTab.INSTANCE.getRunning() && ModuleBetterTab.AccurateLatency.INSTANCE.getRunning() ? width + 30 : width;
}

@Inject(method = "renderLatencyIcon", at = @At("HEAD"), cancellable = true)
private void hookOnRenderLatencyIcon(DrawContext context, int width, int x, int y, PlayerListEntry entry, CallbackInfo ci) {
var accurateLatency = ModuleBetterTab.AccurateLatency.INSTANCE;
if (ModuleBetterTab.INSTANCE.getRunning() && accurateLatency.getRunning()) {
TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;

int latency = MathHelper.clamp(entry.getLatency(), 0, 9999);
int color = latency < 150 ? 0x00E970 : latency < 300 ? 0xE7D020 : 0xD74238;
String text = latency + (accurateLatency.getSuffix() ? "ms" : "");
context.drawTextWithShadow(textRenderer, text, x + width - textRenderer.getWidth(text), y, color);
ci.cancel();
}
}

@ModifyArgs(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;fill(IIIII)V", ordinal = 2))
private void hookRenderPlayerBackground(Args args, @Local(ordinal = 13) int w, @Local(ordinal = 0) List<PlayerListEntry> entries) {
if (!ModuleBetterTab.INSTANCE.getRunning()) {
return;
}

var highlight = ModuleBetterTab.Highlight.INSTANCE;
if (!highlight.getRunning()) {
return;
}

if (w < entries.size()) {
var entry = entries.get(w);
if (highlight.getSelf().getRunning()) {
if (Objects.equals(entry.getProfile().getName(), MinecraftClient.getInstance().player.getGameProfile().getName())) {
args.set(4, highlight.getSelf().getColor().toARGB());
return;
}
}

if (highlight.getFriends().getRunning()) {
if (FriendManager.INSTANCE.isFriend(entry.getProfile().getName())) {
args.set(4, highlight.getFriends().getColor().toARGB());
}
}
}
}

@ModifyReturnValue(method = "getPlayerName", at = @At("RETURN"))
private Text modifyPlayerName(Text original, PlayerListEntry entry) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ object ModuleManager : EventListener, Iterable<ClientModule> by modules {

// Misc
ModuleAntiBot,
ModuleBetterTab,
ModuleBetterChat,
ModuleMiddleClickAction,
ModuleInventoryTracker,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package net.ccbluex.liquidbounce.features.module.modules.misc

import net.ccbluex.liquidbounce.config.types.Configurable
import net.ccbluex.liquidbounce.config.types.NamedChoice
import net.ccbluex.liquidbounce.config.types.ToggleableConfigurable
import net.ccbluex.liquidbounce.features.module.Category
import net.ccbluex.liquidbounce.features.module.ClientModule
import net.ccbluex.liquidbounce.render.engine.Color4b
import net.minecraft.client.network.PlayerListEntry
import net.minecraft.text.Text

/**
* ModuleBetterTab
*
* @author sqlerrorthing
* @since 12/28/2024
**/
object ModuleBetterTab : ClientModule("BetterTab", Category.MISC) {

init {
treeAll(
Limits,
Visibility,
Highlight,
AccurateLatency,
)
}

val sorting by enumChoice("Sorting", Sorting.VANILLA)

object Limits : Configurable("Limits") {
val tabSize by int("TabSize", 80, 1..1000)
val height by int("ColumnHeight", 20, 1..100)
}

object Visibility : Configurable("Visibility") {
val header by boolean("Header", true)
val footer by boolean("Footer", true)
val nameOnly by boolean("NameOnly", false)
}

object Highlight : ToggleableConfigurable(ModuleBetterTab, "Highlight", true) {
class HighlightColored(name: String, color: Color4b) : ToggleableConfigurable(this, name, true) {
val color by color("Color", color)
}

val self = tree(HighlightColored("Self", Color4b(50, 193, 50, 80)))
val friends = tree(HighlightColored("Friends", Color4b(16, 89, 203, 80)))
}

object AccurateLatency : ToggleableConfigurable(ModuleBetterTab, "AccurateLatency", true) {
val suffix by boolean("AppendMSSuffix", true)
}
}

@Suppress("unused")
enum class Sorting(
override val choiceName: String,
val comparator: Comparator<PlayerListEntry>?
) : NamedChoice {
VANILLA("Vanilla", null),
PING("Ping", Comparator.comparingInt { it.latency }),
LENGTH("NameLength", Comparator.comparingInt { it.profile.name.length }),
SCORE_LENGTH("DisplayNameLength", Comparator.comparingInt { (it.displayName ?: Text.empty()).string.length }),
ALPHABETICAL("Alphabetical", Comparator.comparing { it.profile.name }),
REVERSE_ALPHABETICAL("ReverseAlphabetical", Comparator.comparing({ it.profile.name }, Comparator.reverseOrder())),
NONE("None", { _, _ -> 0 })
}


1 change: 1 addition & 0 deletions src/main/resources/assets/liquidbounce/lang/de_de.json
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,7 @@
"liquidbounce.module.autoPearl.description": "Zielt und wirft automatisch eine Enderperle auf die Flugbahn der feindlichen Enderperle.",
"liquidbounce.module.holeFiller.description": "Füllt automatisch sichere Orte mit Blöcken für Crystal PvP.",
"liquidbounce.module.itemChams.description": "Wendet visuelle Effekte auf Gegenstände in deiner Hand an.",
"liquidbounce.module.betterTab.description": "Verschiedene Verbesserungen an der Tab-Liste.",
"liquidbounce.module.itemTags.description": "Zeigt Bilder und Mengenangaben fallengelassener Gegenstände an.",
"liquidbounce.module.baritone.description": "Einstellungen für Baritone."
}
1 change: 1 addition & 0 deletions src/main/resources/assets/liquidbounce/lang/en_pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -434,5 +434,6 @@
"liquidbounce.command.script.subcommand.edit.result.opened": "Opened script named %s.",
"liquidbounce.liquidchat.authenticationFailed": "Authentication failed! LiquidChat requires Minecraft premium account.",
"liquidbounce.module.autoBalls.description": "Automatically throws snowballs at your enemies.",
"liquidbounce.module.betterTab.description": "Multiple enhancements to the tab list.",
"liquidbounce.module.autoPearl.description": "Mira e lança uma pérola na trajetória da pérola inimiga."
}
1 change: 1 addition & 0 deletions src/main/resources/assets/liquidbounce/lang/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -618,5 +618,6 @@
"liquidbounce.module.packetMine.description": "Allows you to mine blocks by clicking them once.",
"liquidbounce.module.fastExp.description": "Automatically repairs your armor.",
"liquidbounce.module.itemChams.description": "Applies visual effects to your held items.",
"liquidbounce.module.betterTab.description": "Multiple improvements to the tab list.",
"liquidbounce.module.autoPearl.description": "Aims and throws a pearl at an enemies pearl trajectory."
}
3 changes: 2 additions & 1 deletion src/main/resources/assets/liquidbounce/lang/ja_jp.json
Original file line number Diff line number Diff line change
Expand Up @@ -500,5 +500,6 @@
"liquidbounce.module.xRay.description": "選択したブロックのみをレンダリングします。",
"liquidbounce.module.zoot.description": "すべての悪い効果/火を取り除きます。",
"modmenu.descriptionTranslation.liquidbounce": "無料でオープンソースのハッククライアント",
"liquidbounce.module.autoPearl.description": "敵の真珠の軌道に向けて真珠を狙い投げる."
"liquidbounce.module.betterTab.description": "タブリストに複数の改良を加えました。",
"liquidbounce.module.autoPearl.description": "敵の真珠の軌道に向けて真珠を狙い投げる."
}
1 change: 1 addition & 0 deletions src/main/resources/assets/liquidbounce/lang/pt_br.json
Original file line number Diff line number Diff line change
Expand Up @@ -501,5 +501,6 @@
"liquidbounce.module.autoFish.value.castDelay.description": "Atraso de lançamento.",
"liquidbounce.module.autoFish.value.reelDelay.description": "Atraso de recolhimento.",
"liquidbounce.module.autoFish.value.recastDelay.description": "Atraso de novo lançamento.",
"liquidbounce.module.betterTab.description": "Diversas melhorias na lista de abas.",
"liquidbounce.module.autoPearl.description": "Mira e lança uma pérola na trajetória da pérola inimiga."
}
1 change: 1 addition & 0 deletions src/main/resources/assets/liquidbounce/lang/ru_ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -622,5 +622,6 @@
"liquidbounce.module.tpAura.description": "Автоматически телепортирует и атакует врагов вокруг.",
"liquidbounce.module.packetMine.description": "Позволяет добывать блоки, щелкая по ним один раз.",
"liquidbounce.module.fastExp.description": "Автоматически чинит вашу броню.",
"liquidbounce.module.betterTab.description": "Различные улучшения в таб-листе.",
"liquidbounce.module.autoPearl.description": "Прицеливается и бросает пёрл по траектории движения вражеского пёрла."
}
1 change: 1 addition & 0 deletions src/main/resources/assets/liquidbounce/lang/ua_ua.json
Original file line number Diff line number Diff line change
Expand Up @@ -505,5 +505,6 @@
"liquidbounce.module.autoShop.description": "Опис автоматичного магазину",
"liquidbounce.module.autoShop.messages.reloadSuccess": "Перезавантаження успішне",
"liquidbounce.module.autoShop.messages.loadError": "Помилка завантаження",
"liquidbounce.module.betterTab.description": "Різноманітні покращення списку вкладок.",
"liquidbounce.module.autoPearl.description": "Прицілюється та кидає перлину по траєкторії ворожої перлини."
}
1 change: 1 addition & 0 deletions src/main/resources/assets/liquidbounce/lang/zh_cn.json
Original file line number Diff line number Diff line change
Expand Up @@ -617,5 +617,6 @@
"liquidbounce.module.surround.description": "自动在周围建造安全方块,保护你免受爆炸伤害。",
"liquidbounce.module.packetMine.description": "使你能通过单次点击破坏方块。",
"liquidbounce.module.fastExp.description": "自动用经验瓶修复你的装备。",
"liquidbounce.module.betterTab.description": "对标签列表进行了多项改进。",
"liquidbounce.module.autoPearl.description": "瞄准并向敌方珍珠的轨迹投掷一颗珍珠。"
}

0 comments on commit 2821033

Please sign in to comment.