Compare commits

..
17 Commits
Author SHA1 Message Date
NEZNAMY 651580078c 6.1.0 2026-06-18 17:50:55 +02:00
NEZNAMY 2c3a7a2256 Fix jar name 2026-06-18 17:50:41 +02:00
NEZNAMY 47b3579fde [Components] Fix last color detection not working for magic codes after using &r 2026-06-18 17:17:08 +02:00
NEZNAMY 8147fd4edf [BungeeCord] Fix magic codes not being sent to <26.2 2026-06-17 20:06:16 +02:00
NEZNAMY 4c6fd3282b [Build] Change how jar for Paper 1.20.5 - 1.21.4 is made and named 2026-06-17 19:09:12 +02:00
NEZNAMY 363f5c7e5e [Bukkit] Add 26.2 support 2026-06-17 18:35:06 +02:00
NEZNAMY 229f7d665b [Fabric / NeoForge] Update to 26.2 2026-06-17 17:52:16 +02:00
NEZNAMY ff2dd5d1d5 [Bukkit] Update error message 2026-06-17 16:58:17 +02:00
NEZNAMY 37f1763d9c [ProxySupport] Implement disable-condition forwarding (#1635) 2026-06-14 17:43:43 +02:00
NEZNAMY 962adc683d [BossBar / Scoreboard] Pass arguments of toggle command to the main command to allow silent toggling and more 2026-06-07 11:05:15 +02:00
NEZNAMY 6f4f6b71ff [Fabric] Update to 26.2-pre4 2026-06-06 14:51:05 +02:00
NEZNAMY 6576c53235 [Bukkit] Avoid modifying final fields in player info packet on 1.17 - 1.19.2 (#1648) 2026-06-06 11:32:20 +02:00
NEZNAMY 3d939c2a39 [Bukkit] Avoid modifying final fields in team packet on 1.17+ (#1648) 2026-06-06 11:07:14 +02:00
NEZNAMY 9564e2cdf8 [Bukkit] Avoid modifying final fields in player info packet on 1.8 - 1.16.5 (#1648) 2026-06-06 09:51:35 +02:00
NEZNAMY 2ed7cb794e [Bukkit] Avoid modifying final fields in team packet on 1.8 - 1.16.5 (#1648) 2026-06-06 09:38:06 +02:00
NEZNAMY f2d4a6bee7 [Per world playerlist] Support "*" wildcard and regex in world names just like in global playerlist 2026-06-06 08:09:39 +02:00
NEZNAMY 63be8824f6 [GitHub] Update badges 2026-06-02 18:07:24 +02:00
83 changed files with 1933 additions and 238 deletions

No files matched your search

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="126" height="20" role="img" aria-label="Documentation: Wiki"><title>Documentation: Wiki</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="126" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="93" height="20" fill="#555"/><rect x="93" width="33" height="20" fill="#dfb317"/><rect width="126" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="475" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="830">Documentation</text><text x="475" y="140" transform="scale(.1)" fill="#fff" textLength="830">Documentation</text><text aria-hidden="true" x="1085" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="230">Wiki</text><text x="1085" y="140" transform="scale(.1)" fill="#fff" textLength="230">Wiki</text></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="126" height="20" role="img" aria-label="Documentation: Wiki"><title>Documentation: Wiki</title><filter id="blur"><feGaussianBlur in="SourceGraphic" stdDeviation="16"/></filter><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="126" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="93" height="20" fill="#555"/><rect x="93" width="33" height="20" fill="#d8b800"/><rect width="126" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="475" y="150" fill="#010101" fill-opacity=".80" filter="url(#blur)" transform="scale(.1)" textLength="830">Documentation</text><text aria-hidden="true" x="475" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="830">Documentation</text><text x="475" y="140" transform="scale(.1)" fill="#fff" textLength="830">Documentation</text><text aria-hidden="true" x="1085" y="150" fill="#010101" fill-opacity=".80" filter="url(#blur)" transform="scale(.1)" textLength="230">Wiki</text><text aria-hidden="true" x="1085" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="230">Wiki</text><text x="1085" y="140" transform="scale(.1)" fill="#fff" textLength="230">Wiki</text></g></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

+1 -1
View File
@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="20" role="img" aria-label="Java: 8+"><title>Java: 8+</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="60" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="35" height="20" fill="#555"/><rect x="35" width="25" height="20" fill="#007ec6"/><rect width="60" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="185" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="250">Java</text><text x="185" y="140" transform="scale(.1)" fill="#fff" textLength="250">Java</text><text aria-hidden="true" x="465" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="150">8+</text><text x="465" y="140" transform="scale(.1)" fill="#fff" textLength="150">8+</text></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="20" role="img" aria-label="Java: 8+"><title>Java: 8+</title><filter id="blur"><feGaussianBlur in="SourceGraphic" stdDeviation="16"/></filter><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="60" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="35" height="20" fill="#555"/><rect x="35" width="25" height="20" fill="#007ec6"/><rect width="60" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="185" y="150" fill="#010101" fill-opacity=".80" filter="url(#blur)" transform="scale(.1)" textLength="250">Java</text><text aria-hidden="true" x="185" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="250">Java</text><text x="185" y="140" transform="scale(.1)" fill="#fff" textLength="250">Java</text><text aria-hidden="true" x="465" y="150" fill="#010101" fill-opacity=".80" filter="url(#blur)" transform="scale(.1)" textLength="150">8+</text><text aria-hidden="true" x="465" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="150">8+</text><text x="465" y="140" transform="scale(.1)" fill="#fff" textLength="150">8+</text></g></svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

+1 -1
View File
@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="92" height="20" role="img" aria-label="Release: 6.0.2"><title>Release: 6.0.2</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="92" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="53" height="20" fill="#555"/><rect x="53" width="39" height="20" fill="#007ec6"/><rect width="92" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="275" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="430">Release</text><text x="275" y="140" transform="scale(.1)" fill="#fff" textLength="430">Release</text><text aria-hidden="true" x="715" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="290">6.0.2</text><text x="715" y="140" transform="scale(.1)" fill="#fff" textLength="290">6.0.2</text></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="92" height="20" role="img" aria-label="Release: 6.0.3"><title>Release: 6.0.3</title><filter id="blur"><feGaussianBlur in="SourceGraphic" stdDeviation="16"/></filter><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="92" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="53" height="20" fill="#555"/><rect x="53" width="39" height="20" fill="#007ec6"/><rect width="92" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="275" y="150" fill="#010101" fill-opacity=".80" filter="url(#blur)" transform="scale(.1)" textLength="430">Release</text><text aria-hidden="true" x="275" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="430">Release</text><text x="275" y="140" transform="scale(.1)" fill="#fff" textLength="430">Release</text><text aria-hidden="true" x="715" y="150" fill="#010101" fill-opacity=".80" filter="url(#blur)" transform="scale(.1)" textLength="290">6.0.3</text><text aria-hidden="true" x="715" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="290">6.0.3</text><text x="715" y="140" transform="scale(.1)" fill="#fff" textLength="290">6.0.3</text></g></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="124" height="20" role="img" aria-label="GitHub: Source code"><title>GitHub: Source code</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="124" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="47" height="20" fill="#555"/><rect x="47" width="77" height="20" fill="#dfb317"/><rect width="124" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="245" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="370">GitHub</text><text x="245" y="140" transform="scale(.1)" fill="#fff" textLength="370">GitHub</text><text aria-hidden="true" x="845" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="670">Source code</text><text x="845" y="140" transform="scale(.1)" fill="#fff" textLength="670">Source code</text></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="124" height="20" role="img" aria-label="GitHub: Source code"><title>GitHub: Source code</title><filter id="blur"><feGaussianBlur in="SourceGraphic" stdDeviation="16"/></filter><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="124" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="47" height="20" fill="#555"/><rect x="47" width="77" height="20" fill="#d8b800"/><rect width="124" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="245" y="150" fill="#010101" fill-opacity=".80" filter="url(#blur)" transform="scale(.1)" textLength="370">GitHub</text><text aria-hidden="true" x="245" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="370">GitHub</text><text x="245" y="140" transform="scale(.1)" fill="#fff" textLength="370">GitHub</text><text aria-hidden="true" x="845" y="150" fill="#010101" fill-opacity=".80" filter="url(#blur)" transform="scale(.1)" textLength="670">Source code</text><text aria-hidden="true" x="845" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="670">Source code</text><text x="845" y="140" transform="scale(.1)" fill="#fff" textLength="670">Source code</text></g></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

+1 -1
View File
@@ -274,7 +274,7 @@ if __name__ == "__main__":
]
)
release = "6.0.2"
release = "6.0.3"
download_image(f"https://img.shields.io/badge/Release-{release}-blue.svg", "Badge_Release.svg")
java = "8+"
+3 -1
View File
@@ -4,7 +4,7 @@ plugins {
allprojects {
group = "me.neznamy"
version = "6.1.0-SNAPSHOT"
version = "6.1.0"
description = "An all-in-one solution that works"
ext.set("id", "tab")
@@ -20,6 +20,7 @@ val platformPaths = setOf(
":bukkit:paper_1_21_4",
":bukkit:paper_1_21_9",
":bukkit:paper_1_21_11",
":bukkit:paper_26_2",
":bukkit:v1_7_R4",
":bukkit:v1_8_R3",
":bukkit:v1_12_R1",
@@ -41,6 +42,7 @@ val platformPaths = setOf(
":bukkit:v1_21_R6",
":bukkit:v1_21_R7",
":bukkit:v26_1",
":bukkit:v26_2",
":bungeecord",
":velocity",
":fabric",
+1 -1
View File
@@ -1,5 +1,5 @@
plugins {
id("io.papermc.paperweight.userdev") version "2.0.0-beta.19"
id("io.papermc.paperweight.userdev") version "2.0.0-beta.21"
}
repositories {
@@ -5,7 +5,6 @@ import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.numbers.FixedFormat;
import net.minecraft.network.protocol.Packet;
@@ -17,9 +16,8 @@ import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
@@ -31,8 +29,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final net.minecraft.world.scores.Team.CollisionRule[] collisions = net.minecraft.world.scores.Team.CollisionRule.values();
private static final net.minecraft.world.scores.Team.Visibility[] visibilities = net.minecraft.world.scores.Team.Visibility.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field players = ReflectionUtils.getOnlyField(ClientboundSetPlayerTeamPacket.class, Collection.class);
private static final Constructor<ClientboundSetPlayerTeamPacket> teamConstructor;
static {
try {
teamConstructor = ClientboundSetPlayerTeamPacket.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
*
@@ -144,7 +150,15 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
if (packet instanceof ClientboundSetPlayerTeamPacket team) {
int action = getMethod(team);
if (action != TeamAction.UPDATE) {
players.set(team, onTeamPacket(action, team.getName(), team.getPlayers() == null ? Collections.emptyList() : team.getPlayers()));
Collection<String> players = team.getPlayers();
if (players != null) {
return teamConstructor.newInstance(
team.getName(),
action,
team.getParameters(),
onTeamPacket(action, team.getName(), players)
);
}
}
}
return packet;
+1 -1
View File
@@ -1,5 +1,5 @@
plugins {
id("io.papermc.paperweight.userdev") version "2.0.0-beta.19"
id("io.papermc.paperweight.userdev") version "2.0.0-beta.21"
}
repositories {
@@ -5,7 +5,6 @@ import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.numbers.FixedFormat;
import net.minecraft.network.protocol.Packet;
@@ -17,9 +16,8 @@ import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
@@ -31,8 +29,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final net.minecraft.world.scores.Team.CollisionRule[] collisions = net.minecraft.world.scores.Team.CollisionRule.values();
private static final net.minecraft.world.scores.Team.Visibility[] visibilities = net.minecraft.world.scores.Team.Visibility.values();
protected static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field players = ReflectionUtils.getOnlyField(ClientboundSetPlayerTeamPacket.class, Collection.class);
private static final Constructor<ClientboundSetPlayerTeamPacket> teamConstructor;
static {
try {
teamConstructor = ClientboundSetPlayerTeamPacket.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
*
@@ -144,7 +150,15 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
if (packet instanceof ClientboundSetPlayerTeamPacket team) {
int action = getMethod(team);
if (action != TeamAction.UPDATE) {
players.set(team, onTeamPacket(action, team.getName(), team.getPlayers() == null ? Collections.emptyList() : team.getPlayers()));
Collection<String> players = team.getPlayers();
if (players != null) {
return teamConstructor.newInstance(
team.getName(),
action,
team.getParameters(),
onTeamPacket(action, team.getName(), players)
);
}
}
}
return packet;
+1 -1
View File
@@ -1,5 +1,5 @@
plugins {
id("io.papermc.paperweight.userdev") version "2.0.0-beta.19"
id("io.papermc.paperweight.userdev") version "2.0.0-beta.21"
}
repositories {
@@ -5,7 +5,6 @@ import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.numbers.FixedFormat;
import net.minecraft.network.protocol.Packet;
@@ -17,9 +16,8 @@ import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
@@ -31,8 +29,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final net.minecraft.world.scores.Team.CollisionRule[] collisions = net.minecraft.world.scores.Team.CollisionRule.values();
private static final net.minecraft.world.scores.Team.Visibility[] visibilities = net.minecraft.world.scores.Team.Visibility.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field players = ReflectionUtils.getOnlyField(ClientboundSetPlayerTeamPacket.class, Collection.class);
private static final Constructor<ClientboundSetPlayerTeamPacket> teamConstructor;
static {
try {
teamConstructor = ClientboundSetPlayerTeamPacket.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
*
@@ -144,7 +150,15 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
if (packet instanceof ClientboundSetPlayerTeamPacket team) {
int action = getMethod(team);
if (action != TeamAction.UPDATE) {
players.set(team, onTeamPacket(action, team.getName(), team.getPlayers() == null ? Collections.emptyList() : team.getPlayers()));
Collection<String> players = team.getPlayers();
if (players != null) {
return teamConstructor.newInstance(
team.getName(),
action,
team.getParameters(),
onTeamPacket(action, team.getName(), players)
);
}
}
}
return packet;
+1 -1
View File
@@ -1,5 +1,5 @@
plugins {
id("io.papermc.paperweight.userdev") version "2.0.0-beta.19"
id("io.papermc.paperweight.userdev") version "2.0.0-beta.21"
}
repositories {
@@ -5,7 +5,6 @@ import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.numbers.FixedFormat;
import net.minecraft.network.protocol.Packet;
@@ -17,9 +16,8 @@ import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
@@ -31,8 +29,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final net.minecraft.world.scores.Team.CollisionRule[] collisions = net.minecraft.world.scores.Team.CollisionRule.values();
private static final net.minecraft.world.scores.Team.Visibility[] visibilities = net.minecraft.world.scores.Team.Visibility.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field players = ReflectionUtils.getOnlyField(ClientboundSetPlayerTeamPacket.class, Collection.class);
private static final Constructor<ClientboundSetPlayerTeamPacket> teamConstructor;
static {
try {
teamConstructor = ClientboundSetPlayerTeamPacket.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
*
@@ -144,7 +150,15 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
if (packet instanceof ClientboundSetPlayerTeamPacket team) {
int action = getMethod(team);
if (action != TeamAction.UPDATE) {
players.set(team, onTeamPacket(action, team.getName(), team.getPlayers() == null ? Collections.emptyList() : team.getPlayers()));
Collection<String> players = team.getPlayers();
if (players != null) {
return teamConstructor.newInstance(
team.getName(),
action,
team.getParameters(),
onTeamPacket(action, team.getName(), players)
);
}
}
}
return packet;
+1 -1
View File
@@ -1,5 +1,5 @@
plugins {
id("io.papermc.paperweight.userdev") version "2.0.0-beta.19"
id("io.papermc.paperweight.userdev") version "2.0.0-beta.21"
}
repositories {
@@ -5,7 +5,6 @@ import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.numbers.FixedFormat;
import net.minecraft.network.protocol.Packet;
@@ -17,9 +16,8 @@ import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
@@ -31,8 +29,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final net.minecraft.world.scores.Team.CollisionRule[] collisions = net.minecraft.world.scores.Team.CollisionRule.values();
private static final net.minecraft.world.scores.Team.Visibility[] visibilities = net.minecraft.world.scores.Team.Visibility.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field players = ReflectionUtils.getOnlyField(ClientboundSetPlayerTeamPacket.class, Collection.class);
private static final Constructor<ClientboundSetPlayerTeamPacket> teamConstructor;
static {
try {
teamConstructor = ClientboundSetPlayerTeamPacket.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
*
@@ -144,7 +150,15 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
if (packet instanceof ClientboundSetPlayerTeamPacket team) {
int action = getMethod(team);
if (action != TeamAction.UPDATE) {
players.set(team, onTeamPacket(action, team.getName(), team.getPlayers() == null ? Collections.emptyList() : team.getPlayers()));
Collection<String> players = team.getPlayers();
if (players != null) {
return teamConstructor.newInstance(
team.getName(),
action,
team.getParameters(),
onTeamPacket(action, team.getName(), players)
);
}
}
}
return packet;
+28
View File
@@ -0,0 +1,28 @@
plugins {
id("io.papermc.paperweight.userdev") version "2.0.0-beta.21"
}
repositories {
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven {
name = "luck-repo"
url = uri("https://repo.lucko.me/")
content {
includeModule("me.lucko", "spark-api")
}
}
}
val version = "26.2.build.+"
dependencies {
implementation(projects.bukkit)
paperweight.paperDevBundle(version)
compileOnly("io.papermc.paper:paper-api:${version}")
}
tasks.compileJava {
options.release.set(25)
}
@@ -0,0 +1,63 @@
package me.neznamy.tab.platforms.bukkit.paper_26_2;
import lombok.NonNull;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import net.minecraft.network.chat.numbers.FixedFormat;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import org.jetbrains.annotations.NotNull;
/**
* Scoreboard override for Canvas (Folia fork) that implements scoreboards with
* new checks in NMS setters since 26.1.2.
*/
public class CanvasPacketScoreboard extends NMSPacketScoreboard {
/**
* Constructs new instance with given player.
*
* @param player
* Player this scoreboard will belong to
*/
public CanvasPacketScoreboard(@NotNull BukkitTabPlayer player) {
super(player);
}
/**
* Updates the objective by creating a new one instead of updating the existing one,
* which would require sync access on Canvas.
*
* @param objective
* Objective to update
*/
@Override
public void updateObjective(@NonNull Objective objective) {
net.minecraft.world.scores.Objective obj = new net.minecraft.world.scores.Objective(
dummyScoreboard,
objective.getName(),
ObjectiveCriteria.DUMMY,
objective.getTitle().convert(),
ObjectiveCriteria.RenderType.values()[objective.getHealthDisplay().ordinal()],
false,
objective.getNumberFormat() == null ? null : objective.getNumberFormat().toFixedFormat(FixedFormat::new)
);
objective.setPlatformObjective(obj);
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.UPDATE));
}
@Override
public void registerTeam(@NonNull Team team) {
player.getPlatform().runSyncGlobal(() -> super.registerTeam(team));
}
@Override
public void unregisterTeam(@NonNull Team team) {
// This one too, to avoid register+unregister causing unregister to run first
player.getPlatform().runSyncGlobal(() -> super.unregisterTeam(team));
}
@Override
public void updateTeam(@NonNull Team team) {
player.getPlatform().runSyncGlobal(() -> super.updateTeam(team));
}
}
@@ -0,0 +1,84 @@
package me.neznamy.tab.platforms.bukkit.paper_26_2;
import com.google.common.collect.ImmutableMultimap;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import com.mojang.authlib.properties.PropertyMap;
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
import me.neznamy.tab.shared.chat.TabStyle;
import me.neznamy.tab.shared.chat.component.object.TabAtlasSprite;
import me.neznamy.tab.shared.chat.component.object.TabPlayerSprite;
import me.neznamy.tab.shared.platform.TabList;
import net.minecraft.network.chat.*;
import net.minecraft.network.chat.contents.objects.AtlasSprite;
import net.minecraft.network.chat.contents.objects.PlayerSprite;
import net.minecraft.resources.Identifier;
import net.minecraft.world.item.component.ResolvableProfile;
import org.jetbrains.annotations.NotNull;
/**
* Component converter using direct mojang-mapped code.
*/
public class NMSComponentConverter extends ComponentConverter<Component> {
@Override
@NotNull
public Component newTextComponent(@NotNull String text) {
return Component.literal(text);
}
@Override
@NotNull
public Component newTranslatableComponent(@NotNull String key) {
return Component.translatable(key);
}
@Override
@NotNull
public Component newKeybindComponent(@NotNull String keybind) {
return Component.keybind(keybind);
}
@Override
@NotNull
public Component newObjectComponent(@NotNull TabAtlasSprite sprite) {
return Component.object(new AtlasSprite(Identifier.parse(sprite.getAtlas()), Identifier.parse(sprite.getSprite())));
}
@Override
@NotNull
public Component newObjectComponent(@NotNull TabPlayerSprite sprite) {
ResolvableProfile profile;
if (sprite.getId() != null) {
profile = ResolvableProfile.createUnresolved(sprite.getId());
} else if (sprite.getName() != null) {
profile = ResolvableProfile.createUnresolved(sprite.getName());
} else if (sprite.getSkin() != null) {
ImmutableMultimap.Builder<String, Property> builder = ImmutableMultimap.builder();
builder.put(TabList.TEXTURES_PROPERTY, new Property(TabList.TEXTURES_PROPERTY, sprite.getSkin().getValue(), sprite.getSkin().getSignature()));
profile = ResolvableProfile.createResolved(new GameProfile(NIL_UUID, "", new PropertyMap(builder.build())));
} else {
throw new IllegalStateException("Player head component does not have id, name or skin set");
}
return Component.object(new PlayerSprite(profile, sprite.isShowHat()));
}
@Override
public void applyStyle(@NotNull Component nmsComponent, @NotNull TabStyle modifier) {
Style style = Style.EMPTY
.withColor(modifier.getColor() == null ? null : TextColor.fromRgb(modifier.getColor().getRgb()))
.withBold(modifier.getBold())
.withItalic(modifier.getItalic())
.withUnderlined(modifier.getUnderlined())
.withStrikethrough(modifier.getStrikethrough())
.withObfuscated(modifier.getObfuscated())
.withFont(modifier.getFont() == null ? null : new FontDescription.Resource(Identifier.parse(modifier.getFont())));
if (modifier.getShadowColor() != null) style = style.withShadowColor(modifier.getShadowColor()); // withShadowColor takes int instead of Integer, bug?
((MutableComponent)nmsComponent).setStyle(style);
}
@Override
public void addSibling(@NotNull Component parent, @NotNull Component child) {
((MutableComponent)parent).append(child);
}
}
@@ -0,0 +1,60 @@
package me.neznamy.tab.platforms.bukkit.paper_26_2;
import io.netty.channel.Channel;
import lombok.Getter;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabListEntryTracker;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* Implementation provider using direct Mojang-mapped NMS code for Paper 26.2.
*/
@Getter
public class NMSImplementationProvider implements ImplementationProvider {
/** Flag tracking if this server is Canvas 26.1.2+ with new scoreboard checks */
private final boolean isCanvas = ReflectionUtils.classExists("io.canvasmc.canvas.world.scores.TeamData");
@NotNull
private final ComponentConverter<?> componentConverter = new NMSComponentConverter();
@Override
@NotNull
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
if (isCanvas) {
return new CanvasPacketScoreboard(player);
} else {
return new NMSPacketScoreboard(player);
}
}
@Override
@NotNull
public TabList newTabList(@NotNull BukkitTabPlayer player) {
return new NMSPacketTabList(player);
}
@Override
@NotNull
public Channel getChannel(@NotNull Player player) {
return ((CraftPlayer)player).getHandle().connection.connection.channel;
}
@Override
@NotNull
public TabListEntryTracker newTabListEntryTracker(@NotNull Player player) {
return new NMSTabListEntryTracker(getChannel(player));
}
@Override
public int getPing(@NotNull BukkitTabPlayer player) {
return player.getPlayer().getPing();
}
}
@@ -0,0 +1,191 @@
package me.neznamy.tab.platforms.bukkit.paper_26_2;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import net.minecraft.network.chat.numbers.FixedFormat;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.Scoreboard;
import net.minecraft.world.scores.TeamColor;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.Collection;
import java.util.Optional;
/**
* Scoreboard implementation using direct mojang-mapped code.
*/
public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final Optional<TeamColor>[] colors = Arrays.stream(TeamColor.values()).map(Optional::of).toArray(Optional[]::new);
private static final net.minecraft.world.scores.Team.CollisionRule[] collisions = net.minecraft.world.scores.Team.CollisionRule.values();
private static final net.minecraft.world.scores.Team.Visibility[] visibilities = net.minecraft.world.scores.Team.Visibility.values();
protected static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Constructor<ClientboundSetPlayerTeamPacket> teamConstructor;
static {
try {
teamConstructor = ClientboundSetPlayerTeamPacket.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
*
* @param player
* Player this scoreboard will belong to
*/
public NMSPacketScoreboard(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
public void registerObjective(@NonNull Objective objective) {
net.minecraft.world.scores.Objective obj = new net.minecraft.world.scores.Objective(
dummyScoreboard,
objective.getName(),
ObjectiveCriteria.DUMMY,
objective.getTitle().convert(),
RenderType.values()[objective.getHealthDisplay().ordinal()],
false,
objective.getNumberFormat() == null ? null : objective.getNumberFormat().toFixedFormat(FixedFormat::new)
);
objective.setPlatformObjective(obj);
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.REGISTER));
}
@Override
public void setDisplaySlot(@NonNull Objective objective) {
sendPacket(new ClientboundSetDisplayObjectivePacket(
net.minecraft.world.scores.DisplaySlot.values()[objective.getDisplaySlot().ordinal()],
(net.minecraft.world.scores.Objective) objective.getPlatformObjective()
));
}
@Override
public void unregisterObjective(@NonNull Objective objective) {
sendPacket(new ClientboundSetObjectivePacket((net.minecraft.world.scores.Objective) objective.getPlatformObjective(), ObjectiveAction.UNREGISTER));
}
@Override
public void updateObjective(@NonNull Objective objective) {
net.minecraft.world.scores.Objective obj = (net.minecraft.world.scores.Objective) objective.getPlatformObjective();
obj.setDisplayName(objective.getTitle().convert());
obj.setRenderType(RenderType.values()[objective.getHealthDisplay().ordinal()]);
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.UPDATE));
}
@Override
public void setScore(@NonNull Score score) {
sendPacket(new ClientboundSetScorePacket(
score.getHolder(),
score.getObjective().getName(),
score.getValue(),
Optional.ofNullable(score.getDisplayName() == null ? null : score.getDisplayName().convert()),
Optional.ofNullable(score.getNumberFormat() == null ? null : score.getNumberFormat().toFixedFormat(FixedFormat::new))
));
}
@Override
public void removeScore(@NonNull Score score) {
sendPacket(new ClientboundResetScorePacket(score.getHolder(), score.getObjective().getName()));
}
@Override
@NotNull
public Object createTeam(@NonNull String name) {
return new PlayerTeam(dummyScoreboard, name);
}
@Override
public void registerTeam(@NonNull Team team) {
updateTeamProperties(team);
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
t.getPlayers().addAll(team.getPlayers());
sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(t, true));
}
@Override
public void unregisterTeam(@NonNull Team team) {
sendPacket(ClientboundSetPlayerTeamPacket.createRemovePacket((PlayerTeam) team.getPlatformTeam()));
}
@Override
public void updateTeam(@NonNull Team team) {
updateTeamProperties(team);
sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket((PlayerTeam) team.getPlatformTeam(), false));
}
private void updateTeamProperties(@NonNull Team team) {
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
t.setAllowFriendlyFire((team.getOptions() & 0x01) != 0);
t.setSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0);
t.setColor(colors[Math.min(team.getColor().ordinal(), colors.length - 1)]);
t.setCollisionRule(collisions[team.getCollision().ordinal()]);
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
t.setPlayerPrefix(team.getPrefix().convert());
t.setPlayerSuffix(team.getSuffix().convert());
}
@Override
@SneakyThrows
@NotNull
public Object onPacketSend(@NonNull Object packet) {
if (packet instanceof ClientboundSetDisplayObjectivePacket display) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player, display.getSlot().ordinal(), display.getObjectiveName());
}
if (packet instanceof ClientboundSetObjectivePacket objective) {
TAB.getInstance().getFeatureManager().onObjective(player, objective.getMethod(), objective.getObjectiveName());
}
if (packet instanceof ClientboundSetPlayerTeamPacket team) {
int action = getMethod(team);
if (action != TeamAction.UPDATE) {
Collection<String> players = team.getPlayers();
if (players != null) {
return teamConstructor.newInstance(
team.getName(),
action,
team.getParameters(),
onTeamPacket(action, team.getName(), players)
);
}
}
}
return packet;
}
private int getMethod(@NonNull ClientboundSetPlayerTeamPacket team) {
if (team.getTeamAction() == ClientboundSetPlayerTeamPacket.Action.ADD) {
return 0;
} else if (team.getTeamAction() == ClientboundSetPlayerTeamPacket.Action.REMOVE) {
return 1;
} else if (team.getPlayerAction() == ClientboundSetPlayerTeamPacket.Action.ADD) {
return 3;
} else if (team.getPlayerAction() == ClientboundSetPlayerTeamPacket.Action.REMOVE) {
return 4;
} else {
return 2;
}
}
/**
* Sends the packet to the player.
*
* @param packet
* Packet to send
*/
protected void sendPacket(@NotNull Packet<?> packet) {
((CraftPlayer)player.getPlayer()).getHandle().connection.send(packet);
}
}
@@ -0,0 +1,204 @@
package me.neznamy.tab.platforms.bukkit.paper_26_2;
import com.google.common.collect.ImmutableMultimap;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import com.mojang.authlib.properties.PropertyMap;
import lombok.NonNull;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundTabListPacket;
import net.minecraft.world.level.GameType;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* TabList implementation using direct mojang-mapped code.
*/
public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> addPlayer = EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateDisplayName = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateLatency = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateGameMode = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListed = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListOrder = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LIST_ORDER);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateHat = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_HAT);
/**
* Constructs new instance.
*
* @param player
* Player this tablist will belong to
*/
public NMSPacketTabList(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
public void removeEntry(@NonNull UUID entry) {
sendPacket(new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry)));
}
@Override
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
sendPacket(updateDisplayName, entry, "", null, false, 0, 0, displayName, 0, false);
}
@Override
public void updateLatency(@NonNull UUID entry, int latency) {
sendPacket(updateLatency, entry, "", null, false, latency, 0, null, 0, false);
}
@Override
public void updateGameMode(@NonNull UUID entry, int gameMode) {
sendPacket(updateGameMode, entry, "", null, false, 0, gameMode, null, 0, false);
}
@Override
public void updateListed(@NonNull UUID entry, boolean listed) {
sendPacket(updateListed, entry, "", null, listed, 0, 0, null, 0, false);
}
@Override
public void updateListOrder(@NonNull UUID entry, int listOrder) {
sendPacket(updateListOrder, entry, "", null, false, 0, 0, null, listOrder, false);
}
@Override
public void updateHat(@NonNull UUID entry, boolean showHat) {
sendPacket(updateHat, entry, "", null, false, 0, 0, null, 0, showHat);
}
@Override
public void addEntry0(@NonNull Entry entry) {
sendPacket(addPlayer, entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.isListed(), entry.getLatency(),
entry.getGameMode(), entry.getDisplayName(), entry.getListOrder(), entry.isShowHat());
}
@Override
public void setPlayerListHeaderFooter0(@NonNull TabComponent header, @NonNull TabComponent footer) {
sendPacket(new ClientboundTabListPacket(header.convert(), footer.convert()));
}
@Override
@Nullable
public Skin getSkin() {
Collection<Property> properties = ((CraftPlayer)player.getPlayer()).getProfile().properties().get(TEXTURES_PROPERTY);
if (properties.isEmpty()) return null; // Offline mode
Property property = properties.iterator().next();
return new Skin(property.value(), property.signature());
}
@Override
@NotNull
public Object onPacketSend(@NonNull Object packet) {
if (packet instanceof ClientboundTabListPacket tablist) {
if (header == null || footer == null) return packet;
if (tablist.header() != header.convert() || tablist.footer() != footer.convert()) {
return new ClientboundTabListPacket(header.convert(), footer.convert());
}
}
if (packet instanceof ClientboundPlayerInfoUpdatePacket info) {
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = info.actions();
List<ClientboundPlayerInfoUpdatePacket.Entry> updatedList = new ArrayList<>();
boolean rewritePacket = false;
for (ClientboundPlayerInfoUpdatePacket.Entry nmsData : info.entries()) {
boolean rewriteEntry = false;
Component displayName = nmsData.displayName();
int latency = nmsData.latency();
int gameMode = nmsData.gameMode().getId();
boolean listed = nmsData.listed();
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) {
TabComponent forcedDisplayName = getForcedDisplayNames().get(nmsData.profileId());
if (forcedDisplayName != null && forcedDisplayName.convert() != displayName) {
displayName = forcedDisplayName.convert();
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE)) {
if (getBlockedSpectators().contains(nmsData.profileId()) && gameMode == 3) {
gameMode = 0;
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY)) {
if (getForcedLatency() != null) {
latency = getForcedLatency();
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED)) {
if (allPlayersHidden && nmsData.profileId().getMostSignificantBits() != 0) { // Filter out layout entries
listed = false;
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
TAB.getInstance().getFeatureManager().onEntryAdd(player, nmsData.profileId(), nmsData.profile().name());
}
updatedList.add(rewriteEntry ? new ClientboundPlayerInfoUpdatePacket.Entry(
nmsData.profileId(), nmsData.profile(), listed, latency, GameType.byId(gameMode), displayName,
nmsData.showHat(), nmsData.listOrder(), nmsData.chatSession()
) : nmsData);
}
if (rewritePacket) return new ClientboundPlayerInfoUpdatePacket(actions, updatedList);
}
return packet;
}
private void sendPacket(@NonNull EnumSet<ClientboundPlayerInfoUpdatePacket.Action> action, @NonNull UUID id, @NonNull String name, @Nullable Skin skin,
boolean listed, int latency, int gameMode, @Nullable TabComponent displayName, int listOrder, boolean showHat) {
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(action, new ClientboundPlayerInfoUpdatePacket.Entry(
id,
action.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER) ? createProfile(id, name, skin) : null,
listed,
latency,
GameType.byId(gameMode),
displayName == null ? null : displayName.convert(),
showHat,
listOrder,
null
));
sendPacket(packet);
}
/**
* Creates GameProfile from given parameters.
*
* @param id
* Profile ID
* @param name
* Profile name
* @param skin
* Player skin
* @return GameProfile from given parameters
*/
@NotNull
private GameProfile createProfile(@NonNull UUID id, @NonNull String name, @Nullable Skin skin) {
ImmutableMultimap.Builder<String, Property> builder = ImmutableMultimap.builder();
if (skin != null) {
builder.put(TEXTURES_PROPERTY, new Property(TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
}
return new GameProfile(id, name, new PropertyMap(builder.build()));
}
/**
* Sends the packet to the player.
*
* @param packet
* Packet to send
*/
private void sendPacket(@NotNull Packet<?> packet) {
((CraftPlayer)player.getPlayer()).getHandle().connection.send(packet);
}
}
@@ -0,0 +1,35 @@
package me.neznamy.tab.platforms.bukkit.paper_26_2;
import io.netty.channel.Channel;
import me.neznamy.tab.shared.platform.NettyTabListEntryTracker;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
/**
* Implementation of TabListEntryTracker.
*/
public class NMSTabListEntryTracker extends NettyTabListEntryTracker {
public NMSTabListEntryTracker(@NotNull Channel channel) {
super(channel);
}
@Override
public void onPacketSend(@NotNull Object packet) {
if (packet instanceof ClientboundPlayerInfoRemovePacket remove) {
for (UUID id : remove.profileIds()) {
tablistEntries.remove(id);
}
}
if (packet instanceof ClientboundPlayerInfoUpdatePacket update) {
if (update.actions().contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
for (ClientboundPlayerInfoUpdatePacket.Entry nmsData : update.entries()) {
tablistEntries.add(nmsData.profileId());
}
}
}
}
}
@@ -20,7 +20,7 @@ public class BukkitTAB extends JavaPlugin {
} catch (IllegalStateException e) {
Bukkit.getConsoleSender().sendMessage("§c[TAB] ================================================================================");
Bukkit.getConsoleSender().sendMessage("§c[TAB] Your server version (" + Bukkit.getBukkitVersion() + ") is not supported.");
Bukkit.getConsoleSender().sendMessage("§c[TAB] This jar only supports 1.7.10, 1.8.8, 1.12.2, 1.16.5, 1.17.1 and 1.18.2 - 26.1.2");
Bukkit.getConsoleSender().sendMessage("§c[TAB] This jar only supports 1.7.10, 1.8.8, 1.12.2, 1.16.5, 1.17.1 and 1.18.2 - 26.2");
Bukkit.getConsoleSender().sendMessage("§c[TAB] If you just updated to a new Minecraft version, check for TAB updates.");
Bukkit.getConsoleSender().sendMessage("§c[TAB] If you are using an unsupported 1.x version, use an older version of TAB (latest TAB 5.x supports all MC 1.x versions).");
Bukkit.getConsoleSender().sendMessage("§c[TAB] Thrown error message: " + e.getMessage());
@@ -95,6 +95,7 @@ public class ServerVersionInfo {
spigotVersions.put(ProtocolVersion.V26_1, "v26_1");
spigotVersions.put(ProtocolVersion.V26_1_1, "v26_1");
spigotVersions.put(ProtocolVersion.V26_1_2, "v26_1");
spigotVersions.put(ProtocolVersion.V26_2, "v26_2");
Map<ProtocolVersion, String> paperVersions = new LinkedHashMap<>();
paperVersions.put(ProtocolVersion.V1_20_5, "paper_1_20_5");
@@ -114,11 +115,12 @@ public class ServerVersionInfo {
paperVersions.put(ProtocolVersion.V26_1, "paper_1_21_11"); // v26_1 works too
paperVersions.put(ProtocolVersion.V26_1_1, "paper_1_21_11"); // v26_1 works too
paperVersions.put(ProtocolVersion.V26_1_2, "paper_1_21_11"); // v26_1 works too
paperVersions.put(ProtocolVersion.V26_2, "paper_26_2"); // v26_2 works too
if (serverVersion == ProtocolVersion.UNKNOWN) {
throw new IllegalStateException(String.format(
"Unknown server version (%s), cannot find implementation.",
minecraftVersion
"Unknown server version (%s %s), cannot find implementation.",
serverName, minecraftVersion
));
}
String implementation = (serverName.equals("Paper") ? paperVersions : spigotVersions).get(serverVersion);
@@ -155,15 +155,21 @@ public class PerWorldPlayerList extends TabFeature implements Listener, Loadable
if (v.asBoolean() && !viewer.hasPermission(TabConstants.Permission.SEE_VANISHED)) return false;
}
if ((configuration.isAllowBypassPermission() && viewer.hasPermission(TabConstants.Permission.PER_WORLD_PLAYERLIST_BYPASS)) || configuration.getIgnoredWorlds().contains(viewer.getWorld().getName())) return true;
String viewerWorldGroup = viewer.getWorld().getName() + "-default"; //preventing unwanted behavior when some group is called exactly like a world
String targetWorldGroup = target.getWorld().getName() + "-default";
return computeWorldGroup(viewer).equals(computeWorldGroup(target));
}
@NotNull
private String computeWorldGroup(@NotNull Player p) {
for (Entry<String, List<String>> group : configuration.getSharedWorlds().entrySet()) {
if (group.getValue() != null) {
if (group.getValue().contains(viewer.getWorld().getName())) viewerWorldGroup = group.getKey();
if (group.getValue().contains(target.getWorld().getName())) targetWorldGroup = group.getKey();
List<String> worlds = group.getValue();
if (worlds == null) continue;
for (String world : worlds) {
if (TAB.getInstance().getDataManager().matchesPattern(p.getWorld().getName(), world)) {
return group.getKey();
}
}
}
return viewerWorldGroup.equals(targetWorldGroup);
return p.getWorld().getName() + "-default"; //preventing unwanted behavior when some group is called exactly like a world
}
@NotNull
@@ -72,7 +72,7 @@ public class BukkitPlatform implements BackendPlatform {
private final ServerVersionInfo serverVersionInfo = new ServerVersionInfo();
/** Variables checking presence of other plugins to hook into */
private final boolean placeholderAPI = ReflectionUtils.classExists("me.clip.placeholderapi.PlaceholderAPI");
private final boolean placeholderAPI = Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI");
/** Spigot field for tracking TPS, the array is final and only being modified instead of re-instantiated */
private double[] recentTps;
@@ -13,7 +13,6 @@ import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
/**
* Scoreboard implementation using direct NMS code.
@@ -137,8 +136,11 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
int action = TeamPacket_ACTION.getInt(packet);
if (action != TeamAction.UPDATE) {
Collection<String> players = (Collection<String>) TeamPacket_PLAYERS.get(packet);
if (players == null) players = Collections.emptyList();
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, (String) TeamPacket_NAME.get(packet), players));
if (players != null) {
Collection<String> modified = onTeamPacket(action, (String) TeamPacket_NAME.get(packet), players);
players.clear();
players.addAll(modified);
}
}
}
return packet;
@@ -173,23 +173,27 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
if (rewritePacket) {
PacketPlayOutPlayerInfo newPacket = new PacketPlayOutPlayerInfo(action, Collections.emptyList());
PLAYERS.set(newPacket, updatedList);
List<Object> entries = (List<Object>) PLAYERS.get(newPacket);
entries.clear();
entries.addAll(updatedList);
return newPacket;
}
return packet;
}
@SneakyThrows
@SuppressWarnings("unchecked")
private void sendPacket(@NonNull EnumPlayerInfoAction action, @NonNull UUID id, @NonNull String name,
@Nullable Skin skin, int latency, int gameMode, @Nullable TabComponent displayName) {
PacketPlayOutPlayerInfo packet = new PacketPlayOutPlayerInfo(action);
PLAYERS.set(packet, Collections.singletonList(newPlayerInfoData(
List<Object> entries = (List<Object>) PLAYERS.get(packet);
entries.add(newPlayerInfoData(
packet,
createProfile(id, name, skin),
latency,
EnumGamemode.getById(gameMode),
displayName == null ? null : displayName.convert())
));
);
sendPacket(packet);
}
@@ -12,7 +12,6 @@ import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
/**
* Scoreboard implementation using direct NMS code.
@@ -150,8 +149,11 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
int action = TeamPacket_ACTION.getInt(packet);
if (action != TeamAction.UPDATE) {
Collection<String> players = (Collection<String>) TeamPacket_PLAYERS.get(packet);
if (players == null) players = Collections.emptyList();
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, (String) TeamPacket_NAME.get(packet), players));
if (players != null) {
Collection<String> modified = onTeamPacket(action, (String) TeamPacket_NAME.get(packet), players);
players.clear();
players.addAll(modified);
}
}
}
return packet;
@@ -167,23 +167,27 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
if (rewritePacket) {
PacketPlayOutPlayerInfo newPacket = new PacketPlayOutPlayerInfo(action, Collections.emptyList());
PLAYERS.set(newPacket, updatedList);
List<Object> entries = (List<Object>) PLAYERS.get(newPacket);
entries.clear();
entries.addAll(updatedList);
return newPacket;
}
return packet;
}
@SneakyThrows
@SuppressWarnings("unchecked")
private void sendPacket(@NonNull EnumPlayerInfoAction action, @NonNull UUID id, @NonNull String name,
@Nullable Skin skin, int latency, int gameMode, @Nullable TabComponent displayName) {
PacketPlayOutPlayerInfo packet = new PacketPlayOutPlayerInfo(action);
PLAYERS.set(packet, Collections.singletonList(newPlayerInfoData(
List<Object> entries = (List<Object>) PLAYERS.get(packet);
entries.add(newPlayerInfoData(
packet,
createProfile(id, name, skin),
latency,
EnumGamemode.getById(gameMode),
displayName == null ? null : displayName.convert())
));
);
sendPacket(packet);
}
@@ -5,7 +5,6 @@ import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.EnumChatFormat;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.PacketPlayOutScoreboardDisplayObjective;
@@ -21,9 +20,9 @@ import net.minecraft.world.scores.criteria.IScoreboardCriteria;
import org.bukkit.craftbukkit.v1_17_R1.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
* Scoreboard implementation using direct NMS code.
@@ -34,7 +33,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
private static final Constructor<PacketPlayOutScoreboardTeam> teamConstructor;
static {
try {
teamConstructor = PacketPlayOutScoreboardTeam.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
@@ -158,8 +166,15 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
int action = getMethod((PacketPlayOutScoreboardTeam) packet);
if (action != TeamAction.UPDATE) {
Collection<String> players = ((PacketPlayOutScoreboardTeam) packet).e();
if (players == null) players = Collections.emptyList();
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, ((PacketPlayOutScoreboardTeam)packet).d(), players));
if (players != null) {
String name = ((PacketPlayOutScoreboardTeam) packet).d();
return teamConstructor.newInstance(
name,
action,
((PacketPlayOutScoreboardTeam) packet).f(),
onTeamPacket(action, name, players)
);
}
}
}
return packet;
@@ -4,12 +4,11 @@ import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo;
@@ -21,7 +20,6 @@ import org.bukkit.craftbukkit.v1_17_R1.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.*;
/**
@@ -29,8 +27,6 @@ import java.util.*;
*/
public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
private static final Field PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, List.class);
private static final EnumPlayerInfoAction ADD_PLAYER = EnumPlayerInfoAction.a;
private static final EnumPlayerInfoAction UPDATE_GAME_MODE = EnumPlayerInfoAction.b;
private static final EnumPlayerInfoAction UPDATE_LATENCY = EnumPlayerInfoAction.c;
@@ -158,21 +154,21 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
if (rewritePacket) {
PacketPlayOutPlayerInfo newPacket = new PacketPlayOutPlayerInfo(action, Collections.emptyList());
PLAYERS.set(newPacket, updatedList);
newPacket.b().clear();
newPacket.b().addAll(updatedList);
return newPacket;
}
return packet;
}
@SneakyThrows
private void sendPacket(@NonNull EnumPlayerInfoAction action, @NonNull UUID id, @NonNull String name,
@Nullable Skin skin, int latency, int gameMode, @Nullable TabComponent displayName) {
PacketPlayOutPlayerInfo packet = new PacketPlayOutPlayerInfo(action);
PLAYERS.set(packet, Collections.singletonList(new PlayerInfoData(
packet.b().add(new PlayerInfoData(
createProfile(id, name, skin),
latency,
EnumGamemode.getById(gameMode),
displayName == null ? null : displayName.convert())
displayName == null ? null : displayName.convert()
));
sendPacket(packet);
}
@@ -5,7 +5,6 @@ import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.EnumChatFormat;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.protocol.Packet;
@@ -22,9 +21,9 @@ import net.minecraft.world.scores.criteria.IScoreboardCriteria;
import org.bukkit.craftbukkit.v1_18_R2.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
* Scoreboard implementation using direct NMS code.
@@ -35,7 +34,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
private static final Constructor<PacketPlayOutScoreboardTeam> teamConstructor;
static {
try {
teamConstructor = PacketPlayOutScoreboardTeam.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
@@ -159,8 +167,15 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
int action = getMethod((PacketPlayOutScoreboardTeam) packet);
if (action != TeamAction.UPDATE) {
Collection<String> players = ((PacketPlayOutScoreboardTeam) packet).e();
if (players == null) players = Collections.emptyList();
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, ((PacketPlayOutScoreboardTeam)packet).d(), players));
if (players != null) {
String name = ((PacketPlayOutScoreboardTeam) packet).d();
return teamConstructor.newInstance(
name,
action,
((PacketPlayOutScoreboardTeam) packet).f(),
onTeamPacket(action, name, players)
);
}
}
}
return packet;
@@ -4,12 +4,11 @@ import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo;
@@ -21,7 +20,6 @@ import org.bukkit.craftbukkit.v1_18_R2.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.*;
/**
@@ -29,8 +27,6 @@ import java.util.*;
*/
public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
private static final Field PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, List.class);
private static final EnumPlayerInfoAction ADD_PLAYER = EnumPlayerInfoAction.a;
private static final EnumPlayerInfoAction UPDATE_GAME_MODE = EnumPlayerInfoAction.b;
private static final EnumPlayerInfoAction UPDATE_LATENCY = EnumPlayerInfoAction.c;
@@ -158,21 +154,21 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
if (rewritePacket) {
PacketPlayOutPlayerInfo newPacket = new PacketPlayOutPlayerInfo(action, Collections.emptyList());
PLAYERS.set(newPacket, updatedList);
newPacket.b().clear();
newPacket.b().addAll(updatedList);
return newPacket;
}
return packet;
}
@SneakyThrows
private void sendPacket(@NonNull EnumPlayerInfoAction action, @NonNull UUID id, @NonNull String name,
@Nullable Skin skin, int latency, int gameMode, @Nullable TabComponent displayName) {
PacketPlayOutPlayerInfo packet = new PacketPlayOutPlayerInfo(action);
PLAYERS.set(packet, Collections.singletonList(new PlayerInfoData(
packet.b().add(new PlayerInfoData(
createProfile(id, name, skin),
latency,
EnumGamemode.a(gameMode),
displayName == null ? null : displayName.convert())
displayName == null ? null : displayName.convert()
));
sendPacket(packet);
}
@@ -5,7 +5,6 @@ import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.EnumChatFormat;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.protocol.Packet;
@@ -22,9 +21,9 @@ import net.minecraft.world.scores.criteria.IScoreboardCriteria;
import org.bukkit.craftbukkit.v1_19_R1.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
* Scoreboard implementation using direct NMS code.
@@ -35,7 +34,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
private static final Constructor<PacketPlayOutScoreboardTeam> teamConstructor;
static {
try {
teamConstructor = PacketPlayOutScoreboardTeam.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
@@ -159,8 +167,15 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
int action = getMethod((PacketPlayOutScoreboardTeam) packet);
if (action != TeamAction.UPDATE) {
Collection<String> players = ((PacketPlayOutScoreboardTeam) packet).e();
if (players == null) players = Collections.emptyList();
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, ((PacketPlayOutScoreboardTeam)packet).d(), players));
if (players != null) {
String name = ((PacketPlayOutScoreboardTeam) packet).d();
return teamConstructor.newInstance(
name,
action,
((PacketPlayOutScoreboardTeam) packet).f(),
onTeamPacket(action, name, players)
);
}
}
}
return packet;
@@ -4,12 +4,11 @@ import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo;
@@ -22,7 +21,6 @@ import org.bukkit.craftbukkit.v1_19_R1.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.*;
/**
@@ -30,8 +28,6 @@ import java.util.*;
*/
public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
private static final Field PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, List.class);
private static final EnumPlayerInfoAction ADD_PLAYER = EnumPlayerInfoAction.a;
private static final EnumPlayerInfoAction UPDATE_GAME_MODE = EnumPlayerInfoAction.b;
private static final EnumPlayerInfoAction UPDATE_LATENCY = EnumPlayerInfoAction.c;
@@ -161,23 +157,23 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
if (rewritePacket) {
PacketPlayOutPlayerInfo newPacket = new PacketPlayOutPlayerInfo(action, Collections.emptyList());
PLAYERS.set(newPacket, updatedList);
newPacket.b().clear();
newPacket.b().addAll(updatedList);
return newPacket;
}
return packet;
}
@SneakyThrows
private void sendPacket(@NonNull EnumPlayerInfoAction action, @NonNull UUID id, @NonNull String name,
@Nullable Skin skin, int latency, int gameMode, @Nullable TabComponent displayName) {
PacketPlayOutPlayerInfo packet = new PacketPlayOutPlayerInfo(action);
PLAYERS.set(packet, Collections.singletonList(new PlayerInfoData(
packet.b().add(new PlayerInfoData(
createProfile(id, name, skin),
latency,
EnumGamemode.a(gameMode),
displayName == null ? null : displayName.convert(),
null
)));
));
sendPacket(packet);
}
@@ -5,7 +5,6 @@ import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.EnumChatFormat;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.protocol.Packet;
@@ -22,9 +21,9 @@ import net.minecraft.world.scores.criteria.IScoreboardCriteria;
import org.bukkit.craftbukkit.v1_19_R2.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
* Scoreboard implementation using direct NMS code.
@@ -35,8 +34,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
private static final Constructor<PacketPlayOutScoreboardTeam> teamConstructor;
static {
try {
teamConstructor = PacketPlayOutScoreboardTeam.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
*
@@ -159,8 +166,15 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
int action = getMethod((PacketPlayOutScoreboardTeam) packet);
if (action != TeamAction.UPDATE) {
Collection<String> players = ((PacketPlayOutScoreboardTeam) packet).e();
if (players == null) players = Collections.emptyList();
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, ((PacketPlayOutScoreboardTeam)packet).d(), players));
if (players != null) {
String name = ((PacketPlayOutScoreboardTeam) packet).d();
return teamConstructor.newInstance(
name,
action,
((PacketPlayOutScoreboardTeam) packet).f(),
onTeamPacket(action, name, players)
);
}
}
}
return packet;
@@ -5,7 +5,6 @@ import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.EnumChatFormat;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.protocol.Packet;
@@ -22,9 +21,9 @@ import net.minecraft.world.scores.criteria.IScoreboardCriteria;
import org.bukkit.craftbukkit.v1_19_R3.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
* Scoreboard implementation using direct NMS code.
@@ -35,7 +34,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
private static final Constructor<PacketPlayOutScoreboardTeam> teamConstructor;
static {
try {
teamConstructor = PacketPlayOutScoreboardTeam.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
@@ -159,8 +167,15 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
int action = getMethod((PacketPlayOutScoreboardTeam) packet);
if (action != TeamAction.UPDATE) {
Collection<String> players = ((PacketPlayOutScoreboardTeam) packet).e();
if (players == null) players = Collections.emptyList();
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, ((PacketPlayOutScoreboardTeam)packet).d(), players));
if (players != null) {
String name = ((PacketPlayOutScoreboardTeam) packet).d();
return teamConstructor.newInstance(
name,
action,
((PacketPlayOutScoreboardTeam) packet).f(),
onTeamPacket(action, name, players)
);
}
}
}
return packet;
@@ -5,7 +5,6 @@ import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.EnumChatFormat;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.protocol.Packet;
@@ -22,9 +21,9 @@ import net.minecraft.world.scores.criteria.IScoreboardCriteria;
import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
* Scoreboard implementation using direct NMS code.
@@ -35,7 +34,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
private static final Constructor<PacketPlayOutScoreboardTeam> teamConstructor;
static {
try {
teamConstructor = PacketPlayOutScoreboardTeam.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
@@ -159,8 +167,15 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
int action = getMethod((PacketPlayOutScoreboardTeam) packet);
if (action != TeamAction.UPDATE) {
Collection<String> players = ((PacketPlayOutScoreboardTeam) packet).e();
if (players == null) players = Collections.emptyList();
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, ((PacketPlayOutScoreboardTeam)packet).d(), players));
if (players != null) {
String name = ((PacketPlayOutScoreboardTeam) packet).d();
return teamConstructor.newInstance(
name,
action,
((PacketPlayOutScoreboardTeam) packet).f(),
onTeamPacket(action, name, players)
);
}
}
}
return packet;
@@ -5,7 +5,6 @@ import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.EnumChatFormat;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.protocol.Packet;
@@ -14,14 +13,17 @@ import net.minecraft.network.protocol.game.PacketPlayOutScoreboardObjective;
import net.minecraft.network.protocol.game.PacketPlayOutScoreboardScore;
import net.minecraft.network.protocol.game.PacketPlayOutScoreboardTeam;
import net.minecraft.server.ScoreboardServer;
import net.minecraft.world.scores.*;
import net.minecraft.world.scores.Scoreboard;
import net.minecraft.world.scores.ScoreboardObjective;
import net.minecraft.world.scores.ScoreboardTeam;
import net.minecraft.world.scores.ScoreboardTeamBase;
import net.minecraft.world.scores.criteria.IScoreboardCriteria;
import org.bukkit.craftbukkit.v1_20_R2.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
* Scoreboard implementation using direct NMS code.
@@ -32,7 +34,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
private static final Constructor<PacketPlayOutScoreboardTeam> teamConstructor;
static {
try {
teamConstructor = PacketPlayOutScoreboardTeam.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
@@ -147,7 +158,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
if (packet instanceof PacketPlayOutScoreboardTeam team) {
int action = getMethod(team);
if (action != TeamAction.UPDATE) {
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, team.e(), team.f() == null ? Collections.emptyList() : team.f()));
Collection<String> players = team.f();
if (players != null) {
String name = team.e();
return teamConstructor.newInstance(
name,
action,
team.g(),
onTeamPacket(action, name, players)
);
}
}
}
return packet;
@@ -5,7 +5,6 @@ import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.EnumChatFormat;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.chat.numbers.FixedFormat;
@@ -19,9 +18,9 @@ import net.minecraft.world.scores.criteria.IScoreboardCriteria;
import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
* Scoreboard implementation using direct NMS code.
@@ -32,7 +31,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
private static final Constructor<PacketPlayOutScoreboardTeam> teamConstructor;
static {
try {
teamConstructor = PacketPlayOutScoreboardTeam.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
@@ -145,7 +153,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
if (packet instanceof PacketPlayOutScoreboardTeam team) {
int action = getMethod(team);
if (action != TeamAction.UPDATE) {
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, team.e(), team.f() == null ? Collections.emptyList() : team.f()));
Collection<String> players = team.f();
if (players != null) {
String name = team.e();
return teamConstructor.newInstance(
name,
action,
team.g(),
onTeamPacket(action, name, players)
);
}
}
}
return packet;
@@ -5,7 +5,6 @@ import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.EnumChatFormat;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.chat.numbers.FixedFormat;
@@ -20,9 +19,8 @@ import net.minecraft.world.scores.criteria.IScoreboardCriteria;
import org.bukkit.craftbukkit.v1_20_R4.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
@@ -34,7 +32,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
private static final Constructor<PacketPlayOutScoreboardTeam> teamConstructor;
static {
try {
teamConstructor = PacketPlayOutScoreboardTeam.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
@@ -147,7 +154,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
if (packet instanceof PacketPlayOutScoreboardTeam team) {
int action = getMethod(team);
if (action != TeamAction.UPDATE) {
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, team.f(), team.g() == null ? Collections.emptyList() : team.g()));
Collection<String> players = team.g();
if (players != null) {
String name = team.f();
return teamConstructor.newInstance(
name,
action,
team.h(),
onTeamPacket(action, name, players)
);
}
}
}
return packet;
@@ -5,7 +5,6 @@ import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.EnumChatFormat;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.chat.numbers.FixedFormat;
@@ -20,9 +19,8 @@ import net.minecraft.world.scores.criteria.IScoreboardCriteria;
import org.bukkit.craftbukkit.v1_21_R1.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
@@ -34,7 +32,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
private static final Constructor<PacketPlayOutScoreboardTeam> teamConstructor;
static {
try {
teamConstructor = PacketPlayOutScoreboardTeam.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
@@ -147,7 +154,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
if (packet instanceof PacketPlayOutScoreboardTeam team) {
int action = getMethod(team);
if (action != TeamAction.UPDATE) {
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, team.f(), team.g() == null ? Collections.emptyList() : team.g()));
Collection<String> players = team.g();
if (players != null) {
String name = team.f();
return teamConstructor.newInstance(
name,
action,
team.h(),
onTeamPacket(action, name, players)
);
}
}
}
return packet;
@@ -5,7 +5,6 @@ import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.EnumChatFormat;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.chat.numbers.FixedFormat;
@@ -20,9 +19,8 @@ import net.minecraft.world.scores.criteria.IScoreboardCriteria;
import org.bukkit.craftbukkit.v1_21_R2.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
@@ -34,7 +32,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
private static final Constructor<PacketPlayOutScoreboardTeam> teamConstructor;
static {
try {
teamConstructor = PacketPlayOutScoreboardTeam.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
@@ -147,7 +154,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
if (packet instanceof PacketPlayOutScoreboardTeam team) {
int action = getMethod(team);
if (action != TeamAction.UPDATE) {
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, team.f(), team.g() == null ? Collections.emptyList() : team.g()));
Collection<String> players = team.g();
if (players != null) {
String name = team.f();
return teamConstructor.newInstance(
name,
action,
team.h(),
onTeamPacket(action, name, players)
);
}
}
}
return packet;
@@ -5,7 +5,6 @@ import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.EnumChatFormat;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.chat.numbers.FixedFormat;
@@ -20,9 +19,8 @@ import net.minecraft.world.scores.criteria.IScoreboardCriteria;
import org.bukkit.craftbukkit.v1_21_R3.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
@@ -34,7 +32,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
private static final Constructor<PacketPlayOutScoreboardTeam> teamConstructor;
static {
try {
teamConstructor = PacketPlayOutScoreboardTeam.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
@@ -147,7 +154,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
if (packet instanceof PacketPlayOutScoreboardTeam team) {
int action = getMethod(team);
if (action != TeamAction.UPDATE) {
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, team.f(), team.g() == null ? Collections.emptyList() : team.g()));
Collection<String> players = team.g();
if (players != null) {
String name = team.f();
return teamConstructor.newInstance(
name,
action,
team.h(),
onTeamPacket(action, name, players)
);
}
}
}
return packet;
@@ -5,7 +5,6 @@ import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.EnumChatFormat;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.chat.numbers.FixedFormat;
@@ -20,9 +19,8 @@ import net.minecraft.world.scores.criteria.IScoreboardCriteria;
import org.bukkit.craftbukkit.v1_21_R4.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
@@ -34,7 +32,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
private static final Constructor<PacketPlayOutScoreboardTeam> teamConstructor;
static {
try {
teamConstructor = PacketPlayOutScoreboardTeam.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
@@ -147,7 +154,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
if (packet instanceof PacketPlayOutScoreboardTeam team) {
int action = getMethod(team);
if (action != TeamAction.UPDATE) {
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, team.f(), team.g() == null ? Collections.emptyList() : team.g()));
Collection<String> players = team.g();
if (players != null) {
String name = team.f();
return teamConstructor.newInstance(
name,
action,
team.h(),
onTeamPacket(action, name, players)
);
}
}
}
return packet;
@@ -5,7 +5,6 @@ import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.EnumChatFormat;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.chat.numbers.FixedFormat;
@@ -20,9 +19,8 @@ import net.minecraft.world.scores.criteria.IScoreboardCriteria;
import org.bukkit.craftbukkit.v1_21_R5.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
@@ -34,7 +32,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
private static final Constructor<PacketPlayOutScoreboardTeam> teamConstructor;
static {
try {
teamConstructor = PacketPlayOutScoreboardTeam.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
@@ -147,7 +154,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
if (packet instanceof PacketPlayOutScoreboardTeam team) {
int action = getMethod(team);
if (action != TeamAction.UPDATE) {
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, team.f(), team.g() == null ? Collections.emptyList() : team.g()));
Collection<String> players = team.g();
if (players != null) {
String name = team.f();
return teamConstructor.newInstance(
name,
action,
team.h(),
onTeamPacket(action, name, players)
);
}
}
}
return packet;
@@ -5,7 +5,6 @@ import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.EnumChatFormat;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.chat.numbers.FixedFormat;
@@ -20,9 +19,8 @@ import net.minecraft.world.scores.criteria.IScoreboardCriteria;
import org.bukkit.craftbukkit.v1_21_R6.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
@@ -34,7 +32,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
private static final Constructor<PacketPlayOutScoreboardTeam> teamConstructor;
static {
try {
teamConstructor = PacketPlayOutScoreboardTeam.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
@@ -147,7 +154,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
if (packet instanceof PacketPlayOutScoreboardTeam team) {
int action = getMethod(team);
if (action != TeamAction.UPDATE) {
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, team.f(), team.g() == null ? Collections.emptyList() : team.g()));
Collection<String> players = team.g();
if (players != null) {
String name = team.f();
return teamConstructor.newInstance(
name,
action,
team.h(),
onTeamPacket(action, name, players)
);
}
}
}
return packet;
@@ -5,7 +5,6 @@ import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.EnumChatFormat;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.chat.numbers.FixedFormat;
@@ -20,9 +19,8 @@ import net.minecraft.world.scores.criteria.IScoreboardCriteria;
import org.bukkit.craftbukkit.v1_21_R7.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
@@ -34,7 +32,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
private static final Constructor<PacketPlayOutScoreboardTeam> teamConstructor;
static {
try {
teamConstructor = PacketPlayOutScoreboardTeam.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
@@ -147,7 +154,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
if (packet instanceof PacketPlayOutScoreboardTeam team) {
int action = getMethod(team);
if (action != TeamAction.UPDATE) {
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, team.f(), team.g() == null ? Collections.emptyList() : team.g()));
Collection<String> players = team.g();
if (players != null) {
String name = team.f();
return teamConstructor.newInstance(
name,
action,
team.h(),
onTeamPacket(action, name, players)
);
}
}
}
return packet;
@@ -13,7 +13,6 @@ import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
/**
* Scoreboard implementation using direct NMS code.
@@ -135,8 +134,11 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
int action = TeamPacket_ACTION.getInt(packet);
if (action != TeamAction.UPDATE) {
Collection<String> players = (Collection<String>) TeamPacket_PLAYERS.get(packet);
if (players == null) players = Collections.emptyList();
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, (String) TeamPacket_NAME.get(packet), players));
if (players != null) {
Collection<String> modified = onTeamPacket(action, (String) TeamPacket_NAME.get(packet), players);
players.clear();
players.addAll(modified);
}
}
}
return packet;
@@ -162,22 +162,26 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
if (rewritePacket) {
PacketPlayOutPlayerInfo newPacket = new PacketPlayOutPlayerInfo(action, Collections.emptyList());
PLAYERS.set(newPacket, updatedList);
List<PlayerInfoData> entries = (List<PlayerInfoData>) PLAYERS.get(newPacket);
entries.clear();
entries.addAll(updatedList);
return newPacket;
}
return packet;
}
@SneakyThrows
@SuppressWarnings("unchecked")
private void sendPacket(@NonNull EnumPlayerInfoAction action, @NonNull UUID id, @NonNull String name,
@Nullable Skin skin, int latency, int gameMode, @Nullable TabComponent displayName) {
PacketPlayOutPlayerInfo packet = new PacketPlayOutPlayerInfo(action);
PLAYERS.set(packet, Collections.singletonList(packet.new PlayerInfoData(
List<PlayerInfoData> entries = (List<PlayerInfoData>) PLAYERS.get(packet);
entries.add(packet.new PlayerInfoData(
createProfile(id, name, skin),
latency,
EnumGamemode.getById(gameMode),
displayName == null ? null : displayName.convert())
));
);
sendPacket(packet);
}
@@ -19,7 +19,7 @@ import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
/**
* Implementation provider using direct NMS code for 26.1.
* Implementation provider using direct NMS code for 26.1.x.
*/
@Getter
public class NMSImplementationProvider implements ImplementationProvider {
@@ -5,7 +5,6 @@ import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.numbers.FixedFormat;
import net.minecraft.network.protocol.Packet;
@@ -17,9 +16,8 @@ import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
@@ -31,7 +29,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final net.minecraft.world.scores.Team.CollisionRule[] collisions = net.minecraft.world.scores.Team.CollisionRule.values();
private static final net.minecraft.world.scores.Team.Visibility[] visibilities = net.minecraft.world.scores.Team.Visibility.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field players = ReflectionUtils.getOnlyField(ClientboundSetPlayerTeamPacket.class, Collection.class);
private static final Constructor<ClientboundSetPlayerTeamPacket> teamConstructor;
static {
try {
teamConstructor = ClientboundSetPlayerTeamPacket.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
@@ -144,7 +151,15 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
if (packet instanceof ClientboundSetPlayerTeamPacket team) {
int action = getMethod(team);
if (action != TeamAction.UPDATE) {
players.set(team, onTeamPacket(action, team.getName(), team.getPlayers() == null ? Collections.emptyList() : team.getPlayers()));
Collection<String> players = team.getPlayers();
if (players != null) {
return teamConstructor.newInstance(
team.getName(),
action,
team.getParameters(),
onTeamPacket(action, team.getName(), players)
);
}
}
}
return packet;
+8
View File
@@ -0,0 +1,8 @@
dependencies {
implementation(projects.bukkit)
compileOnly("org.spigotmc:spigot:26.2-R0.1-SNAPSHOT")
}
tasks.compileJava {
options.release.set(21) // Included library "jtracy" requires Java 21
}
@@ -0,0 +1,84 @@
package me.neznamy.tab.platforms.bukkit.v26_2;
import com.google.common.collect.ImmutableMultimap;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import com.mojang.authlib.properties.PropertyMap;
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
import me.neznamy.tab.shared.chat.TabStyle;
import me.neznamy.tab.shared.chat.component.object.TabAtlasSprite;
import me.neznamy.tab.shared.chat.component.object.TabPlayerSprite;
import me.neznamy.tab.shared.platform.TabList;
import net.minecraft.network.chat.*;
import net.minecraft.network.chat.contents.objects.AtlasSprite;
import net.minecraft.network.chat.contents.objects.PlayerSprite;
import net.minecraft.resources.Identifier;
import net.minecraft.world.item.component.ResolvableProfile;
import org.jetbrains.annotations.NotNull;
/**
* Component converter using direct mojang-mapped code.
*/
public class NMSComponentConverter extends ComponentConverter<Component> {
@Override
@NotNull
public Component newTextComponent(@NotNull String text) {
return Component.literal(text);
}
@Override
@NotNull
public Component newTranslatableComponent(@NotNull String key) {
return Component.translatable(key);
}
@Override
@NotNull
public Component newKeybindComponent(@NotNull String keybind) {
return Component.keybind(keybind);
}
@Override
@NotNull
public Component newObjectComponent(@NotNull TabAtlasSprite sprite) {
return Component.object(new AtlasSprite(Identifier.parse(sprite.getAtlas()), Identifier.parse(sprite.getSprite())));
}
@Override
@NotNull
public Component newObjectComponent(@NotNull TabPlayerSprite sprite) {
ResolvableProfile profile;
if (sprite.getId() != null) {
profile = ResolvableProfile.createUnresolved(sprite.getId());
} else if (sprite.getName() != null) {
profile = ResolvableProfile.createUnresolved(sprite.getName());
} else if (sprite.getSkin() != null) {
ImmutableMultimap.Builder<String, Property> builder = ImmutableMultimap.builder();
builder.put(TabList.TEXTURES_PROPERTY, new Property(TabList.TEXTURES_PROPERTY, sprite.getSkin().getValue(), sprite.getSkin().getSignature()));
profile = ResolvableProfile.createResolved(new GameProfile(NIL_UUID, "", new PropertyMap(builder.build())));
} else {
throw new IllegalStateException("Player head component does not have id, name or skin set");
}
return Component.object(new PlayerSprite(profile, sprite.isShowHat()));
}
@Override
public void applyStyle(@NotNull Component nmsComponent, @NotNull TabStyle modifier) {
Style style = Style.EMPTY
.withColor(modifier.getColor() == null ? null : TextColor.fromRgb(modifier.getColor().getRgb()))
.withBold(modifier.getBold())
.withItalic(modifier.getItalic())
.withUnderlined(modifier.getUnderlined())
.withStrikethrough(modifier.getStrikethrough())
.withObfuscated(modifier.getObfuscated())
.withFont(modifier.getFont() == null ? null : new FontDescription.Resource(Identifier.parse(modifier.getFont())));
if (modifier.getShadowColor() != null) style = style.withShadowColor(modifier.getShadowColor()); // withShadowColor takes int instead of Integer, bug?
((MutableComponent)nmsComponent).setStyle(style);
}
@Override
public void addSibling(@NotNull Component parent, @NotNull Component child) {
((MutableComponent)parent).append(child);
}
}
@@ -0,0 +1,62 @@
package me.neznamy.tab.platforms.bukkit.v26_2;
import io.netty.channel.Channel;
import lombok.Getter;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabListEntryTracker;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.Connection;
import net.minecraft.server.network.ServerCommonPacketListenerImpl;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
/**
* Implementation provider using direct NMS code for 26.2.
*/
@Getter
public class NMSImplementationProvider implements ImplementationProvider {
/** Field is somehow private */
private static final Field networkManager = ReflectionUtils.getOnlyField(ServerCommonPacketListenerImpl.class, Connection.class);
@NotNull
private final ComponentConverter<?> componentConverter = new NMSComponentConverter();
@Override
@NotNull
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
return new NMSPacketScoreboard(player);
}
@Override
@NotNull
public TabList newTabList(@NotNull BukkitTabPlayer player) {
return new NMSPacketTabList(player);
}
@Override
@NotNull
@SneakyThrows
public Channel getChannel(@NotNull Player player) {
return ((Connection)networkManager.get(((CraftPlayer)player).getHandle().connection)).channel;
}
@Override
@NotNull
public TabListEntryTracker newTabListEntryTracker(@NotNull Player player) {
return new NMSTabListEntryTracker(getChannel(player));
}
@Override
public int getPing(@NotNull BukkitTabPlayer player) {
return player.getPlayer().getPing();
}
}
@@ -0,0 +1,192 @@
package me.neznamy.tab.platforms.bukkit.v26_2;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import net.minecraft.network.chat.numbers.FixedFormat;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.Scoreboard;
import net.minecraft.world.scores.TeamColor;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.Collection;
import java.util.Optional;
/**
* Scoreboard implementation using direct mojang-mapped code.
*/
public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final Optional<TeamColor>[] colors = Arrays.stream(TeamColor.values()).map(Optional::of).toArray(Optional[]::new);
private static final net.minecraft.world.scores.Team.CollisionRule[] collisions = net.minecraft.world.scores.Team.CollisionRule.values();
private static final net.minecraft.world.scores.Team.Visibility[] visibilities = net.minecraft.world.scores.Team.Visibility.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Constructor<ClientboundSetPlayerTeamPacket> teamConstructor;
static {
try {
teamConstructor = ClientboundSetPlayerTeamPacket.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
*
* @param player
* Player this scoreboard will belong to
*/
public NMSPacketScoreboard(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
public void registerObjective(@NonNull Objective objective) {
net.minecraft.world.scores.Objective obj = new net.minecraft.world.scores.Objective(
dummyScoreboard,
objective.getName(),
ObjectiveCriteria.DUMMY,
objective.getTitle().convert(),
RenderType.values()[objective.getHealthDisplay().ordinal()],
false,
objective.getNumberFormat() == null ? null : objective.getNumberFormat().toFixedFormat(FixedFormat::new)
);
objective.setPlatformObjective(obj);
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.REGISTER));
}
@Override
public void setDisplaySlot(@NonNull Objective objective) {
sendPacket(new ClientboundSetDisplayObjectivePacket(
net.minecraft.world.scores.DisplaySlot.values()[objective.getDisplaySlot().ordinal()],
(net.minecraft.world.scores.Objective) objective.getPlatformObjective()
));
}
@Override
public void unregisterObjective(@NonNull Objective objective) {
sendPacket(new ClientboundSetObjectivePacket((net.minecraft.world.scores.Objective) objective.getPlatformObjective(), ObjectiveAction.UNREGISTER));
}
@Override
public void updateObjective(@NonNull Objective objective) {
net.minecraft.world.scores.Objective obj = (net.minecraft.world.scores.Objective) objective.getPlatformObjective();
obj.setDisplayName(objective.getTitle().convert());
obj.setRenderType(RenderType.values()[objective.getHealthDisplay().ordinal()]);
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.UPDATE));
}
@Override
public void setScore(@NonNull Score score) {
sendPacket(new ClientboundSetScorePacket(
score.getHolder(),
score.getObjective().getName(),
score.getValue(),
Optional.ofNullable(score.getDisplayName() == null ? null : score.getDisplayName().convert()),
Optional.ofNullable(score.getNumberFormat() == null ? null : score.getNumberFormat().toFixedFormat(FixedFormat::new))
));
}
@Override
public void removeScore(@NonNull Score score) {
sendPacket(new ClientboundResetScorePacket(score.getHolder(), score.getObjective().getName()));
}
@Override
@NotNull
public Object createTeam(@NonNull String name) {
return new PlayerTeam(dummyScoreboard, name);
}
@Override
public void registerTeam(@NonNull Team team) {
updateTeamProperties(team);
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
t.getPlayers().addAll(team.getPlayers());
sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(t, true));
}
@Override
public void unregisterTeam(@NonNull Team team) {
sendPacket(ClientboundSetPlayerTeamPacket.createRemovePacket((PlayerTeam) team.getPlatformTeam()));
}
@Override
public void updateTeam(@NonNull Team team) {
updateTeamProperties(team);
sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket((PlayerTeam) team.getPlatformTeam(), false));
}
private void updateTeamProperties(@NonNull Team team) {
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
t.setAllowFriendlyFire((team.getOptions() & 0x01) != 0);
t.setSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0);
t.setColor(colors[Math.min(team.getColor().ordinal(), colors.length - 1)]);
t.setCollisionRule(collisions[team.getCollision().ordinal()]);
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
t.setPlayerPrefix(team.getPrefix().convert());
t.setPlayerSuffix(team.getSuffix().convert());
}
@Override
@SneakyThrows
@NotNull
public Object onPacketSend(@NonNull Object packet) {
if (packet instanceof ClientboundSetDisplayObjectivePacket display) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player, display.getSlot().ordinal(), display.getObjectiveName());
}
if (packet instanceof ClientboundSetObjectivePacket objective) {
TAB.getInstance().getFeatureManager().onObjective(player, objective.getMethod(), objective.getObjectiveName());
}
if (packet instanceof ClientboundSetPlayerTeamPacket team) {
int action = getMethod(team);
if (action != TeamAction.UPDATE) {
Collection<String> players = team.getPlayers();
if (players != null) {
return teamConstructor.newInstance(
team.getName(),
action,
team.getParameters(),
onTeamPacket(action, team.getName(), players)
);
}
}
}
return packet;
}
private int getMethod(@NonNull ClientboundSetPlayerTeamPacket team) {
if (team.getTeamAction() == ClientboundSetPlayerTeamPacket.Action.ADD) {
return 0;
} else if (team.getTeamAction() == ClientboundSetPlayerTeamPacket.Action.REMOVE) {
return 1;
} else if (team.getPlayerAction() == ClientboundSetPlayerTeamPacket.Action.ADD) {
return 3;
} else if (team.getPlayerAction() == ClientboundSetPlayerTeamPacket.Action.REMOVE) {
return 4;
} else {
return 2;
}
}
/**
* Sends the packet to the player.
*
* @param packet
* Packet to send
*/
private void sendPacket(@NotNull Packet<?> packet) {
((CraftPlayer)player.getPlayer()).getHandle().connection.send(packet);
}
}
@@ -0,0 +1,216 @@
package me.neznamy.tab.platforms.bukkit.v26_2;
import com.google.common.collect.ImmutableMultimap;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import com.mojang.authlib.properties.PropertyMap;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundTabListPacket;
import net.minecraft.world.level.GameType;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.*;
/**
* TabList implementation using direct mojang-mapped code.
*/
public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
private static final Field PLAYERS = ReflectionUtils.getOnlyField(ClientboundPlayerInfoUpdatePacket.class, List.class);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> addPlayer = EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateDisplayName = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateLatency = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateGameMode = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListed = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListOrder = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LIST_ORDER);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateHat = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_HAT);
/**
* Constructs new instance.
*
* @param player
* Player this tablist will belong to
*/
public NMSPacketTabList(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
public void removeEntry(@NonNull UUID entry) {
sendPacket(new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry)));
}
@Override
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
sendPacket(updateDisplayName, entry, "", null, false, 0, 0, displayName, 0, false);
}
@Override
public void updateLatency(@NonNull UUID entry, int latency) {
sendPacket(updateLatency, entry, "", null, false, latency, 0, null, 0, false);
}
@Override
public void updateGameMode(@NonNull UUID entry, int gameMode) {
sendPacket(updateGameMode, entry, "", null, false, 0, gameMode, null, 0, false);
}
@Override
public void updateListed(@NonNull UUID entry, boolean listed) {
sendPacket(updateListed, entry, "", null, listed, 0, 0, null, 0, false);
}
@Override
public void updateListOrder(@NonNull UUID entry, int listOrder) {
sendPacket(updateListOrder, entry, "", null, false, 0, 0, null, listOrder, false);
}
@Override
public void updateHat(@NonNull UUID entry, boolean showHat) {
sendPacket(updateHat, entry, "", null, false, 0, 0, null, 0, showHat);
}
@Override
public void addEntry0(@NonNull Entry entry) {
sendPacket(addPlayer, entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.isListed(), entry.getLatency(),
entry.getGameMode(), entry.getDisplayName(), entry.getListOrder(), entry.isShowHat());
}
@Override
public void setPlayerListHeaderFooter0(@NonNull TabComponent header, @NonNull TabComponent footer) {
sendPacket(new ClientboundTabListPacket(header.convert(), footer.convert()));
}
@Override
@Nullable
public Skin getSkin() {
Collection<Property> properties = ((CraftPlayer)player.getPlayer()).getProfile().properties().get(TEXTURES_PROPERTY);
if (properties.isEmpty()) return null; // Offline mode
Property property = properties.iterator().next();
return new Skin(property.value(), property.signature());
}
@Override
@NotNull
@SneakyThrows
public Object onPacketSend(@NonNull Object packet) {
if (packet instanceof ClientboundTabListPacket tablist) {
if (header == null || footer == null) return packet;
if (tablist.header() != header.convert() || tablist.footer() != footer.convert()) {
return new ClientboundTabListPacket(header.convert(), footer.convert());
}
}
if (packet instanceof ClientboundPlayerInfoUpdatePacket info) {
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = info.actions();
List<ClientboundPlayerInfoUpdatePacket.Entry> updatedList = new ArrayList<>();
boolean rewritePacket = false;
for (ClientboundPlayerInfoUpdatePacket.Entry nmsData : info.entries()) {
boolean rewriteEntry = false;
Component displayName = nmsData.displayName();
int latency = nmsData.latency();
int gameMode = nmsData.gameMode().getId();
boolean listed = nmsData.listed();
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) {
TabComponent forcedDisplayName = getForcedDisplayNames().get(nmsData.profileId());
if (forcedDisplayName != null && forcedDisplayName.convert() != displayName) {
displayName = forcedDisplayName.convert();
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE)) {
if (getBlockedSpectators().contains(nmsData.profileId()) && gameMode == 3) {
gameMode = 0;
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY)) {
if (getForcedLatency() != null) {
latency = getForcedLatency();
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED)) {
if (allPlayersHidden && nmsData.profileId().getMostSignificantBits() != 0) { // Filter out layout entries
listed = false;
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
TAB.getInstance().getFeatureManager().onEntryAdd(player, nmsData.profileId(), nmsData.profile().name());
}
updatedList.add(rewriteEntry ? new ClientboundPlayerInfoUpdatePacket.Entry(
nmsData.profileId(), nmsData.profile(), listed, latency, GameType.byId(gameMode), displayName,
nmsData.showHat(), nmsData.listOrder(), nmsData.chatSession()
) : nmsData);
}
if (rewritePacket) {
ClientboundPlayerInfoUpdatePacket newPacket = new ClientboundPlayerInfoUpdatePacket(actions, Collections.emptyList());
PLAYERS.set(newPacket, updatedList);
return newPacket;
}
}
return packet;
}
@SneakyThrows
private void sendPacket(@NonNull EnumSet<ClientboundPlayerInfoUpdatePacket.Action> action, @NonNull UUID id, @NonNull String name, @Nullable Skin skin,
boolean listed, int latency, int gameMode, @Nullable TabComponent displayName, int listOrder, boolean showHat) {
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(action, Collections.emptyList());
PLAYERS.set(packet, Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.Entry(
id,
action.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER) ? createProfile(id, name, skin) : null,
listed,
latency,
GameType.byId(gameMode),
displayName == null ? null : displayName.convert(),
showHat,
listOrder,
null
)));
sendPacket(packet);
}
/**
* Creates GameProfile from given parameters.
*
* @param id
* Profile ID
* @param name
* Profile name
* @param skin
* Player skin
* @return GameProfile from given parameters
*/
@NotNull
private GameProfile createProfile(@NonNull UUID id, @NonNull String name, @Nullable Skin skin) {
ImmutableMultimap.Builder<String, Property> builder = ImmutableMultimap.builder();
if (skin != null) {
builder.put(TEXTURES_PROPERTY, new Property(TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
}
return new GameProfile(id, name, new PropertyMap(builder.build()));
}
/**
* Sends the packet to the player.
*
* @param packet
* Packet to send
*/
private void sendPacket(@NotNull Packet<?> packet) {
((CraftPlayer)player.getPlayer()).getHandle().connection.send(packet);
}
}
@@ -0,0 +1,35 @@
package me.neznamy.tab.platforms.bukkit.v26_2;
import io.netty.channel.Channel;
import me.neznamy.tab.shared.platform.NettyTabListEntryTracker;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
/**
* Implementation of TabListEntryTracker.
*/
public class NMSTabListEntryTracker extends NettyTabListEntryTracker {
public NMSTabListEntryTracker(@NotNull Channel channel) {
super(channel);
}
@Override
public void onPacketSend(@NotNull Object packet) {
if (packet instanceof ClientboundPlayerInfoRemovePacket remove) {
for (UUID id : remove.profileIds()) {
tablistEntries.remove(id);
}
}
if (packet instanceof ClientboundPlayerInfoUpdatePacket update) {
if (update.actions().contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
for (ClientboundPlayerInfoUpdatePacket.Entry nmsData : update.entries()) {
tablistEntries.add(nmsData.profileId());
}
}
}
}
}
@@ -2,11 +2,10 @@ package me.neznamy.tab.platforms.bungeecord;
import com.google.common.collect.Lists;
import lombok.NonNull;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.shared.Limitations;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.protocol.data.NumberFormat;
@@ -19,7 +18,6 @@ import net.md_5.bungee.protocol.util.Either;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
@@ -31,9 +29,6 @@ import java.util.Optional;
*/
public class BungeeScoreboard extends SafeScoreboard<BungeeTabPlayer> {
private static final Optional<Integer>[] colors = Arrays.stream(EnumChatFormat.values()).map(e -> Optional.of(Math.min(e.ordinal(), 15))).toArray(Optional[]::new);
private static final Optional<Integer> optionalZero = Optional.of(0);
/**
* Constructs new instance with given parameter
*
@@ -108,7 +103,7 @@ public class BungeeScoreboard extends SafeScoreboard<BungeeTabPlayer> {
@Override
public void unregisterTeam(@NonNull Team team) {
sendTeamPacket(team, (byte) TeamAction.REMOVE);
player.sendPacket(new net.md_5.bungee.protocol.packet.Team(team.getName()));
}
@Override
@@ -117,6 +112,14 @@ public class BungeeScoreboard extends SafeScoreboard<BungeeTabPlayer> {
}
private void sendTeamPacket(@NonNull Team team, byte action) {
Optional<Integer> color;
if (player.getVersion().getNetworkId() >= ProtocolVersion.V26_2.getNetworkId()) {
color = Optional.of(Math.min(team.getColor().ordinal(), 15));
} else if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_13.getNetworkId()) {
color = Optional.of(team.getColor().ordinal());
} else {
color = Optional.of(0);
}
player.sendPacket(new net.md_5.bungee.protocol.packet.Team(
team.getName(),
action,
@@ -125,7 +128,7 @@ public class BungeeScoreboard extends SafeScoreboard<BungeeTabPlayer> {
either(team.getSuffix(), Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13),
convertVisibility(team.getVisibility()),
convertCollision(team.getCollision()),
player.getVersion().getNetworkId() >= ProtocolVersion.V1_13.getNetworkId() ? colors[team.getColor().ordinal()] : optionalZero,
color,
(byte) team.getOptions(),
team.getPlayers().toArray(new String[0])
));
+3 -3
View File
@@ -14,13 +14,13 @@ repositories {
maven("https://repo.papermc.io/repository/maven-public/") // Adventure
}
val minecraftVersion = "26.2-pre-1"
val minecraftVersion = "26.2"
// Fabric API versions for each Minecraft version for easier backporting
// Official website (for updating in the future): https://fabricmc.net/develop/
val fabricApiVersions = mapOf(
"26.2-pre-1" to "0.149.2+26.2",
"26.1.2" to "0.146.1+26.1.2"
"26.2" to "0.150.3+26.2",
"26.1.2" to "0.150.0+26.1.2"
)
dependencies {
+1 -1
View File
@@ -14,7 +14,7 @@
"fabric-api-base": "*",
"java": ">=25",
"minecraft": [
"26.2-pre.1"
"26.2"
]
},
"recommends": {
+15 -6
View File
@@ -7,6 +7,12 @@ plugins {
id("com.gradleup.shadow")
}
val brokenPlatformPaths = setOf(
":bukkit:paper_1_20_5",
":bukkit:paper_1_21_2",
":bukkit:paper_1_21_4"
)
val platformPaths = setOf(
":bukkit",
":bukkit:paper_1_20_5",
@@ -14,6 +20,7 @@ val platformPaths = setOf(
":bukkit:paper_1_21_4",
":bukkit:paper_1_21_9",
":bukkit:paper_1_21_11",
":bukkit:paper_26_2",
":bukkit:v1_7_R4",
":bukkit:v1_8_R3",
":bukkit:v1_12_R1",
@@ -35,6 +42,7 @@ val platformPaths = setOf(
":bukkit:v1_21_R6",
":bukkit:v1_21_R7",
":bukkit:v26_1",
":bukkit:v26_2",
":bungeecord",
":velocity"
)
@@ -45,12 +53,13 @@ val moddedPaths = setOf(
// ":forge"
)
val brokenPlatforms: List<Project> = brokenPlatformPaths.map { rootProject.project(it) }
val platforms: List<Project> = platformPaths.map { rootProject.project(it) }
val moddedPlatforms: List<Project> = moddedPaths.map { rootProject.project(it) }
tasks {
shadowJar {
archiveFileName.set("TAB v${project.version} - Fabric, NeoForge.jar")
archiveFileName.set("TAB v${project.version}.jar")
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
fun registerPlatform(project: Project, jarTask: AbstractArchiveTask) {
@@ -70,9 +79,9 @@ tasks {
}
}
val shadowJarVanilla = register<ShadowJar>("shadowJarVanilla") {
description = "Shadows only vanilla platforms, without any modded platforms that require Java 25+."
archiveFileName.set("TAB v${project.version} - Vanilla.jar")
val shadowJarBrokenPaper = register<ShadowJar>("shadowJarBrokenPaper") {
description = "Shadows only Paper versions 1.20.5 - 1.21.4, which break if jar has classes compiled with Java 24+."
archiveFileName.set("TAB v${project.version} - Paper 1.20.5 - 1.21.4.jar")
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
fun registerPlatform(project: Project, jarTask: AbstractArchiveTask) {
@@ -81,11 +90,11 @@ tasks {
from(zipTree(jarTask.archiveFile))
}
platforms.forEach { p ->
brokenPlatforms.forEach { p ->
val task = p.tasks.named<ShadowJar>("shadowJar").get()
registerPlatform(p, task)
}
}
build.get().dependsOn(shadowJar, shadowJarVanilla)
build.get().dependsOn(shadowJar, shadowJarBrokenPaper)
}
+2 -1
View File
@@ -15,11 +15,12 @@ repositories {
maven("https://repo.papermc.io/repository/maven-public/") // Adventure
}
val minecraftVersion = "26.1.2"
val minecraftVersion = "26.2"
// NeoForge API versions for each Minecraft version for easier backporting
// Official website (for updating in the future): https://projects.neoforged.net/neoforged/neoforge
val neoforgeApiVersions = mapOf(
"26.2" to "26.2.0.1-beta",
"26.1.2" to "26.1.2.17-beta"
)
@@ -3,16 +3,17 @@ package me.neznamy.tab.platforms.neoforge;
import lombok.NonNull;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.numbers.FixedFormat;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.Scoreboard;
import net.minecraft.world.scores.TeamColor;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Optional;
/**
@@ -20,7 +21,7 @@ import java.util.Optional;
*/
public class NeoForgeScoreboard extends SafeScoreboard<NeoForgeTabPlayer> {
private static final ChatFormatting[] formats = ChatFormatting.values();
private static final Optional<TeamColor>[] colors = Arrays.stream(TeamColor.values()).map(Optional::of).toArray(Optional[]::new);
private static final net.minecraft.world.scores.Team.CollisionRule[] collisions = net.minecraft.world.scores.Team.CollisionRule.values();
private static final net.minecraft.world.scores.Team.Visibility[] visibilities = net.minecraft.world.scores.Team.Visibility.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
@@ -113,7 +114,7 @@ public class NeoForgeScoreboard extends SafeScoreboard<NeoForgeTabPlayer> {
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
t.setAllowFriendlyFire((team.getOptions() & 0x01) != 0);
t.setSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0);
t.setColor(formats[team.getColor().ordinal()]);
t.setColor(colors[Math.min(team.getColor().ordinal(), colors.length - 1)]);
t.setCollisionRule(collisions[team.getCollision().ordinal()]);
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
t.setPlayerPrefix(team.getPrefix().convert());
@@ -15,6 +15,6 @@ description = '''@description@'''
[[dependencies.tab]]
modId = "minecraft"
type = "required"
versionRange = "[26.1, 26.1.2]"
versionRange = "[26.2]"
ordering = "NONE"
side = "SERVER"
+2
View File
@@ -37,6 +37,7 @@ include(":bukkit:paper_1_21_2")
include(":bukkit:paper_1_21_4")
include(":bukkit:paper_1_21_9")
include(":bukkit:paper_1_21_11")
include(":bukkit:paper_26_2")
include(":bukkit:v1_7_R4")
include(":bukkit:v1_8_R3")
include(":bukkit:v1_12_R1")
@@ -58,6 +59,7 @@ include(":bukkit:v1_21_R5")
include(":bukkit:v1_21_R6")
include(":bukkit:v1_21_R7")
include(":bukkit:v26_1")
include(":bukkit:v26_2")
include(":bungeecord")
include(":fabric")
include(":neoforge")
@@ -11,6 +11,7 @@ import org.jetbrains.annotations.NotNull;
public enum ProtocolVersion {
UNKNOWN,
V26_2 (776),
V26_1_2 (775),
V26_1_1 (775),
V26_1 (775),
@@ -45,9 +45,11 @@ public class LegacyTextComponent extends TabTextComponent {
modifier.setStrikethrough(true);
} else if (color == TabTextColor.OBFUSCATED) {
modifier.setObfuscated(true);
} else if (color == TabTextColor.RESET) {
return modifier; // Return whatever we have
} else {
modifier.setColor(color);
break; // color found, do not continue anymore
return modifier; // color found, do not continue anymore
}
}
}
@@ -116,7 +116,7 @@ public class ModernConverter {
config.removeOption("scoreboard.use-numbers");
config.removeOption("scoreboard.static-number");
});
converters.put(3, config -> config.set("proxy-support.channel-name-suffix", "TAB"));
converters.put(3, config -> config.set("proxy-support.channel-name-suffix", "custom"));
converters.put(4, config -> {
ConfigurationSection layout = config.getConfigurationSection("layout");
ConfigurationSection layouts = layout.getConfigurationSection("layouts");
@@ -64,7 +64,7 @@ public class DataManager {
if (globalPlayerListConfiguration == null) return null;
for (ServerGroup group : serverGroups.values()) {
for (String serverDefinition : group.getPatterns()) {
if (matchesServerPattern(server.getName(), serverDefinition)) {
if (matchesPattern(server.getName(), serverDefinition)) {
return group;
}
}
@@ -77,32 +77,32 @@ public class DataManager {
}
/**
* Checks if a server name matches the given pattern. Supports:
* Checks if name of an object (server / world) matches the given pattern. Supports:
* - Exact match: "lobby"
* - Prefix wildcard: "lobby*"
* - Suffix wildcard: "*lobby"
* - Regex pattern: "regex:lobby-[0-9]+"
*
* @param serverName
* Server name to check
* @param objectName
* Server / world name to check
* @param pattern
* Pattern to match against
* @return {@code true} if server name matches the pattern, {@code false} otherwise
* @return {@code true} if name matches the pattern, {@code false} otherwise
*/
public boolean matchesServerPattern(@NotNull String serverName, @NotNull String pattern) {
public boolean matchesPattern(@NotNull String objectName, @NotNull String pattern) {
if (pattern.startsWith("regex:")) {
try {
return Pattern.compile(pattern.substring(6)).matcher(serverName).matches();
return Pattern.compile(pattern.substring(6)).matcher(objectName).matches();
} catch (PatternSyntaxException e) {
// Invalid regex pattern, treat as literal match
return serverName.equals(pattern);
return objectName.equals(pattern);
}
} else if (pattern.endsWith("*")) {
return serverName.startsWith(pattern.substring(0, pattern.length()-1));
return objectName.startsWith(pattern.substring(0, pattern.length()-1));
} else if (pattern.startsWith("*")) {
return serverName.endsWith(pattern.substring(1));
return objectName.endsWith(pattern.substring(1));
} else {
return serverName.equals(pattern);
return objectName.equals(pattern);
}
}
}
@@ -227,7 +227,7 @@ public class BelowName extends RefreshableFeature implements JoinListener, QuitL
if (viewer.belowNameData.disabled.get()) return;
if (viewer.server != scoreHolder.server || viewer.world != scoreHolder.world) return; // Viewer definitely cannot see this player in game
if (viewer.canSee(scoreHolder)) {
if (fancyValue.isEmpty() && viewer.getVersionId() > ProtocolVersion.V26_1_2.getNetworkId()) { // TODO change this to use V26_2 once added
if (fancyValue.isEmpty() && viewer.getVersionId() >= ProtocolVersion.V26_2.getNetworkId()) {
viewer.getScoreboard().removeScore(OBJECTIVE_NAME, scoreHolder.getNickname());
} else {
viewer.getScoreboard().setScore(
@@ -7,7 +7,6 @@ import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.api.bossbar.BossBar;
import me.neznamy.tab.api.bossbar.BossBarManager;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.cpu.ThreadExecutor;
import me.neznamy.tab.shared.cpu.TimedCaughtTask;
import me.neznamy.tab.shared.features.ToggleManager;
@@ -65,11 +64,16 @@ public class BossBarManagerImpl extends RefreshableFeature implements BossBarMan
public void load() {
TAB.getInstance().getPlatform().registerCustomCommand(configuration.getToggleCommand().replaceFirst("/", ""), (p, args) -> {
if (!isActive()) return;
if (p.hasPermission(TabConstants.Permission.COMMAND_BOSSBAR_TOGGLE)) {
toggleBossBar(p, true);
String[] forwardedArgs;
if (args.length == 0) {
forwardedArgs = new String[]{"bossbar", "toggle"};
} else {
p.sendMessage(TAB.getInstance().getConfiguration().getMessages().getNoPermission());
forwardedArgs = new String[args.length + 2];
forwardedArgs[0] = "bossbar";
forwardedArgs[1] = "toggle";
System.arraycopy(args, 0, forwardedArgs, 2, args.length);
}
TAB.getInstance().getCommand().execute(p, forwardedArgs);
});
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
onJoin(p);
@@ -315,7 +315,7 @@ public class GlobalPlayerList extends RefreshableFeature implements JoinListener
*/
private boolean matchesAnyPattern(@NotNull String serverName, @NotNull List<String> patterns) {
for (String pattern : patterns) {
if (TAB.getInstance().getDataManager().matchesServerPattern(serverName, pattern)) {
if (TAB.getInstance().getDataManager().matchesPattern(serverName, pattern)) {
return true;
}
}
@@ -206,6 +206,9 @@ public class NameTag extends TabFeature implements NameTagManager, JoinListener,
} else {
registerTeam(p);
}
if (proxy != null) {
proxyHandler.sendProxyMessage(p);
}
}
/**
@@ -340,7 +343,7 @@ public class NameTag extends TabFeature implements NameTagManager, JoinListener,
p.getNametag() == null ? "NULL" : "\"" + p.getNametag().getPrefix() + "\"",
p.getNametag() == null ? "NULL" : lastColorCache.get(p.getNametag().getPrefix()).getLastStyle().toEnumChatFormat().name(),
p.getNametag() == null ? "NULL" : "\"" + p.getNametag().getSuffix() + "\"",
"N/A"
p.getNametag() == null ? "NULL" : String.valueOf(p.getNametag().isDisabled())
)).collect(Collectors.toList()));
}
map.put("current values for all players (without applying relational placeholders)", DumpUtils.tableToLines(header, players));
@@ -28,7 +28,8 @@ public class NameTagProxyHandler implements ProxyFeature {
player.teamData.teamName,
player.teamData.prefix.get(),
player.teamData.suffix.get(),
player.teamData.getTeamVisibility(player) ? Scoreboard.NameVisibility.ALWAYS : Scoreboard.NameVisibility.NEVER
player.teamData.getTeamVisibility(player) ? Scoreboard.NameVisibility.ALWAYS : Scoreboard.NameVisibility.NEVER,
player.teamData.isDisabled()
));
}
}
@@ -52,6 +53,7 @@ public class NameTagProxyHandler implements ProxyFeature {
@Override
public void onJoin(@NotNull ProxyPlayer player) {
if (player.getNametag() == null) return; // Player not loaded yet
if (player.getNametag().isDisabled()) return;
for (TabPlayer viewer : feature.getOnlinePlayers().getPlayers()) {
viewer.teamData.registerTeam(
player,
@@ -36,6 +36,7 @@ public class NameTagProxyPlayerData extends ProxyMessage {
@NotNull private final String suffix;
@NotNull private final Scoreboard.NameVisibility nameVisibility;
@Nullable private String resolvedTeamName;
private final boolean disabled;
/**
* Creates new instance and reads data from byte input.
@@ -53,6 +54,7 @@ public class NameTagProxyPlayerData extends ProxyMessage {
prefix = in.readUTF();
suffix = in.readUTF();
nameVisibility = Scoreboard.NameVisibility.getByName(in.readUTF());
disabled = in.readBoolean();
}
@NotNull
@@ -68,6 +70,7 @@ public class NameTagProxyPlayerData extends ProxyMessage {
out.writeUTF(prefix);
out.writeUTF(suffix);
out.writeUTF(nameVisibility.toString());
out.writeBoolean(disabled);
}
@Override
@@ -95,7 +98,28 @@ public class NameTagProxyPlayerData extends ProxyMessage {
TabComponent lastColor = feature.getLastColorCache().get(this.prefix);
TabComponent suffix = feature.getSuffixCache().get(this.suffix);
for (TabPlayer viewer : feature.getOnlinePlayers().getPlayers()) {
if (oldData != null && !oldData.disabled && disabled) {
// Enabled to disabled
viewer.teamData.unregisterTeam(target);
continue;
}
if (oldData != null && oldData.disabled && !disabled) {
// Disabled to enabled
viewer.teamData.registerTeam(
target,
resolvedTeamName,
prefix,
suffix,
nameVisibility,
Scoreboard.CollisionRule.ALWAYS,
Collections.singletonList(target.getNickname()),
feature.getTeamOptions(),
lastColor.getLastStyle().toEnumChatFormat()
);
continue;
}
if (oldData != null && resolvedTeamName.equals(oldData.resolvedTeamName)) {
// Property update
if (viewer.teamData.hasTeamRegistered(target)) {
viewer.getScoreboard().updateTeam(
oldData.teamName,
@@ -108,6 +132,7 @@ public class NameTagProxyPlayerData extends ProxyMessage {
);
}
} else {
// Team rename
viewer.teamData.unregisterTeam(target);
viewer.teamData.registerTeam(
target,
@@ -189,6 +189,9 @@ public class PlayerList extends RefreshableFeature implements TabListFormatManag
} else {
formatPlayerForEveryone(p, true);
}
if (proxy != null) {
sendProxyMessage(p);
}
}
@NotNull
@@ -284,10 +287,10 @@ public class PlayerList extends RefreshableFeature implements TabListFormatManag
for (ProxyPlayer proxied : proxy.getProxyPlayers().values()) {
players.add(Arrays.asList(
"[Proxy] " + proxied.getName(),
proxied.getTabFormat() == null ? "null" : "\"" + proxied.getTabFormat().getPrefix() + "\"",
proxied.getTabFormat() == null ? "null" : "\"" + proxied.getTabFormat().getName() + "\"",
proxied.getTabFormat() == null ? "null" : "\"" + proxied.getTabFormat().getSuffix() + "\"",
"N/A"
proxied.getTabFormat() == null ? "NULL" : "\"" + proxied.getTabFormat().getPrefix() + "\"",
proxied.getTabFormat() == null ? "NULL" : "\"" + proxied.getTabFormat().getName() + "\"",
proxied.getTabFormat() == null ? "NULL" : "\"" + proxied.getTabFormat().getSuffix() + "\"",
proxied.getTabFormat() == null ? "NULL" : String.valueOf(proxied.getTabFormat().isDisabled())
));
}
}
@@ -424,7 +427,8 @@ public class PlayerList extends RefreshableFeature implements TabListFormatManag
player.tablistData.prefix.get(),
player.tablistData.name.get(),
player.tablistData.suffix.get(),
TabComponent.empty() // This instance is for writing, parsed is not needed on this side
TabComponent.empty(), // This instance is for writing, parsed is not needed on this side
player.tablistData.disabled.get()
));
}
}
@@ -11,6 +11,7 @@ import me.neznamy.tab.shared.features.proxy.ProxyPlayer;
import me.neznamy.tab.shared.features.proxy.ProxySupport;
import me.neznamy.tab.shared.features.proxy.QueuedData;
import me.neznamy.tab.shared.features.proxy.message.ProxyMessage;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
@@ -47,6 +48,9 @@ public class PlayerListProxyPlayerData extends ProxyMessage {
/** TabComponent of the format (parsed version of prefix + name + suffix) */
@NotNull private final TabComponent formatComponent;
/** Whether the player has the feature disabled with condition or not */
private final boolean disabled;
/**
* Creates new instance and reads data from byte input.
*
@@ -64,6 +68,7 @@ public class PlayerListProxyPlayerData extends ProxyMessage {
name = in.readUTF();
suffix = in.readUTF();
formatComponent = feature.getCache().get(prefix + name + suffix);
disabled = in.readBoolean();
}
@Override
@@ -74,6 +79,7 @@ public class PlayerListProxyPlayerData extends ProxyMessage {
out.writeUTF(prefix);
out.writeUTF(name);
out.writeUTF(suffix);
out.writeBoolean(disabled);
}
@Override
@@ -91,8 +97,18 @@ public class PlayerListProxyPlayerData extends ProxyMessage {
TAB.getInstance().debug("Dropping tabformat update action for player " + target.getName() + " due to newer action already being present");
return;
}
PlayerListProxyPlayerData oldData = target.getTabFormat();
target.setTabFormat(this);
if (target.getConnectionState() == ProxyPlayer.ConnectionState.CONNECTED) {
if (disabled) {
if (!oldData.disabled) {
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
viewer.getTabList().updateDisplayName(target.getTablistId(), null);
}
}
return;
}
feature.formatPlayerForEveryone(target);
}
}
@@ -45,7 +45,7 @@ public class ProxySupportConfiguration {
type = "PLUGIN";
}
String channelName = ProjectVariables.PLUGIN_NAME + "_3_" + section.getString("channel-name-suffix", "TAB");
String channelName = ProjectVariables.PLUGIN_NAME + "_4_" + section.getString("channel-name-suffix", "custom");
ConfigurationSection plugin = section.getConfigurationSection("plugin");
String pluginName = plugin.getString("name", "RedisBungee");
@@ -70,11 +70,16 @@ public class ScoreboardManagerImpl extends RefreshableFeature implements Scorebo
public void load() {
TAB.getInstance().getPlatform().registerCustomCommand(configuration.getToggleCommand().replaceFirst("/", ""), (p, args) -> {
if (!isActive()) return;
if (p.hasPermission(TabConstants.Permission.COMMAND_SCOREBOARD_TOGGLE)) {
toggleScoreboard(p, true);
String[] forwardedArgs;
if (args.length == 0) {
forwardedArgs = new String[]{"scoreboard", "toggle"};
} else {
p.sendMessage(TAB.getInstance().getConfiguration().getMessages().getNoPermission());
forwardedArgs = new String[args.length + 2];
forwardedArgs[0] = "scoreboard";
forwardedArgs[1] = "toggle";
System.arraycopy(args, 0, forwardedArgs, 2, args.length);
}
TAB.getInstance().getCommand().execute(p, forwardedArgs);
});
for (Entry<String, ScoreboardDefinition> entry : configuration.getScoreboards().entrySet()) {
String scoreboardName = entry.getKey();
+1 -1
View File
@@ -242,7 +242,7 @@ mysql:
# https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Multi-server-support
proxy-support:
enabled: true
channel-name-suffix: "TAB"
channel-name-suffix: "custom"
# Supported types: PLUGIN, REDIS, RABBITMQ
type: PLUGIN
plugin: