Compare commits

...
34 Commits
Author SHA1 Message Date
NEZNAMY a748a6b2c0 6.2.1 2026-10-18 17:03:58 +02:00
NEZNAMY 5f8eb14c92 [Placeholders] Apply dynamic placeholders to existing placeholders to override them on API use (#1733) 2026-10-17 14:12:43 +02:00
NEZNAMY 1fc5034a77 [Dump Command] Fix NPE when config was not converted correctly 2026-10-15 18:49:37 +02:00
NEZNAMY 5130d1659e [Placeholders] Fix NPE (#1733) 2026-10-13 19:28:08 +02:00
NEZNAMY f68cba21f0 [Bukkit] Fix Canvas detection (once again) 2026-10-13 17:25:34 +02:00
NEZNAMY 6c7d1eba32 [Configuration] Properly detect groups with "." in their name 2026-10-11 09:20:50 +02:00
NEZNAMY 9713db862a [Wiki] Monthly wiki backup for changes made in September 2026 2026-10-07 17:50:39 +02:00
NEZNAMY fbee22d785 Merge pull request #1731 from freddycor/master
Fixed 1.8+ clients not seeing 1.7 players correct names format
2026-10-02 18:51:55 +02:00
freddycor 0ee39728e5 Fixed 1.8+ clients not seeing 1.7 players correct names. 2026-10-02 17:40:49 +02:00
NEZNAMY 37aa69b64e [Teams] Cache teams by entries for O(1) lookup complexity 2026-09-30 19:54:30 +02:00
NEZNAMY 22ddad85ab [Belowname / Playerlist objectives] Calculate value once instead of for every viewer 2026-09-30 17:37:06 +02:00
NEZNAMY 261a81586f [Placeholders] Use correct "continue" operator instead of "return" 2026-09-30 17:19:17 +02:00
NEZNAMY f0abc99b61 [Layout] Properly format / unformat original tablist entries when layout is set / reset (#1728) 2026-09-28 19:27:52 +02:00
NEZNAMY b669871ff1 [Forge] Update to 26.3 2026-09-22 19:38:53 +02:00
NEZNAMY 174c58b642 Update Gradle to 9.7.1 2026-09-21 19:41:44 +02:00
NEZNAMY 28431ef136 6.2.1-SNAPSHOT 2026-09-21 17:59:13 +02:00
NEZNAMY 5f25bf97ba [Description] Update badges 2026-09-17 17:48:28 +02:00
NEZNAMY f6359d2f77 6.2.0 2026-09-17 16:26:18 +02:00
NEZNAMY 727c500b17 [Bukkit] Update supported versions message 2026-09-16 20:40:47 +02:00
NEZNAMY bebf2988fb [Bukkit] Mark Spigot 26.3 as compatible 2026-09-16 20:37:36 +02:00
NEZNAMY e3cde1968b [NeoForge] Update to support 26.3 as well 2026-09-16 20:26:49 +02:00
NEZNAMY ef6b668c70 [Fabric] Change "players" field access to support both 26.2 and 26.3 2026-09-16 20:04:56 +02:00
NEZNAMY fef62e796b [Bukkit] Drop support for 1.19.1 - 1.19.3 2026-09-16 18:04:28 +02:00
NEZNAMY 0d4b1dbce2 [Bukkit] Mark Paper 26.3 as compatible 2026-09-16 18:01:42 +02:00
NEZNAMY 43d210dbf8 [Bukkit] Update to new bStats chart 2026-09-15 19:03:33 +02:00
NEZNAMY e0d2209e97 [API] Mark redundant method as deprecated 2026-09-15 18:30:02 +02:00
NEZNAMY 720cae3e94 [Fabric] Update to 26.3 2026-09-15 17:56:00 +02:00
NEZNAMY f2ea34a717 [Conditions] Fix NPE during config conversion 2026-09-15 17:51:27 +02:00
NEZNAMY 2c33747e1b [Placeholders] Make method synchronized to avoid concurrency issues (#1724, #1725) 2026-09-12 11:32:36 +02:00
NEZNAMY 5f9d72eb90 Include forgotten change 2026-09-12 10:47:42 +02:00
NEZNAMY 3abcc1d96f [Fabric] Update to 26.3-rc1 2026-09-12 10:35:59 +02:00
NEZNAMY 7d458422c7 [Belowname / Playerlist objective] Only create the features if they are actually enabled (got broken by a PR) 2026-09-12 10:28:24 +02:00
NEZNAMY 0feee14bfb 6.2.0-SNAPSHOT 2026-09-10 18:53:28 +02:00
NEZNAMY 0023cfcafb [Description] Update badges 2026-09-06 17:57:35 +02:00
58 changed files with 262 additions and 1332 deletions

No files matched your search

+1 -1
View File
@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="92" height="20" role="img" aria-label="Release: 6.1.2"><title>Release: 6.1.2</title><filter id="blur"><feGaussianBlur 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"/></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"><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="275" y="150" fill-opacity=".8" filter="url(#blur)" textLength="430">Release</text><text x="275" y="150" fill-opacity=".3" textLength="430">Release</text></g><text x="275" y="140" textLength="430">Release</text></g><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="715" y="150" fill-opacity=".8" filter="url(#blur)" textLength="290">6.1.2</text><text x="715" y="150" fill-opacity=".3" textLength="290">6.1.2</text></g><text x="715" y="140" textLength="290">6.1.2</text></g></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="92" height="20" role="img" aria-label="Release: 6.2.0"><title>Release: 6.2.0</title><filter id="blur"><feGaussianBlur 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"/></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"><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="275" y="150" fill-opacity=".8" filter="url(#blur)" textLength="430">Release</text><text x="275" y="150" fill-opacity=".3" textLength="430">Release</text></g><text x="275" y="140" textLength="430">Release</text></g><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="715" y="150" fill-opacity=".8" filter="url(#blur)" textLength="290">6.2.0</text><text x="715" y="150" fill-opacity=".3" textLength="290">6.2.0</text></g><text x="715" y="140" textLength="290">6.2.0</text></g></g></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

+1 -1
View File
@@ -274,7 +274,7 @@ if __name__ == "__main__":
]
)
release = "6.1.2"
release = "6.2.0"
download_image(f"https://img.shields.io/badge/Release-{release}-blue.svg", "Badge_Release.svg")
java = "8+"
+12 -3
View File
@@ -13,7 +13,7 @@ TAB does not depend on any other plugins. The experience can however be enhanced
With the introduction of new Minecraft versions, it is becoming harder and harder to support many versions in the same jar, eventually making it impossible since Minecraft 26.1.
**As a general rule, if you want the latest TAB version for your server, please use [Modrinth](https://modrinth.com/plugin/tab-was-taken)'s download filter, where you can select your server software and version and you'll get the latest jar.**
Currently, the latest Minecraft version supported by TAB is **26.2**. When new versions come out, TAB is always updated soon after to support them.
Currently, the latest Minecraft version supported by TAB is **26.3**. When new versions come out, TAB is always updated soon after to support them.
If your server software and version is reasonably popular and TAB hasn't been backported to that version in a very long time, you may [request a backport](https://github.com/NEZNAMY/TAB/issues/new/choose) (unless backports to that version are no longer offered, see below).
Since TAB 6.0.0, Minecraft 1.x started taking hits with each subsequent TAB versions - Bukkit versions started disappearing and **modded platforms no longer offer backports of TAB 6.0.0+ to MC 1.x** (this is because the build scripts had to be massively changed for MC 26+).
@@ -21,8 +21,17 @@ Since TAB 6.0.0, Minecraft 1.x started taking hits with each subsequent TAB vers
The sections below provide **additional** information for each platform.
## Bukkit
TAB 5.5.0 supports all 1.x versions starting with 1.7. For 1.5.2 - 1.6.4, use TAB v5.2.5.
Since TAB 6.0.0, support for unused 1.x versions will continue to disappear with each subsequent Minecraft release.
Since TAB 6.0.0, support for unused 1.x versions started disappearing and will continue to disappear with each subsequent Minecraft release.
Currently, the latest release supports the following versions:
* 1.7.10
* 1.8.8
* 1.12.2
* 1.16.5
* 1.17.1
* 1.18.2
* 1.19.4 - 26.3
If you want a jar with support for a 1.x version that is no longer supported, you can use TAB v5.5.0, which supports all MC 1.x versions. For 1.5.2 - 1.6.4, use TAB v5.2.5.
## BungeeCord / Velocity
For proxies, the plugin is made to work with the latest build.
@@ -15,8 +15,8 @@
* ~Examples~
# About
This feature allows you to see players from other servers, instead of only seeing players on the same server.
If TAB is installed on a backend server, you'll need to set up [Multi server support](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Multi-server-support) for proper linking of servers. On proxy installation, this is not required.
This feature allows you to see players from different servers, instead of only seeing players on the same server. The typical use is to show players under the whole proxy network.
If TAB is installed on a backend server, you'll need to set up [Multi server support](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Multi-server-support) to properly make the servers communicate. On proxy installation, this is not needed, unless you have multiple proxies.
# Configuration
The feature can be configured in **config.yml** under **global-playerlist** section.
@@ -118,6 +118,8 @@ global-playerlist:
- isolatedServer2
```
This is not needed when `isolate-unlisted-servers` is set to `true`, in which case all unlisted servers are isolated by default.
## Seeing all players on the network from some server
If, despite configuring server groups,
you want players to see everyone on the network if they're connected to specific server(s),
+17 -1
View File
@@ -19,7 +19,7 @@
* [Additional note 2 - [1.8 - 1.21.3] Second layer of skin missing](#additional-note-2---18---1213-second-layer-of-skin-missing)
* [Additional note 3 - Entry overlap](#additional-note-3---entry-overlap)
* ~Troubleshooting~
* ~API~
* [API](#api)
* [Examples](#examples)
* [Example 1 - Per-server columns](#example-1---per-server-columns)
* [Example 2 - Per-world playerlist](#example-2---per-world-playerlist)
@@ -284,6 +284,22 @@ Here are a few tips how to do that:
* Remove unnecessary brackets from prefix
# API
*To get started with the API, see [Developer API](https://github.com/NEZNAMY/TAB/wiki/Developer-API) page.*
To access this feature, you'll need to obtain `LayoutManager` instance. Get it using `TabAPI.getInstance().getLayoutManager()`. If this feature is disabled, the method will return `null`.
The `LayoutManager` has the following methods:
* `Layout createNewLayout(String name)` - Creates new layout with given internal name and 80 slots.
* `Layout createNewLayout(String name, int slotCount)` - Creates new layout with given internal name and slot count (for 1.19.3+, <1.19.3 players will still see 80 slots).
* `Layout getLayout(String name)` - Returns layout by given internal name. Works for layouts created in both configuration and through the API.
* `sendLayout(TabPlayer player, Layout layout)` - Sends given layout to the player, overriding configuration. Sending `null` as layout will restore layout from configuration.
After creating a `Layout` using the API or getting it from configuration, you can use the following methods:
* `String getName()` - Returns layout's name
* `addFixedSlot(int slot, String text)` / `addFixedSlot(int slot, String text, String skin)` / `addFixedSlot(int slot, String text, int ping)` / `addFixedSlot(int slot, String text, String skin, int ping)` / `addFixedSlot(int slot, String text, String skin, String ping)` - Adds a fixed slot with gieven properties - slot, text, skin (optional) and ping (optional)
* `addGroup(String condition, int[] slots)` - Adds a new player group with given condition (nullable for no condition) and slots. Condition can be either a conditional expression, or a condition created in config.
# Examples
## Example 1 - Per-server columns
```
+1 -1
View File
@@ -1,5 +1,5 @@
# About the wiki
The wiki explains the functionality of the plugin as of version **6.1.2**.
The wiki explains the functionality of the plugin as of version **6.2.0**.
If your config looks different or is missing some features, you are using an old version of the plugin.
To get the latest version, check [releases](https://github.com/NEZNAMY/TAB/releases/).
Wiki for older plugin versions is not available.
@@ -48,14 +48,12 @@ public interface LayoutManager {
Layout getLayout(@NonNull String name);
/**
* Sends layout to player. Set to {@code null} to make player not see
* any layout, but default TabList instead.
* Sends layout to player. Set to {@code null} to reset layout back to original one based on configuration.
*
* @param player
* Player to send layout to
* @param layout
* Layout to send
* @see #resetLayout(TabPlayer)
*/
void sendLayout(@NonNull TabPlayer player, @Nullable Layout layout);
@@ -65,6 +63,8 @@ public interface LayoutManager {
* @param player
* Player to reset layout for
* @see #sendLayout(TabPlayer, Layout)
* @deprecated Use {@link #sendLayout(TabPlayer, Layout)} with {@code null} instead
*/
@Deprecated
void resetLayout(@NonNull TabPlayer player);
}
+1 -3
View File
@@ -4,7 +4,7 @@ plugins {
allprojects {
group = "me.neznamy"
version = "6.1.3"
version = "6.2.1"
description = "An all-in-one solution that works"
ext.set("id", "tab")
@@ -27,8 +27,6 @@ val platformPaths = setOf(
":bukkit:v1_16_R3",
":bukkit:v1_17_R1",
":bukkit:v1_18_R2",
":bukkit:v1_19_R1",
":bukkit:v1_19_R2",
":bukkit:v1_19_R3",
":bukkit:v1_20_R1",
":bukkit:v1_20_R2",
@@ -14,14 +14,16 @@ import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* Implementation provider using direct Mojang-mapped NMS code for Paper 26.2.
* Implementation provider using direct Mojang-mapped NMS code for Paper 26.2 - 26.3.
*/
@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") ||
ReflectionUtils.classExists("io.canvasmc.canvas.threadedregions.scores.TeamData");
private final boolean isCanvas =
ReflectionUtils.classExists("io.canvasmc.canvas.world.scores.TeamData") ||
ReflectionUtils.classExists("io.canvasmc.canvas.threadedregions.scores.TeamData") ||
ReflectionUtils.classExists("io.canvasmc.canvas.threadedregions.world.scores.TeamData");
@NotNull
private final ComponentConverter<?> componentConverter = new NMSComponentConverter();
@@ -28,7 +28,7 @@ public class BukkitTAB extends JavaPlugin {
Bukkit.getConsoleSender().sendMessage("§c[TAB] Use the other jar in the release instead.");
} else {
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.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, 1.18.2 and 1.19.4 - 26.3");
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());
@@ -96,6 +96,7 @@ public class ServerVersionInfo {
spigotVersions.put(ProtocolVersion.V26_1_1, "v26_1");
spigotVersions.put(ProtocolVersion.V26_1_2, "v26_1");
spigotVersions.put(ProtocolVersion.V26_2, "v26_2");
spigotVersions.put(ProtocolVersion.V26_3, "v26_2");
Map<ProtocolVersion, String> paperVersions = new LinkedHashMap<>();
paperVersions.put(ProtocolVersion.V1_20_5, "paper_1_20_5");
@@ -116,6 +117,7 @@ public class ServerVersionInfo {
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
paperVersions.put(ProtocolVersion.V26_3, "paper_26_2");
if (serverVersion == ProtocolVersion.UNKNOWN) {
throw new IllegalStateException(String.format(
@@ -238,9 +238,9 @@ public class BukkitPlatform implements BackendPlatform {
Metrics metrics = new Metrics(plugin, TabConstants.BSTATS_PLUGIN_ID_BUKKIT);
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.PERMISSION_SYSTEM,
() -> TAB.getInstance().getGroupManager().getPermissionPlugin()));
metrics.addCustomChart(new SimplePie("tab_6_1_0_servers",
metrics.addCustomChart(new SimplePie("tab_6_2_x_servers",
() -> serverVersionInfo.getServerName() + " " + serverVersionInfo.getServerVersion().getFriendlyName()));
metrics.addCustomChart(new SimplePie("tab_6_1_0_package", serverVersionInfo::getImplementationPackage));
metrics.addCustomChart(new SimplePie("tab_6_2_x_package", serverVersionInfo::getImplementationPackage));
}
@Override
-10
View File
@@ -1,10 +0,0 @@
dependencies {
implementation(projects.bukkit)
compileOnly("org.spigotmc:spigot:1.19.2-R0.1-SNAPSHOT") {
// Broken dependencies
exclude("com.mojang", "authlib")
exclude("com.mojang", "logging")
exclude("com.mojang", "datafixerupper")
}
compileOnly("com.mojang:authlib:1.5.25")
}
@@ -1,64 +0,0 @@
package me.neznamy.tab.platforms.bukkit.v1_19_R1;
import me.neznamy.tab.shared.chat.TabStyle;
import me.neznamy.tab.shared.chat.component.object.TabAtlasSprite;
import me.neznamy.tab.shared.chat.component.object.TabObjectComponent;
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
import me.neznamy.tab.shared.chat.component.object.TabPlayerSprite;
import net.minecraft.network.chat.*;
import net.minecraft.resources.MinecraftKey;
import org.jetbrains.annotations.NotNull;
/**
* Component converter using direct NMS code.
*/
public class NMSComponentConverter extends ComponentConverter<IChatBaseComponent> {
@Override
@NotNull
public IChatBaseComponent newTextComponent(@NotNull String text) {
return IChatBaseComponent.b(text);
}
@Override
@NotNull
public IChatBaseComponent newTranslatableComponent(@NotNull String key) {
return IChatBaseComponent.c(key);
}
@Override
@NotNull
public IChatBaseComponent newKeybindComponent(@NotNull String keybind) {
return IChatBaseComponent.d(keybind);
}
@Override
@NotNull
public IChatBaseComponent newObjectComponent(@NotNull TabAtlasSprite sprite) {
return IChatBaseComponent.b(TabObjectComponent.ERROR_MESSAGE);
}
@Override
@NotNull
public IChatBaseComponent newObjectComponent(@NotNull TabPlayerSprite sprite) {
return IChatBaseComponent.b(TabObjectComponent.ERROR_MESSAGE);
}
@Override
public void applyStyle(@NotNull IChatBaseComponent nmsComponent, @NotNull TabStyle modifier) {
((IChatMutableComponent)nmsComponent).c(ChatModifier.a
.a(modifier.getColor() == null ? null : ChatHexColor.a(modifier.getColor().getRgb()))
.a(modifier.getBold())
.b(modifier.getItalic())
.c(modifier.getUnderlined())
.d(modifier.getStrikethrough())
.e(modifier.getObfuscated())
.a(modifier.getFont() == null ? null : MinecraftKey.a(modifier.getFont()))
);
}
@Override
public void addSibling(@NotNull IChatBaseComponent parent, @NotNull IChatBaseComponent child) {
((IChatMutableComponent)parent).b(child);
}
}
@@ -1,52 +0,0 @@
package me.neznamy.tab.platforms.bukkit.v1_19_R1;
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 org.bukkit.craftbukkit.v1_19_R1.entity.CraftPlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* Implementation provider using direct NMS code for 1.19.1 - 1.19.2.
*/
@Getter
public class NMSImplementationProvider implements ImplementationProvider {
@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
public Channel getChannel(@NotNull Player player) {
return ((CraftPlayer)player).getHandle().b.b.m;
}
@Override
@NotNull
public TabListEntryTracker newTabListEntryTracker(@NotNull Player player) {
return new NMSTabListEntryTracker(getChannel(player));
}
@Override
public int getPing(@NotNull BukkitTabPlayer player) {
return player.getPlayer().getPing();
}
}
@@ -1,207 +0,0 @@
package me.neznamy.tab.platforms.bukkit.v1_19_R1;
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.EnumChatFormat;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.PacketPlayOutScoreboardDisplayObjective;
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.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_19_R1.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Optional;
/**
* Scoreboard implementation using direct NMS code.
*/
public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ScoreboardTeamBase.EnumNameTagVisibility[] visibilities = ScoreboardTeamBase.EnumNameTagVisibility.values();
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
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.
*
* @param player
* Player this scoreboard will belong to
*/
public NMSPacketScoreboard(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
public void registerObjective(@NonNull Objective objective) {
ScoreboardObjective obj = new ScoreboardObjective(
dummyScoreboard,
objective.getName(),
IScoreboardCriteria.a,
objective.getTitle().convert(),
IScoreboardCriteria.EnumScoreboardHealthDisplay.values()[objective.getHealthDisplay().ordinal()]
);
objective.setPlatformObjective(obj);
sendPacket(new PacketPlayOutScoreboardObjective(obj, ObjectiveAction.REGISTER));
}
@Override
public void setDisplaySlot(@NonNull Objective objective) {
sendPacket(new PacketPlayOutScoreboardDisplayObjective(
objective.getDisplaySlot().ordinal(),
(ScoreboardObjective) objective.getPlatformObjective()
));
}
@Override
public void unregisterObjective(@NonNull Objective objective) {
sendPacket(new PacketPlayOutScoreboardObjective((ScoreboardObjective) objective.getPlatformObjective(), ObjectiveAction.UNREGISTER));
}
@Override
public void updateObjective(@NonNull Objective objective) {
ScoreboardObjective obj = (ScoreboardObjective) objective.getPlatformObjective();
obj.a((IChatBaseComponent) objective.getTitle().convert());
obj.a(IScoreboardCriteria.EnumScoreboardHealthDisplay.valueOf(objective.getHealthDisplay().name()));
sendPacket(new PacketPlayOutScoreboardObjective(obj, ObjectiveAction.UPDATE));
}
@Override
public void setScore(@NonNull Score score) {
sendPacket(new PacketPlayOutScoreboardScore(
ScoreboardServer.Action.a,
score.getObjective().getName(),
score.getHolder(),
score.getValue()
));
}
@Override
public void removeScore(@NonNull Score score) {
sendPacket(new PacketPlayOutScoreboardScore(
ScoreboardServer.Action.b,
score.getObjective().getName(),
score.getHolder(),
score.getValue()
));
}
@Override
@NotNull
public Object createTeam(@NonNull String name) {
return new ScoreboardTeam(dummyScoreboard, name);
}
@Override
public void registerTeam(@NonNull Team team) {
updateTeamProperties(team);
ScoreboardTeam t = (ScoreboardTeam) team.getPlatformTeam();
t.g().addAll(team.getPlayers());
sendPacket(PacketPlayOutScoreboardTeam.a(t, true));
}
@Override
public void unregisterTeam(@NonNull Team team) {
sendPacket(PacketPlayOutScoreboardTeam.a((ScoreboardTeam) team.getPlatformTeam()));
}
@Override
public void updateTeam(@NonNull Team team) {
updateTeamProperties(team);
sendPacket(PacketPlayOutScoreboardTeam.a((ScoreboardTeam) team.getPlatformTeam(), false));
}
private void updateTeamProperties(@NonNull Team team) {
ScoreboardTeam t = (ScoreboardTeam) team.getPlatformTeam();
t.a((team.getOptions() & 0x01) != 0);
t.b((team.getOptions() & 0x02) != 0);
t.a(visibilities[team.getVisibility().ordinal()]);
t.a(collisions[team.getCollision().ordinal()]);
t.b((IChatBaseComponent) team.getPrefix().convert());
t.c(team.getSuffix().convert());
t.a(EnumChatFormat.valueOf(team.getColor().name()));
}
@Override
@SneakyThrows
@NotNull
public Object onPacketSend(@NonNull Object packet) {
if (packet instanceof PacketPlayOutScoreboardDisplayObjective) {
TAB.getInstance().getFeatureManager().onDisplayObjective(
player,
((PacketPlayOutScoreboardDisplayObjective)packet).b(),
((PacketPlayOutScoreboardDisplayObjective)packet).c()
);
}
if (packet instanceof PacketPlayOutScoreboardObjective) {
TAB.getInstance().getFeatureManager().onObjective(
player,
((PacketPlayOutScoreboardObjective)packet).d(),
((PacketPlayOutScoreboardObjective)packet).b()
);
}
if (packet instanceof PacketPlayOutScoreboardTeam) {
int action = getMethod((PacketPlayOutScoreboardTeam) packet);
if (action != TeamAction.UPDATE) {
Collection<String> players = ((PacketPlayOutScoreboardTeam) packet).e();
if (players != null) {
String name = ((PacketPlayOutScoreboardTeam) packet).d();
return teamConstructor.newInstance(
name,
action,
((PacketPlayOutScoreboardTeam) packet).f(),
onTeamPacket(action, name, players)
);
}
}
}
return packet;
}
private int getMethod(@NonNull PacketPlayOutScoreboardTeam team) {
if (team.c() == PacketPlayOutScoreboardTeam.a.a) {
return 0;
} else if (team.c() == PacketPlayOutScoreboardTeam.a.b) {
return 1;
} else if (team.b() == PacketPlayOutScoreboardTeam.a.a) {
return 3;
} else if (team.b() == PacketPlayOutScoreboardTeam.a.b) {
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().b.a(packet);
}
}
@@ -1,210 +0,0 @@
package me.neznamy.tab.platforms.bukkit.v1_19_R1;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
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.TabList;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo;
import net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
import net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo.PlayerInfoData;
import net.minecraft.network.protocol.game.PacketPlayOutPlayerListHeaderFooter;
import net.minecraft.world.entity.player.ProfilePublicKey;
import net.minecraft.world.level.EnumGamemode;
import org.bukkit.craftbukkit.v1_19_R1.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* TabList implementation using direct NMS code.
*/
public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
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;
private static final EnumPlayerInfoAction UPDATE_DISPLAY_NAME = EnumPlayerInfoAction.d;
private static final EnumPlayerInfoAction REMOVE_PLAYER = EnumPlayerInfoAction.e;
/**
* Constructs new instance.
*
* @param player
* Player this tablist will belong to
*/
public NMSPacketTabList(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
@SneakyThrows
public void removeEntry(@NonNull UUID entry) {
sendPacket(EnumPlayerInfoAction.e, entry, "", null, 0, 0, null);
}
@Override
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
sendPacket(EnumPlayerInfoAction.d, entry, "", null, 0, 0, displayName);
}
@Override
public void updateLatency(@NonNull UUID entry, int latency) {
sendPacket(EnumPlayerInfoAction.c, entry, "", null, latency, 0, null);
}
@Override
public void updateGameMode(@NonNull UUID entry, int gameMode) {
sendPacket(EnumPlayerInfoAction.b, entry, "", null, 0, gameMode, null);
}
@Override
public void updateListed(@NonNull UUID entry, boolean listed) {
// Added in 1.19.3
}
@Override
public void updateListOrder(@NonNull UUID entry, int listOrder) {
// Added in 1.21.2
}
@Override
public void updateHat(@NonNull UUID entry, boolean showHat) {
// Added in 1.21.4
}
@Override
public void addEntry0(@NonNull Entry entry) {
sendPacket(EnumPlayerInfoAction.a, entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.getLatency(),
entry.getGameMode(), entry.getDisplayName());
}
@Override
@SneakyThrows
public void setPlayerListHeaderFooter0(@NonNull TabComponent header, @NonNull TabComponent footer) {
sendPacket(new PacketPlayOutPlayerListHeaderFooter(header.convert(), footer.convert()));
}
@Override
@Nullable
public Skin getSkin() {
Collection<Property> properties = ((CraftPlayer)player.getPlayer()).getProfile().getProperties().get(TEXTURES_PROPERTY);
if (properties.isEmpty()) return null; // Offline mode
Property property = properties.iterator().next();
return new Skin(property.getValue(), property.getSignature());
}
@Override
@SneakyThrows
@NotNull
public Object onPacketSend(@NonNull Object packet) {
if (packet instanceof PacketPlayOutPlayerListHeaderFooter) {
PacketPlayOutPlayerListHeaderFooter tablist = (PacketPlayOutPlayerListHeaderFooter) packet;
if (header == null || footer == null) return packet;
if (tablist.a != header.convert() || tablist.b != footer.convert()) {
return new PacketPlayOutPlayerListHeaderFooter(header.convert(), footer.convert());
}
}
if (!(packet instanceof PacketPlayOutPlayerInfo)) return packet;
PacketPlayOutPlayerInfo info = (PacketPlayOutPlayerInfo) packet;
EnumPlayerInfoAction action = info.c();
List<PlayerInfoData> updatedList = new ArrayList<>();
boolean rewritePacket = false;
for (PlayerInfoData nmsData : info.b()) {
boolean rewriteEntry = false;
GameProfile profile = nmsData.a();
UUID id = profile.getId();
IChatBaseComponent displayName = nmsData.d();
int latency = nmsData.b();
int gameMode = nmsData.c().a();
ProfilePublicKey.a publicKey = nmsData.e();
if (action == UPDATE_DISPLAY_NAME || action == ADD_PLAYER) {
TabComponent forcedDisplayName = getForcedDisplayNames().get(id);
if (forcedDisplayName != null && forcedDisplayName.convert() != displayName) {
displayName = forcedDisplayName.convert();
rewriteEntry = rewritePacket = true;
}
}
if (action == UPDATE_GAME_MODE || action == ADD_PLAYER) {
if (getBlockedSpectators().contains(id) && gameMode == 3) {
gameMode = 0;
rewriteEntry = rewritePacket = true;
}
}
if (action == UPDATE_LATENCY || action == ADD_PLAYER) {
if (getForcedLatency() != null) {
latency = getForcedLatency();
rewriteEntry = rewritePacket = true;
}
}
if (action == ADD_PLAYER) {
TAB.getInstance().getFeatureManager().onEntryAdd(player, id, profile.getName());
}
updatedList.add(rewriteEntry ? new PlayerInfoData(
profile,
latency,
EnumGamemode.a(gameMode),
displayName,
publicKey
) : nmsData);
}
if (rewritePacket) {
PacketPlayOutPlayerInfo newPacket = new PacketPlayOutPlayerInfo(action, Collections.emptyList());
newPacket.b().clear();
newPacket.b().addAll(updatedList);
return newPacket;
}
return packet;
}
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);
packet.b().add(new PlayerInfoData(
createProfile(id, name, skin),
latency,
EnumGamemode.a(gameMode),
displayName == null ? null : displayName.convert(),
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) {
GameProfile profile = new GameProfile(id, name);
if (skin != null) {
profile.getProperties().put(TabList.TEXTURES_PROPERTY,
new Property(TabList.TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
}
return profile;
}
/**
* Sends the packet to the player.
*
* @param packet
* Packet to send
*/
private void sendPacket(@NotNull Packet<?> packet) {
((CraftPlayer)player.getPlayer()).getHandle().b.a(packet);
}
}
@@ -1,35 +0,0 @@
package me.neznamy.tab.platforms.bukkit.v1_19_R1;
import io.netty.channel.Channel;
import me.neznamy.tab.shared.platform.NettyTabListEntryTracker;
import net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo;
import net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
import org.jetbrains.annotations.NotNull;
/**
* Implementation of TabListEntryTracker.
*/
public class NMSTabListEntryTracker extends NettyTabListEntryTracker {
private static final EnumPlayerInfoAction ADD_PLAYER = EnumPlayerInfoAction.valueOf("ADD_PLAYER");
private static final EnumPlayerInfoAction REMOVE_PLAYER = EnumPlayerInfoAction.valueOf("REMOVE_PLAYER");
public NMSTabListEntryTracker(@NotNull Channel channel) {
super(channel);
}
@Override
public void onPacketSend(@NotNull Object packet) {
if (packet instanceof PacketPlayOutPlayerInfo) {
PacketPlayOutPlayerInfo info = (PacketPlayOutPlayerInfo) packet;
for (PacketPlayOutPlayerInfo.PlayerInfoData nmsData : info.b()) {
if (info.c() == ADD_PLAYER) {
tablistEntries.add(nmsData.a().getId());
}
if (info.c() == REMOVE_PLAYER) {
tablistEntries.remove(nmsData.a().getId());
}
}
}
}
}
-14
View File
@@ -1,14 +0,0 @@
dependencies {
implementation(projects.bukkit)
compileOnly("org.spigotmc:spigot:1.19.3-R0.1-SNAPSHOT") {
// Broken dependencies
exclude("com.mojang", "authlib")
exclude("com.mojang", "logging")
exclude("com.mojang", "datafixerupper")
}
compileOnly("com.mojang:authlib:1.5.25")
}
tasks.compileJava {
options.release.set(14) // Records
}
@@ -1,64 +0,0 @@
package me.neznamy.tab.platforms.bukkit.v1_19_R2;
import me.neznamy.tab.shared.chat.TabStyle;
import me.neznamy.tab.shared.chat.component.object.TabAtlasSprite;
import me.neznamy.tab.shared.chat.component.object.TabObjectComponent;
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
import me.neznamy.tab.shared.chat.component.object.TabPlayerSprite;
import net.minecraft.network.chat.*;
import net.minecraft.resources.MinecraftKey;
import org.jetbrains.annotations.NotNull;
/**
* Component converter using direct NMS code.
*/
public class NMSComponentConverter extends ComponentConverter<IChatBaseComponent> {
@Override
@NotNull
public IChatBaseComponent newTextComponent(@NotNull String text) {
return IChatBaseComponent.b(text);
}
@Override
@NotNull
public IChatBaseComponent newTranslatableComponent(@NotNull String key) {
return IChatBaseComponent.c(key);
}
@Override
@NotNull
public IChatBaseComponent newKeybindComponent(@NotNull String keybind) {
return IChatBaseComponent.d(keybind);
}
@Override
@NotNull
public IChatBaseComponent newObjectComponent(@NotNull TabAtlasSprite sprite) {
return IChatBaseComponent.b(TabObjectComponent.ERROR_MESSAGE);
}
@Override
@NotNull
public IChatBaseComponent newObjectComponent(@NotNull TabPlayerSprite sprite) {
return IChatBaseComponent.b(TabObjectComponent.ERROR_MESSAGE);
}
@Override
public void applyStyle(@NotNull IChatBaseComponent nmsComponent, @NotNull TabStyle modifier) {
((IChatMutableComponent)nmsComponent).c(ChatModifier.a
.a(modifier.getColor() == null ? null : ChatHexColor.a(modifier.getColor().getRgb()))
.a(modifier.getBold())
.b(modifier.getItalic())
.c(modifier.getUnderlined())
.d(modifier.getStrikethrough())
.e(modifier.getObfuscated())
.a(modifier.getFont() == null ? null : MinecraftKey.a(modifier.getFont()))
);
}
@Override
public void addSibling(@NotNull IChatBaseComponent parent, @NotNull IChatBaseComponent child) {
((IChatMutableComponent)parent).b(child);
}
}
@@ -1,52 +0,0 @@
package me.neznamy.tab.platforms.bukkit.v1_19_R2;
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 org.bukkit.craftbukkit.v1_19_R2.entity.CraftPlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* Implementation provider using direct NMS code for 1.19.3.
*/
@Getter
public class NMSImplementationProvider implements ImplementationProvider {
@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
public Channel getChannel(@NotNull Player player) {
return ((CraftPlayer)player).getHandle().b.b.m;
}
@Override
@NotNull
public TabListEntryTracker newTabListEntryTracker(@NotNull Player player) {
return new NMSTabListEntryTracker(getChannel(player));
}
@Override
public int getPing(@NotNull BukkitTabPlayer player) {
return player.getPlayer().getPing();
}
}
@@ -1,206 +0,0 @@
package me.neznamy.tab.platforms.bukkit.v1_19_R2;
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.EnumChatFormat;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.PacketPlayOutScoreboardDisplayObjective;
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.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_19_R2.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Optional;
/**
* Scoreboard implementation using direct NMS code.
*/
public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ScoreboardTeamBase.EnumNameTagVisibility[] visibilities = ScoreboardTeamBase.EnumNameTagVisibility.values();
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
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.
*
* @param player
* Player this scoreboard will belong to
*/
public NMSPacketScoreboard(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
public void registerObjective(@NonNull Objective objective) {
ScoreboardObjective obj = new ScoreboardObjective(
dummyScoreboard,
objective.getName(),
IScoreboardCriteria.a,
objective.getTitle().convert(),
IScoreboardCriteria.EnumScoreboardHealthDisplay.values()[objective.getHealthDisplay().ordinal()]
);
objective.setPlatformObjective(obj);
sendPacket(new PacketPlayOutScoreboardObjective(obj, ObjectiveAction.REGISTER));
}
@Override
public void setDisplaySlot(@NonNull Objective objective) {
sendPacket(new PacketPlayOutScoreboardDisplayObjective(
objective.getDisplaySlot().ordinal(),
(ScoreboardObjective) objective.getPlatformObjective()
));
}
@Override
public void unregisterObjective(@NonNull Objective objective) {
sendPacket(new PacketPlayOutScoreboardObjective((ScoreboardObjective) objective.getPlatformObjective(), ObjectiveAction.UNREGISTER));
}
@Override
public void updateObjective(@NonNull Objective objective) {
ScoreboardObjective obj = (ScoreboardObjective) objective.getPlatformObjective();
obj.a((IChatBaseComponent) objective.getTitle().convert());
obj.a(IScoreboardCriteria.EnumScoreboardHealthDisplay.valueOf(objective.getHealthDisplay().name()));
sendPacket(new PacketPlayOutScoreboardObjective(obj, ObjectiveAction.UPDATE));
}
@Override
public void setScore(@NonNull Score score) {
sendPacket(new PacketPlayOutScoreboardScore(
ScoreboardServer.Action.a,
score.getObjective().getName(),
score.getHolder(),
score.getValue()
));
}
@Override
public void removeScore(@NonNull Score score) {
sendPacket(new PacketPlayOutScoreboardScore(
ScoreboardServer.Action.b,
score.getObjective().getName(),
score.getHolder(),
score.getValue()
));
}
@Override
@NotNull
public Object createTeam(@NonNull String name) {
return new ScoreboardTeam(dummyScoreboard, name);
}
@Override
public void registerTeam(@NonNull Team team) {
updateTeamProperties(team);
ScoreboardTeam t = (ScoreboardTeam) team.getPlatformTeam();
t.g().addAll(team.getPlayers());
sendPacket(PacketPlayOutScoreboardTeam.a(t, true));
}
@Override
public void unregisterTeam(@NonNull Team team) {
sendPacket(PacketPlayOutScoreboardTeam.a((ScoreboardTeam) team.getPlatformTeam()));
}
@Override
public void updateTeam(@NonNull Team team) {
updateTeamProperties(team);
sendPacket(PacketPlayOutScoreboardTeam.a((ScoreboardTeam) team.getPlatformTeam(), false));
}
private void updateTeamProperties(@NonNull Team team) {
ScoreboardTeam t = (ScoreboardTeam) team.getPlatformTeam();
t.a((team.getOptions() & 0x01) != 0);
t.b((team.getOptions() & 0x02) != 0);
t.a(visibilities[team.getVisibility().ordinal()]);
t.a(collisions[team.getCollision().ordinal()]);
t.b((IChatBaseComponent) team.getPrefix().convert());
t.c(team.getSuffix().convert());
t.a(EnumChatFormat.valueOf(team.getColor().name()));
}
@Override
@SneakyThrows
@NotNull
public Object onPacketSend(@NonNull Object packet) {
if (packet instanceof PacketPlayOutScoreboardDisplayObjective) {
TAB.getInstance().getFeatureManager().onDisplayObjective(
player,
((PacketPlayOutScoreboardDisplayObjective)packet).b(),
((PacketPlayOutScoreboardDisplayObjective)packet).c()
);
}
if (packet instanceof PacketPlayOutScoreboardObjective) {
TAB.getInstance().getFeatureManager().onObjective(
player,
((PacketPlayOutScoreboardObjective)packet).d(),
((PacketPlayOutScoreboardObjective)packet).b()
);
}
if (packet instanceof PacketPlayOutScoreboardTeam) {
int action = getMethod((PacketPlayOutScoreboardTeam) packet);
if (action != TeamAction.UPDATE) {
Collection<String> players = ((PacketPlayOutScoreboardTeam) packet).e();
if (players != null) {
String name = ((PacketPlayOutScoreboardTeam) packet).d();
return teamConstructor.newInstance(
name,
action,
((PacketPlayOutScoreboardTeam) packet).f(),
onTeamPacket(action, name, players)
);
}
}
}
return packet;
}
private int getMethod(@NonNull PacketPlayOutScoreboardTeam team) {
if (team.c() == PacketPlayOutScoreboardTeam.a.a) {
return 0;
} else if (team.c() == PacketPlayOutScoreboardTeam.a.b) {
return 1;
} else if (team.b() == PacketPlayOutScoreboardTeam.a.a) {
return 3;
} else if (team.b() == PacketPlayOutScoreboardTeam.a.b) {
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().b.a(packet);
}
}
@@ -1,223 +0,0 @@
package me.neznamy.tab.platforms.bukkit.v1_19_R2;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
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.features.layout.LayoutManagerImpl;
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.ClientboundPlayerInfoRemovePacket;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
import net.minecraft.network.protocol.game.PacketPlayOutPlayerListHeaderFooter;
import net.minecraft.world.level.EnumGamemode;
import org.bukkit.craftbukkit.v1_19_R2.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.*;
/**
* TabList implementation using direct NMS code.
*/
public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
private static final Field PLAYERS = ReflectionUtils.getOnlyField(ClientboundPlayerInfoUpdatePacket.class, List.class);
private static final ClientboundPlayerInfoUpdatePacket.a ADD_PLAYER = ClientboundPlayerInfoUpdatePacket.a.valueOf("ADD_PLAYER");
private static final ClientboundPlayerInfoUpdatePacket.a UPDATE_GAME_MODE = ClientboundPlayerInfoUpdatePacket.a.valueOf("UPDATE_GAME_MODE");
private static final ClientboundPlayerInfoUpdatePacket.a UPDATE_LATENCY = ClientboundPlayerInfoUpdatePacket.a.valueOf("UPDATE_LATENCY");
private static final ClientboundPlayerInfoUpdatePacket.a UPDATE_DISPLAY_NAME = ClientboundPlayerInfoUpdatePacket.a.valueOf("UPDATE_DISPLAY_NAME");
private static final ClientboundPlayerInfoUpdatePacket.a UPDATE_LISTED = ClientboundPlayerInfoUpdatePacket.a.valueOf("UPDATE_LISTED");
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.a> ADD_PLAYER_SET = EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.a.class);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.a> UPDATE_GAME_MODE_SET = EnumSet.of(UPDATE_GAME_MODE);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.a> UPDATE_DISPLAY_NAME_SET = EnumSet.of(UPDATE_DISPLAY_NAME);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.a> UPDATE_LATENCY_SET = EnumSet.of(UPDATE_LATENCY);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.a> UPDATE_LISTED_SET = EnumSet.of(UPDATE_LISTED);
/**
* Constructs new instance.
*
* @param player
* Player this tablist will belong to
*/
public NMSPacketTabList(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
@SneakyThrows
public void removeEntry(@NonNull UUID entry) {
sendPacket(new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry)));
}
@Override
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
sendPacket(UPDATE_DISPLAY_NAME_SET, entry, "", null, false, 0, 0, displayName);
}
@Override
public void updateLatency(@NonNull UUID entry, int latency) {
sendPacket(UPDATE_LATENCY_SET, entry, "", null, false, latency, 0, null);
}
@Override
public void updateGameMode(@NonNull UUID entry, int gameMode) {
sendPacket(UPDATE_GAME_MODE_SET, entry, "", null, false, 0, gameMode, null);
}
@Override
public void updateListed(@NonNull UUID entry, boolean listed) {
sendPacket(UPDATE_LISTED_SET, entry, "", null, listed, 0, 0, null);
}
@Override
public void updateListOrder(@NonNull UUID entry, int listOrder) {
// Added in 1.21.2
}
@Override
public void updateHat(@NonNull UUID entry, boolean showHat) {
// Added in 1.21.4
}
@Override
public void addEntry0(@NonNull Entry entry) {
sendPacket(ADD_PLAYER_SET, entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.isListed(), entry.getLatency(),
entry.getGameMode(), entry.getDisplayName());
}
@Override
@SneakyThrows
public void setPlayerListHeaderFooter0(@NonNull TabComponent header, @NonNull TabComponent footer) {
sendPacket(new PacketPlayOutPlayerListHeaderFooter(header.convert(), footer.convert()));
}
@Override
@Nullable
public Skin getSkin() {
Collection<Property> properties = ((CraftPlayer)player.getPlayer()).getProfile().getProperties().get(TEXTURES_PROPERTY);
if (properties.isEmpty()) return null; // Offline mode
Property property = properties.iterator().next();
return new Skin(property.getValue(), property.getSignature());
}
@SneakyThrows
@Override
@NotNull
public Object onPacketSend(@NonNull Object packet) {
if (packet instanceof PacketPlayOutPlayerListHeaderFooter) {
PacketPlayOutPlayerListHeaderFooter tablist = (PacketPlayOutPlayerListHeaderFooter) packet;
if (header == null || footer == null) return packet;
if (tablist.a != header.convert() || tablist.b != footer.convert()) {
return new PacketPlayOutPlayerListHeaderFooter(header.convert(), footer.convert());
}
}
if (!(packet instanceof ClientboundPlayerInfoUpdatePacket)) return packet;
ClientboundPlayerInfoUpdatePacket info = (ClientboundPlayerInfoUpdatePacket) packet;
EnumSet<ClientboundPlayerInfoUpdatePacket.a> actions = info.b();
List<ClientboundPlayerInfoUpdatePacket.b> updatedList = new ArrayList<>();
boolean rewritePacket = false;
for (ClientboundPlayerInfoUpdatePacket.b nmsData : info.c()) {
UUID profileId = nmsData.a();
boolean rewriteEntry = false;
IChatBaseComponent displayName = nmsData.f();
int latency = nmsData.d();
int gameMode = nmsData.e().a();
boolean listed = nmsData.c();
if (actions.contains(UPDATE_DISPLAY_NAME)) {
TabComponent forcedDisplayName = getForcedDisplayNames().get(profileId);
if (forcedDisplayName != null && forcedDisplayName.convert() != displayName) {
displayName = forcedDisplayName.convert();
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(UPDATE_GAME_MODE)) {
if (getBlockedSpectators().contains(profileId) && gameMode == 3) {
gameMode = 0;
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(UPDATE_LATENCY)) {
if (getForcedLatency() != null) {
latency = getForcedLatency();
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(UPDATE_LISTED)) {
if (allPlayersHidden && !LayoutManagerImpl.UUIDS_SET.contains(nmsData.a())) { // Filter out layout entries
listed = false;
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ADD_PLAYER)) {
TAB.getInstance().getFeatureManager().onEntryAdd(player, profileId, nmsData.b().getName());
}
updatedList.add(rewriteEntry ? new ClientboundPlayerInfoUpdatePacket.b(
profileId, nmsData.b(), listed, latency, EnumGamemode.a(gameMode), displayName,
nmsData.g()
) : 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.a> action, @NonNull UUID id, @NonNull String name,
@Nullable Skin skin, boolean listed, int latency, int gameMode, @Nullable TabComponent displayName) {
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(action, Collections.emptyList());
PLAYERS.set(packet, Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.b(
id,
createProfile(id, name, skin),
listed,
latency,
EnumGamemode.a(gameMode),
displayName == null ? null : displayName.convert(),
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) {
GameProfile profile = new GameProfile(id, name);
if (skin != null) {
profile.getProperties().put(TabList.TEXTURES_PROPERTY,
new Property(TabList.TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
}
return profile;
}
/**
* Sends the packet to the player.
*
* @param packet
* Packet to send
*/
private void sendPacket(@NotNull Packet<?> packet) {
((CraftPlayer)player.getPlayer()).getHandle().b.a(packet);
}
}
@@ -1,40 +0,0 @@
package me.neznamy.tab.platforms.bukkit.v1_19_R2;
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 net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket.a;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
/**
* Implementation of TabListEntryTracker.
*/
public class NMSTabListEntryTracker extends NettyTabListEntryTracker {
private static final a ADD_PLAYER = a.valueOf("ADD_PLAYER");
public NMSTabListEntryTracker(@NotNull Channel channel) {
super(channel);
}
@Override
public void onPacketSend(@NotNull Object packet) {
if (packet instanceof ClientboundPlayerInfoRemovePacket) {
ClientboundPlayerInfoRemovePacket remove = (ClientboundPlayerInfoRemovePacket) packet;
for (UUID id : remove.b()) {
tablistEntries.remove(id);
}
}
if (packet instanceof ClientboundPlayerInfoUpdatePacket) {
ClientboundPlayerInfoUpdatePacket update = (ClientboundPlayerInfoUpdatePacket) packet;
if (update.b().contains(ADD_PLAYER)) {
for (ClientboundPlayerInfoUpdatePacket.b nmsData : update.c()) {
tablistEntries.add(nmsData.a());
}
}
}
}
}
+2 -1
View File
@@ -14,11 +14,12 @@ repositories {
maven("https://repo.papermc.io/repository/maven-public/") // Adventure
}
val minecraftVersion = "26.2"
val minecraftVersion = "26.3"
// 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.3" to "0.160.5+26.3",
"26.2" to "0.150.3+26.2",
"26.1.2" to "0.150.0+26.1.2"
)
@@ -133,7 +133,12 @@ public class FabricScoreboard extends SafeScoreboard<FabricTabPlayer> {
if (packet instanceof ClientboundSetPlayerTeamPacket team) {
int method = getMethod(team);
if (method != TeamAction.UPDATE) {
team.players = onTeamPacket(method, team.getName(), team.getPlayers());
return new ClientboundSetPlayerTeamPacket(
team.getName(),
method,
team.getParameters(),
onTeamPacket(method, team.getName(), team.getPlayers())
);
}
}
return packet;
+2 -1
View File
@@ -14,7 +14,8 @@
"fabric-api-base": "*",
"java": ">=25",
"minecraft": [
"26.2"
"26.2",
"26.3"
]
},
"recommends": {
@@ -5,8 +5,7 @@ accessible field net/minecraft/server/network/ServerCommonPacketListenerImpl con
accessible field net/minecraft/network/Connection channel Lio/netty/channel/Channel;
# Scoreboard team
accessible field net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket players Ljava/util/Collection;
mutable field net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket players Ljava/util/Collection;
accessible method net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket <init> (Ljava/lang/String;ILjava/util/Optional;Ljava/util/Collection;)V
# Player info
accessible field net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket entries Ljava/util/List;
+4 -3
View File
@@ -15,13 +15,14 @@ repositories {
maven("https://repo.papermc.io/repository/maven-public/") // Adventure
}
val minecraftVersion = "26.2"
val minecraftVersion = "26.3"
// Forge API versions for each Minecraft version for easier backporting
// Official website (for updating in the future): https://files.minecraftforge.net/net/minecraftforge/forge/
val forgeApiVersions = mapOf(
"26.2" to "26.2-65.0.0",
"26.1.2" to "26.1.2-64.0.4"
"26.3" to "26.3-66.0.2",
"26.2" to "26.2-65.1.3",
"26.1.2" to "26.1.2-64.1.3"
)
dependencies {
@@ -4,7 +4,6 @@ import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.chat.numbers.FixedFormat;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
@@ -15,9 +14,7 @@ import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collection;
import java.util.Optional;
/**
@@ -29,7 +26,6 @@ public class ForgeScoreboard extends SafeScoreboard<ForgeTabPlayer> {
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);
/**
* Constructs new instance with given player.
@@ -139,7 +135,12 @@ public class ForgeScoreboard extends SafeScoreboard<ForgeTabPlayer> {
if (packet instanceof ClientboundSetPlayerTeamPacket team) {
int method = getMethod(team);
if (method != TeamAction.UPDATE) {
players.set(team, onTeamPacket(method, team.getName(), team.getPlayers()));
return new ClientboundSetPlayerTeamPacket(
team.getName(),
method,
team.getParameters(),
onTeamPacket(method, team.getName(), team.getPlayers())
);
}
}
return packet;
@@ -5,12 +5,10 @@ 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.shared.TAB;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.shared.features.layout.LayoutManagerImpl;
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;
@@ -20,7 +18,6 @@ import net.minecraft.world.level.GameType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.*;
/**
@@ -36,8 +33,6 @@ public class ForgeTabList extends TrackedTabList<ForgeTabPlayer> {
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);
private static final Field entries = ReflectionUtils.getOnlyField(ClientboundPlayerInfoUpdatePacket.class, List.class);
/**
* Constructs new instance.
*
@@ -104,7 +99,6 @@ public class ForgeTabList extends TrackedTabList<ForgeTabPlayer> {
}
@Override
@SneakyThrows
@NotNull
public Object onPacketSend(@NonNull Object packet) {
if (packet instanceof ClientboundTabListPacket tablist) {
@@ -158,18 +152,17 @@ public class ForgeTabList extends TrackedTabList<ForgeTabPlayer> {
}
if (rewritePacket) {
ClientboundPlayerInfoUpdatePacket newPacket = new ClientboundPlayerInfoUpdatePacket(actions, Collections.emptyList());
entries.set(newPacket, updatedList);
newPacket.entries = 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());
entries.set(packet, Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.Entry(
packet.entries = Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.Entry(
id,
action.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER) ? createProfile(id, name, skin) : null,
listed,
@@ -179,7 +172,7 @@ public class ForgeTabList extends TrackedTabList<ForgeTabPlayer> {
showHat,
listOrder,
null
)));
));
sendPacket(packet);
}
@@ -0,0 +1,5 @@
# Scoreboard team
public net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket <init>(Ljava/lang/String;ILjava/util/Optional;Ljava/util/Collection;)V
# Player info
public-f net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket entries
+1 -1
View File
@@ -15,6 +15,6 @@ displayURL = "@website@"
[[dependencies.tab]]
modId = "minecraft"
mandatory = true
versionRange = "[26.2]"
versionRange = "[26.2],[26.3]"
ordering = "NONE"
side = "SERVER"
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
-2
View File
@@ -27,8 +27,6 @@ val platformPaths = setOf(
":bukkit:v1_16_R3",
":bukkit:v1_17_R1",
":bukkit:v1_18_R2",
":bukkit:v1_19_R1",
":bukkit:v1_19_R2",
":bukkit:v1_19_R3",
":bukkit:v1_20_R1",
":bukkit:v1_20_R2",
+4 -3
View File
@@ -15,13 +15,14 @@ repositories {
maven("https://repo.papermc.io/repository/maven-public/") // Adventure
}
val minecraftVersion = "26.2"
val minecraftVersion = "26.3"
// 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"
"26.3" to "26.3.0.1-beta",
"26.2" to "26.2.0.88",
"26.1.2" to "26.1.2.109"
)
dependencies {
@@ -133,7 +133,12 @@ public class NeoForgeScoreboard extends SafeScoreboard<NeoForgeTabPlayer> {
if (packet instanceof ClientboundSetPlayerTeamPacket team) {
int method = getMethod(team);
if (method != TeamAction.UPDATE) {
team.players = onTeamPacket(method, team.getName(), team.getPlayers());
return new ClientboundSetPlayerTeamPacket(
team.getName(),
method,
team.getParameters(),
onTeamPacket(method, team.getName(), team.getPlayers())
);
}
}
return packet;
@@ -1,5 +1,5 @@
# Scoreboard team
public-f net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket players
public net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket <init>(Ljava/lang/String;ILjava/util/Optional;Ljava/util/Collection;)V
# Player info
public-f net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket entries
@@ -15,6 +15,6 @@ description = '''@description@'''
[[dependencies.tab]]
modId = "minecraft"
type = "required"
versionRange = "[26.2]"
versionRange = "[26.2],[26.3]"
ordering = "NONE"
side = "SERVER"
-2
View File
@@ -52,8 +52,6 @@ include(":bukkit:v1_12_R1")
include(":bukkit:v1_16_R3")
include(":bukkit:v1_17_R1")
include(":bukkit:v1_18_R2")
include(":bukkit:v1_19_R1")
include(":bukkit:v1_19_R2")
include(":bukkit:v1_19_R3")
include(":bukkit:v1_20_R1")
include(":bukkit:v1_20_R2")
@@ -596,8 +596,12 @@ public class FeatureManager {
if (config.getScoreboard() != null) {
registerFeature(TabConstants.Feature.SCOREBOARD, new ScoreboardManagerImpl(config.getScoreboard()));
}
registerFeature(TabConstants.Feature.YELLOW_NUMBER, new YellowNumber(config.getPlayerlistObjective()));
registerFeature(TabConstants.Feature.BELOW_NAME, new BelowName(config.getBelowname()));
if (config.getPlayerlistObjective() != null) {
registerFeature(TabConstants.Feature.YELLOW_NUMBER, new YellowNumber(config.getPlayerlistObjective()));
}
if (config.getBelowname() != null) {
registerFeature(TabConstants.Feature.BELOW_NAME, new BelowName(config.getBelowname()));
}
if (config.getSorting() != null) {
registerFeature(TabConstants.Feature.SORTING, new Sorting(config.getSorting()));
}
@@ -11,6 +11,7 @@ import org.jetbrains.annotations.NotNull;
public enum ProtocolVersion {
UNKNOWN,
V26_3 (777),
V26_2 (776),
V26_1_2 (775),
V26_1_1 (775),
@@ -149,9 +149,13 @@ public class DumpCommand extends SubCommand {
mysql.put("password", "*CENSORED*");
Map<String, Object> proxySupport = (Map<String, Object>) config.get("proxy-support");
Map<String, Object> redis = (Map<String, Object>) proxySupport.get("redis");
redis.put("url", "*CENSORED*");
if (redis != null) {
redis.put("url", "*CENSORED*");
}
Map<String, Object> rabbitmq = (Map<String, Object>) proxySupport.get("rabbitmq");
rabbitmq.put("url", "*CENSORED*");
if (rabbitmq != null) {
rabbitmq.put("url", "*CENSORED*");
}
}
@NotNull
@@ -25,8 +25,9 @@ public class YamlPropertyConfigurationFile extends YamlConfigurationFile impleme
private final String PER_WORLD = "per-world";
private final String category;
private final Collection<String> worldGroups = new ArrayList<>(this.<String, Object>getMap(PER_WORLD).keySet());
private final Collection<String> serverGroups = new ArrayList<>(this.<String, Object>getMap(PER_SERVER).keySet());
private final ConfigurationSection root = getConfigurationSection("");
private final Collection<String> worldGroups = section(PER_WORLD).getKeys().stream().map(Object::toString).collect(Collectors.toList());
private final Collection<String> serverGroups = section(PER_SERVER).getKeys().stream().map(Object::toString).collect(Collectors.toList());
/**
* Constructs new instance and attempts to load specified configuration file.
@@ -48,26 +49,29 @@ public class YamlPropertyConfigurationFile extends YamlConfigurationFile impleme
public YamlPropertyConfigurationFile(@Nullable InputStream source, @NonNull File destination) throws IOException {
super(source, destination);
category = destination.getName().contains("groups") ? "group" : "user";
for (Map.Entry<Object, Object> entry : getValues().entrySet()) {
if (entry.getKey().equals(PER_SERVER)) {
for (Object key : root.getKeys()) {
String entry = key.toString();
if (entry.equals(PER_SERVER)) {
for (String server : serverGroups) {
for (String name : this.<String, Object>getMap(PER_SERVER + "." + server).keySet()) {
for (String property : this.<String, Object>getMap(PER_SERVER + "." + server + "." + name).keySet()) {
checkProperty(destination.getName(), category, name, property, server, null, true);
ConfigurationSection serverSection = section(PER_SERVER, server);
for (Object name : serverSection.getKeys()) {
for (Object property : serverSection.getConfigurationSection(name.toString()).getKeys()) {
checkProperty(destination.getName(), category, name.toString(), property.toString(), server, null, true);
}
}
}
} else if (entry.getKey().equals(PER_WORLD)) {
} else if (entry.equals(PER_WORLD)) {
for (String world : worldGroups) {
for (String name : this.<String, Object>getMap(PER_WORLD + "." + world).keySet()) {
for (String property : this.<String, Object>getMap(PER_WORLD + "." + world + "." + name).keySet()) {
checkProperty(destination.getName(), category, name, property, null, world, true);
ConfigurationSection worldSection = section(PER_WORLD, world);
for (Object name : worldSection.getKeys()) {
for (Object property : worldSection.getConfigurationSection(name.toString()).getKeys()) {
checkProperty(destination.getName(), category, name.toString(), property.toString(), null, world, true);
}
}
}
} else {
for (String property : this.<String, Object>getMap(entry.getKey().toString()).keySet()) {
checkProperty(destination.getName(), category, entry.getKey().toString(), property, null, null, true);
for (Object property : root.getConfigurationSection(entry).getKeys()) {
checkProperty(destination.getName(), category, entry, property.toString(), null, null, true);
}
}
}
@@ -75,13 +79,16 @@ public class YamlPropertyConfigurationFile extends YamlConfigurationFile impleme
@Override
public void setProperty(@NonNull String name, @NonNull String property, @Nullable Server server, @Nullable World world, @Nullable String value) {
ConfigurationSection target;
if (world != null) {
set(String.format("%s.%s.%s.%s", PER_WORLD, world.getName(), name, property), fromString(value));
target = getOrCreateSection(PER_WORLD, world.getName(), name);
} else if (server != null) {
set(String.format("%s.%s.%s.%s", PER_SERVER, server.getName(), name, property), fromString(value));
target = getOrCreateSection(PER_SERVER, server.getName(), name);
} else {
set(String.format("%s.%s", name, property), fromString(value));
target = getOrCreateSection(name);
}
target.put(property, fromString(value));
save();
}
@Override
@@ -89,22 +96,22 @@ public class YamlPropertyConfigurationFile extends YamlConfigurationFile impleme
String worldName = world == null ? null : world.getName();
String serverName = server == null ? null : server.getName();
Object value;
if ((value = getObject(new String[] {PER_WORLD, TAB.getInstance().getConfiguration().getGroup(worldGroups, worldName), name, property})) != null) {
if ((value = section(PER_WORLD, TAB.getInstance().getConfiguration().getGroup(worldGroups, worldName), name).getObject(property)) != null) {
return new String[] {toString(value), category + "=" + name + ", world=" + worldName};
}
if ((value = getObject(new String[] {PER_WORLD, TAB.getInstance().getConfiguration().getGroup(worldGroups, worldName), TabConstants.DEFAULT_GROUP, property})) != null) {
if ((value = section(PER_WORLD, TAB.getInstance().getConfiguration().getGroup(worldGroups, worldName), TabConstants.DEFAULT_GROUP).getObject(property)) != null) {
return new String[] {toString(value), category + "=" + TabConstants.DEFAULT_GROUP + ", world=" + worldName};
}
if ((value = getObject(new String[] {PER_SERVER, TAB.getInstance().getConfiguration().getServerGroup(serverGroups, server), name, property})) != null) {
if ((value = section(PER_SERVER, TAB.getInstance().getConfiguration().getServerGroup(serverGroups, server), name).getObject(property)) != null) {
return new String[] {toString(value), category + "=" + name + ", server=" + serverName};
}
if ((value = getObject(new String[] {PER_SERVER, TAB.getInstance().getConfiguration().getServerGroup(serverGroups, server), TabConstants.DEFAULT_GROUP, property})) != null) {
if ((value = section(PER_SERVER, TAB.getInstance().getConfiguration().getServerGroup(serverGroups, server), TabConstants.DEFAULT_GROUP).getObject(property)) != null) {
return new String[] {toString(value), category + "=" + TabConstants.DEFAULT_GROUP + ", server=" + serverName};
}
if ((value = getObject(new String[] {name, property})) != null) {
if ((value = section(name).getObject(property)) != null) {
return new String[] {toString(value), category + "=" + name};
}
if ((value = getObject(new String[] {TabConstants.DEFAULT_GROUP, property})) != null) {
if ((value = section(TabConstants.DEFAULT_GROUP).getObject(property)) != null) {
return new String[] {toString(value), category + "=" + TabConstants.DEFAULT_GROUP};
}
return new String[0];
@@ -112,39 +119,81 @@ public class YamlPropertyConfigurationFile extends YamlConfigurationFile impleme
@Override
public void remove(@NonNull String name) {
set(name, null);
getMap(PER_WORLD).keySet().forEach(world -> set(PER_WORLD + "." + world + "." + name, null));
getMap(PER_SERVER).keySet().forEach(server -> set(PER_SERVER + "." + server + "." + name, null));
root.remove(name);
for (Object world : section(PER_WORLD).getKeys()) {
section(PER_WORLD, world.toString()).remove(name);
}
for (Object server : section(PER_SERVER).getKeys()) {
section(PER_SERVER, server.toString()).remove(name);
}
save();
}
@Override
@NotNull
public Map<String, Object> getGlobalSettings(@NonNull String name) {
return getMap(name);
Map<String, Object> settings = root.getMap(name);
return settings == null ? new LinkedHashMap<>() : settings;
}
@Override
@NotNull
public Map<String, Map<String, Object>> getPerWorldSettings(@NonNull String name) {
return convertMap(getMap(PER_WORLD), name);
return getSettingsByGroup(PER_WORLD, name);
}
@Override
@NotNull
public Map<String, Map<String, Object>> getPerServerSettings(@NonNull String name) {
return convertMap(getMap(PER_SERVER), name);
return getSettingsByGroup(PER_SERVER, name);
}
@Override
@NotNull
public Set<String> getAllEntries() {
Set<Object> set = new HashSet<>(values.keySet());
Set<Object> set = new HashSet<>(root.getKeys());
set.remove(PER_WORLD);
set.remove(PER_SERVER);
Map<String, Map<String, Map<String, String>>> perWorld = getMap(PER_WORLD);
perWorld.values().forEach(m -> set.addAll(m.keySet()));
Map<String, Map<String, Map<String, String>>> perServer = getMap(PER_SERVER);
perServer.values().forEach(m -> set.addAll(m.keySet()));
addNestedEntryNames(set, PER_WORLD);
addNestedEntryNames(set, PER_SERVER);
return set.stream().map(Object::toString).collect(Collectors.toSet());
}
@NotNull
private ConfigurationSection section(@NotNull String... path) {
ConfigurationSection section = root;
for (String key : path) {
section = section.getConfigurationSection(key);
}
return section;
}
@NotNull
private ConfigurationSection getOrCreateSection(@NotNull String... path) {
ConfigurationSection section = root;
for (String key : path) {
if (section.getMap(key) == null) {
section.put(key, new LinkedHashMap<>());
}
section = section.getConfigurationSection(key);
}
return section;
}
@NotNull
private Map<String, Map<String, Object>> getSettingsByGroup(@NotNull String sectionName, @NotNull String name) {
Map<String, Map<String, Object>> settings = new HashMap<>();
ConfigurationSection sections = section(sectionName);
for (Object key : sections.getKeys()) {
settings.put(key.toString(), sections.getConfigurationSection(key.toString()).getMap(name));
}
return settings;
}
private void addNestedEntryNames(@NotNull Set<Object> entries, @NotNull String sectionName) {
ConfigurationSection sections = section(sectionName);
for (Object key : sections.getKeys()) {
entries.addAll(sections.getConfigurationSection(key.toString()).getKeys());
}
}
}
@@ -40,7 +40,7 @@ public class Config {
@NotNull private final ConfigurationFile config = new YamlConfigurationFile(getClass().getClassLoader().getResourceAsStream("config/config.yml"),
new File(TAB.getInstance().getDataFolder(), "config.yml"));
@NotNull private final BelowNameConfiguration belowname;
@Nullable private BelowNameConfiguration belowname;
@Nullable private BossBarConfiguration bossbar;
@NotNull private final ConditionsSection conditions;
@Nullable private GlobalPlayerListConfiguration globalPlayerList;
@@ -52,7 +52,7 @@ public class Config {
@NotNull private final PlaceholderRefreshConfiguration refresh;
@NotNull private final PlaceholderReplacementsConfiguration replacements;
@NotNull private final PlaceholdersConfiguration placeholders;
@NotNull private final PlayerListObjectiveConfiguration playerlistObjective;
@Nullable private PlayerListObjectiveConfiguration playerlistObjective;
@Nullable private ScoreboardConfiguration scoreboard;
@Nullable private SortingConfiguration sorting;
@Nullable private TablistFormattingConfiguration tablistFormatting;
@@ -95,10 +95,10 @@ public class Config {
replacements = PlaceholderReplacementsConfiguration.fromSection(config.getConfigurationSection("placeholder-output-replacements"));
placeholders = PlaceholdersConfiguration.fromSection(config.getConfigurationSection("placeholders"));
components = ComponentConfiguration.fromSection(config.getConfigurationSection("components"));
belowname = BelowNameConfiguration.fromSection(config.getConfigurationSection("belowname-objective"));
playerlistObjective = PlayerListObjectiveConfiguration.fromSection(config.getConfigurationSection("playerlist-objective"));
if (config.getBoolean("bossbar.enabled", false)) bossbar = BossBarConfiguration.fromSection(config.getConfigurationSection("bossbar"));
if (config.getBoolean("belowname-objective.enabled", false)) belowname = BelowNameConfiguration.fromSection(config.getConfigurationSection("belowname-objective"));
if (config.getBoolean("playerlist-objective.enabled", true)) playerlistObjective = PlayerListObjectiveConfiguration.fromSection(config.getConfigurationSection("playerlist-objective"));
if (config.getBoolean("global-playerlist.enabled", false)) globalPlayerList = GlobalPlayerListConfiguration.fromSection(config.getConfigurationSection("global-playerlist"));
if (config.getBoolean("header-footer.enabled", true)) headerFooter = HeaderFooterConfiguration.fromSection(config.getConfigurationSection("header-footer"));
if (config.getBoolean("layout.enabled", false)) layout = LayoutConfiguration.fromSection(config.getConfigurationSection("layout"));
@@ -118,7 +118,7 @@ public class Config {
" fake players, making per world playerlist completely useless as real players are pushed out of the playerlist. " +
"Disable per world playerlist for the same result, but with better performance.");
}
if (playerlistObjective.isEnabled()) {
if (playerlistObjective != null) {
TAB.getInstance().getConfigHelper().startup().startupWarn(config.getFile(), "Layout feature breaks playerlist-objective feature, because it replaces real player with fake slots " +
"with different usernames for more reliable functionality. Disable playerlist-objective feature, as it will only look bad " +
"and consume resources.");
@@ -495,6 +495,8 @@ public class PlaceholderManagerImpl extends RefreshableFeature implements Placeh
public void registerServerPlaceholder(@NonNull Pattern identifier, int refresh, @NonNull Function<Matcher, Supplier<String>> function) {
ensureActive();
registeredDynamicPlaceholders.put(identifier, id -> groups -> new ServerPlaceholderImpl(id, refresh, function.apply(groups)));
replaceMatchingPlaceholders(identifier,
(id, groups) -> new ServerPlaceholderImpl(id, refresh, function.apply(groups)));
}
@Override
@@ -502,6 +504,8 @@ public class PlaceholderManagerImpl extends RefreshableFeature implements Placeh
@NonNull Function<Matcher, Function<me.neznamy.tab.api.TabPlayer, String>> function) {
ensureActive();
registeredDynamicPlaceholders.put(identifier, id -> groups -> new PlayerPlaceholderImpl(id, refresh, function.apply(groups)));
replaceMatchingPlaceholders(identifier,
(id, groups) -> new PlayerPlaceholderImpl(id, refresh, function.apply(groups)));
}
@Override
@@ -509,6 +513,18 @@ public class PlaceholderManagerImpl extends RefreshableFeature implements Placeh
@NonNull Function<Matcher, BiFunction<me.neznamy.tab.api.TabPlayer, me.neznamy.tab.api.TabPlayer, String>> function) {
ensureActive();
registeredDynamicPlaceholders.put(identifier, id -> groups -> new RelationalPlaceholderImpl(id, refresh, function.apply(groups)));
replaceMatchingPlaceholders(identifier,
(id, groups) -> new RelationalPlaceholderImpl(id, refresh, function.apply(groups)));
}
private void replaceMatchingPlaceholders(@NotNull Pattern identifier,
@NotNull BiFunction<String, Matcher, ? extends Placeholder> factory) {
for (Entry<String, PlaceholderReference> entry : new ArrayList<>(registeredPlaceholders.entrySet())) {
Matcher matcher = identifier.matcher(entry.getKey());
if (!matcher.matches()) continue;
bridgePlaceholders.remove(entry.getKey());
registerPlaceholder(factory.apply(entry.getKey(), matcher));
}
}
@NotNull
@@ -150,15 +150,11 @@ public class BelowName extends RefreshableFeature implements JoinListener, QuitL
p.belowNameData.disabled.set(!forced);
return;
}
if (!configuration.isEnabled()) {
p.belowNameData.disabled.set(true);
return;
}
updateVisibility(p, disabledNow);
}
private boolean configuredDisabled(@NotNull TabPlayer p) {
return !configuration.isEnabled() || disableChecker.isDisableConditionMet(p);
return disableChecker.isDisableConditionMet(p);
}
private void applyDisabledState(@NotNull TabPlayer p, boolean disabled) {
@@ -294,9 +290,10 @@ public class BelowName extends RefreshableFeature implements JoinListener, QuitL
}
private void updatePlayer(@NotNull TabPlayer player) {
int value = getValue(player);
for (TabPlayer all : onlinePlayers.getPlayers()) {
setScore(player, all, getValue(all), all.belowNameData.fancyValue.getFormat(player));
if (all != player) setScore(all, player, getValue(player), player.belowNameData.fancyValue.getFormat(all));
if (all != player) setScore(player, all, getValue(all), all.belowNameData.fancyValue.getFormat(player));
setScore(all, player, value, player.belowNameData.fancyValue.getFormat(all));
}
}
@@ -330,8 +327,9 @@ public class BelowName extends RefreshableFeature implements JoinListener, QuitL
@Override
public void onVanishStatusChange(@NotNull TabPlayer player) {
if (player.isVanished()) return;
int value = getValue(player);
for (TabPlayer viewer : onlinePlayers.getPlayers()) {
setScore(viewer, player, getValue(player), player.belowNameData.fancyValue.getFormat(viewer));
setScore(viewer, player, value, player.belowNameData.fancyValue.getFormat(viewer));
}
}
@@ -17,7 +17,6 @@ import java.util.Arrays;
public class BelowNameConfiguration {
@NotNull private final ConfigurationSection section;
private final boolean enabled;
@NotNull private final String value;
@NotNull private final String title;
@NotNull private final String fancyValue;
@@ -71,7 +70,6 @@ public class BelowNameConfiguration {
return new BelowNameConfiguration(
section,
section.getBoolean("enabled", false),
value,
title,
section.getString("fancy-value", "&c" + TabConstants.Placeholder.HEALTH),
@@ -77,18 +77,10 @@ public class LayoutManagerImpl extends RefreshableFeature implements LayoutManag
p.layoutData.sortingString = p.sortingData.fullTeamName;
sortedPlayers.put(p, p.sortingData.fullTeamName);
LayoutPattern highest = getHighestLayout(p);
if (highest != null) {
sendLayout(p, highest);
}
sendLayout(p, highest);
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
if (all.layoutData.currentLayout != null) all.layoutData.currentLayout.view.onJoin(p);
}
// Unformat original entries for players who can see a layout to avoid spaces due to unparsed placeholders and such
if (highest == null) return;
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
p.getTabList().updateDisplayName(all, null);
}
}
@Override
@@ -112,17 +104,30 @@ public class LayoutManagerImpl extends RefreshableFeature implements LayoutManag
LayoutPattern current = p.layoutData.currentLayout == null ? null : p.layoutData.currentLayout.view.getPattern();
if (highest != current) {
if (current != null) p.layoutData.currentLayout.view.destroy();
p.layoutData.currentLayout = null;
if (highest != null) {
sendLayout(p, highest);
}
sendLayout(p, highest);
}
}
private void sendLayout(@NotNull TabPlayer player, @NotNull LayoutPattern pattern) {
LayoutBase view = new FakeEntryLayout(this, pattern, player);
player.layoutData.currentLayout = new LayoutData(view);
view.send();
private void sendLayout(@NotNull TabPlayer player, @Nullable LayoutPattern pattern) {
if (pattern != null) {
LayoutBase view = new FakeEntryLayout(this, pattern, player);
player.layoutData.currentLayout = new LayoutData(view);
view.send();
if (playerList != null) {
// Unformat original entries for players who can see a layout to avoid spaces due to unparsed placeholders and such
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
player.getTabList().updateDisplayName(all, null);
}
}
} else {
player.layoutData.currentLayout = null;
if (playerList != null) {
// Reformat original entries for players without any layout
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
player.getTabList().updateDisplayName(all, playerList.getTabFormat(all, player));
}
}
}
}
@Override
@@ -210,11 +215,7 @@ public class LayoutManagerImpl extends RefreshableFeature implements LayoutManag
@Override
public void resetLayout(@NonNull me.neznamy.tab.api.TabPlayer player) {
ensureActive();
TabPlayer p = (TabPlayer) player;
p.ensureLoaded();
p.layoutData.forcedLayout = null;
refresh(p, false);
sendLayout(player, null);
}
@NotNull
@@ -18,7 +18,6 @@ import java.util.Arrays;
public class PlayerListObjectiveConfiguration {
@NotNull private final ConfigurationSection section;
private final boolean enabled;
@NotNull private final String value;
@NotNull private final String fancyValue;
@NotNull private final String title;
@@ -72,7 +71,6 @@ public class PlayerListObjectiveConfiguration {
return new PlayerListObjectiveConfiguration(
section,
section.getBoolean("enabled", true),
value,
section.getString("fancy-value", "&7Ping: " + Placeholder.PING),
section.getString("title", "TAB"),
@@ -138,15 +138,11 @@ public class YellowNumber extends RefreshableFeature implements JoinListener, Qu
p.playerlistObjectiveData.disabled.set(!forced);
return;
}
if (!configuration.isEnabled()) {
p.playerlistObjectiveData.disabled.set(true);
return;
}
updateVisibility(p, disabledNow);
}
private boolean configuredDisabled(@NotNull TabPlayer p) {
return !configuration.isEnabled() || disableChecker.isDisableConditionMet(p);
return disableChecker.isDisableConditionMet(p);
}
private void applyDisabledState(@NotNull TabPlayer p, boolean disabled) {
@@ -301,8 +297,9 @@ public class YellowNumber extends RefreshableFeature implements JoinListener, Qu
@Override
public void onVanishStatusChange(@NotNull TabPlayer player) {
if (player.isVanished()) return;
int value = getValue(player);
for (TabPlayer viewer : onlinePlayers.getPlayers()) {
setScore(viewer, player, getValue(player), player.playerlistObjectiveData.fancyValue.getFormat(viewer));
setScore(viewer, player, value, player.playerlistObjectiveData.fancyValue.getFormat(viewer));
}
}
@@ -78,6 +78,7 @@ public class Condition {
List<PlaceholderReference> placeholdersInConditions = new ArrayList<>();
PlaceholderManagerImpl manager = TAB.getInstance().getPlaceholderManager();
if (manager == null) return; // Header / footer config conversion, the placeholder manager is not initialized yet
placeholdersInConditions.addAll(manager.detectPlaceholders(yes).stream().map(manager::getPlaceholderReference).collect(Collectors.toList()));
placeholdersInConditions.addAll(manager.detectPlaceholders(no).stream().map(manager::getPlaceholderReference).collect(Collectors.toList()));
for (ConditionalExpression expression : expressions) {
@@ -138,7 +138,7 @@ public class PlayerPlaceholderImpl extends TabPlaceholder implements PlayerPlace
@Override
@NotNull
public String getLastReturnedValue(@Nullable TabPlayer player) {
public synchronized String getLastReturnedValue(@Nullable TabPlayer player) {
if (player == null) return identifier;
String value = player.lastPlaceholderReturnedValues.get(this);
if (value != null) return value;
@@ -95,7 +95,7 @@ public class RelationalPlaceholderImpl extends TabPlaceholder implements Relatio
String value = request(viewer, target);
String s = replacements.findReplacement(String.valueOf(value));
viewer.lastRelationalValues.computeIfAbsent(this, v -> Collections.synchronizedMap(new WeakHashMap<>())).put(target, s);
if (!target.isLoaded()) return; // Updated on join
if (!target.isLoaded()) continue; // Updated on join
for (RefreshableFeature f : reference.getUsedByFeatures()) {
TimedCaughtTask task = new TimedCaughtTask(TAB.getInstance().getCpu(), () -> f.refresh(target, true),
f.getFeatureName(), f.getRefreshDisplayName());
@@ -115,7 +115,9 @@ public abstract class TabPlaceholder implements Placeholder {
if (PlaceholderIdentifier.isRelational(s)) continue; // Relational placeholders are handled separately
if ((identifier.startsWith("%sync:") && ("%" + identifier.substring(6)).equals(s))) continue; // Self, but as sync variant
PlaceholderReference nested = TAB.getInstance().getPlaceholderManager().getPlaceholderReference(s);
nested.addParent(reference);
if (reference != null) {
nested.addParent(reference);
}
string = string.replace(s, nested.getHandle().parse(player));
}
return string;
@@ -49,6 +49,9 @@ public abstract class SafeScoreboard<T extends TabPlayer> implements Scoreboard
/** Registered teams */
private final Map<String, Team> teams = new ConcurrentHashMap<>();
/** Teams indexed by entries */
private final Map<String, Team> teamsByEntries = new ConcurrentHashMap<>();
@Override
public synchronized void registerObjective(@NonNull String objectiveName, @NonNull TabComponent title,
@NonNull HealthDisplay display, @Nullable TabComponent numberFormat) {
@@ -143,6 +146,9 @@ public abstract class SafeScoreboard<T extends TabPlayer> implements Scoreboard
}
Team team = new Team(createTeam(name), name, prefix, suffix, visibility, collision, players, options, color);
teams.put(name, team);
for (String entry : players) {
teamsByEntries.put(entry, team);
}
if (frozen) return;
registerTeam(team);
}
@@ -154,6 +160,9 @@ public abstract class SafeScoreboard<T extends TabPlayer> implements Scoreboard
error("Tried to unregister non-existing team %s for player ", teamName);
return;
}
for (String entry : team.getPlayers()) {
teamsByEntries.remove(entry, team);
}
if (frozen) return;
unregisterTeam(team);
}
@@ -324,14 +333,14 @@ public abstract class SafeScoreboard<T extends TabPlayer> implements Scoreboard
* Team name in the packet
* @param players
* Players in the packet
* @return Modified collection of players
* @return Modified list of players
*/
@NotNull
public Collection<String> onTeamPacket(int action, @NonNull String teamName, @NonNull Collection<String> players) {
Collection<String> newList = new ArrayList<>();
public List<String> onTeamPacket(int action, @NonNull String teamName, @NonNull Collection<String> players) {
List<String> newList = new ArrayList<>();
if (action == TeamAction.CREATE || action == TeamAction.ADD_PLAYER) {
for (String entry : players) {
Team expectedTeam = getExpectedTeam(entry);
Team expectedTeam = teamsByEntries.get(entry);
if (expectedTeam == null) {
blockedTeamAdds.remove(entry);
allowedTeamAdds.put(entry, teamName);
@@ -350,7 +359,7 @@ public abstract class SafeScoreboard<T extends TabPlayer> implements Scoreboard
if (action == TeamAction.REMOVE_PLAYER) {
// TAB does not send remove player, making checks easier
for (String entry : players) {
Team expectedTeam = getExpectedTeam(entry);
Team expectedTeam = teamsByEntries.get(entry);
if (expectedTeam != null) {
allowedTeamAdds.remove(entry);
blockedTeamAdds.remove(entry);
@@ -371,14 +380,6 @@ public abstract class SafeScoreboard<T extends TabPlayer> implements Scoreboard
return newList;
}
@Nullable
private Team getExpectedTeam(@NotNull String player) {
for (Team team : teams.values()) {
if (team.getPlayers().contains(player)) return team;
}
return null;
}
/**
* Logs a message into anti-override log when blocking attempt to add
* a player into a team.
@@ -86,7 +86,7 @@ public abstract class TrackedTabList<P extends TabPlayer> implements TabList {
} else {
forcedDisplayNames.remove(target.getTablistId());
}
if (target.getVersion().getNetworkId() < ProtocolVersion.V1_8.getNetworkId()) {
if (player.getVersion().getNetworkId() < ProtocolVersion.V1_8.getNetworkId()) {
return; // Display names are not supported on 1.7 and below
}
if (containsEntry(target.getTablistId())) {