mirror of
https://github.com/NEZNAMY/TAB.git
synced 2026-11-13 12:42:06 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9da0db5f74 | ||
|
|
562a973c84 | ||
|
|
967980ddf7 | ||
|
|
81d3675a87 | ||
|
|
7be786bbaf | ||
|
|
f41c9be1f7 | ||
|
|
e88062d028 | ||
|
|
edb2828643 | ||
|
|
58c189cd0e | ||
|
|
012994847e | ||
|
|
d87801be5f | ||
|
|
6d78c0c104 | ||
|
|
3a83519122 | ||
|
|
a9f940ebed | ||
|
|
db73d9ef81 | ||
|
|
e2bd21c717 | ||
|
|
871215b7b5 | ||
|
|
a6153ef46f | ||
|
|
35475a6103 | ||
|
|
bf77ffc1a5 | ||
|
|
7a64a9a534 | ||
|
|
68e057b99c | ||
|
|
7338afff96 | ||
|
|
97ae7813aa | ||
|
|
c0f3667ac1 |
No files matched your search
@@ -60,4 +60,6 @@ body:
|
||||
- label: I ticked all of the boxes without actually reading them
|
||||
required: false
|
||||
- label: I have read the Compatibility wiki page and am not trying to run the plugin on an unsupported server version / platform
|
||||
required: true
|
||||
- label: I am willing to cooperate to get this issue resolved faster, including providing any additional information requested or testing new builds
|
||||
required: true
|
||||
@@ -0,0 +1,38 @@
|
||||
name: Compatibility issue
|
||||
description: A compatibility issue with another plugin/mod or a specific version of a platform marked as supported
|
||||
labels: ["Type: Compatibility issue"]
|
||||
body:
|
||||
- type: input
|
||||
attributes:
|
||||
label: TAB version
|
||||
description: Full version in numbers, "latest" is not a version.
|
||||
placeholder: 4.0.0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Software causing compatibility issue
|
||||
description: plugin/mod/platform version causing the issue
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional info
|
||||
description: Anything else you want to provide that will help in resolving the issue faster.
|
||||
validations:
|
||||
required: false
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Checklist
|
||||
description: Let's make sure this report is valid
|
||||
options:
|
||||
- label: I am running latest version of the plugin
|
||||
required: true
|
||||
- label: I ticked all of the boxes without actually reading them
|
||||
required: false
|
||||
- label: The compatibility wiki page does not mention this incompatibility
|
||||
required: true
|
||||
- label: I am willing to cooperate to get this issue resolved faster, including providing any additional information requested or testing new builds
|
||||
required: true
|
||||
@@ -0,0 +1 @@
|
||||
blank_issues_enabled: false
|
||||
+1
-1
@@ -4,7 +4,7 @@ plugins {
|
||||
|
||||
allprojects {
|
||||
group = "me.neznamy"
|
||||
version = "4.0.7"
|
||||
version = "4.0.8-SNAPSHOT"
|
||||
description = "An all-in-one solution that works"
|
||||
|
||||
ext.set("id", "tab")
|
||||
|
||||
@@ -62,11 +62,7 @@ public class BukkitUtils {
|
||||
sb.append(component.getModifier().getColor().getLegacyColor().getFormat());
|
||||
}
|
||||
}
|
||||
if (component.getModifier().isBold()) sb.append(EnumChatFormat.BOLD.getFormat());
|
||||
if (component.getModifier().isStrikethrough()) sb.append(EnumChatFormat.STRIKETHROUGH.getFormat());
|
||||
if (component.getModifier().isItalic()) sb.append(EnumChatFormat.ITALIC.getFormat());
|
||||
if (component.getModifier().isObfuscated()) sb.append(EnumChatFormat.OBFUSCATED.getFormat());
|
||||
if (component.getModifier().isUnderlined()) sb.append(EnumChatFormat.UNDERLINE.getFormat());
|
||||
sb.append(component.getModifier().getMagicCodes());
|
||||
if (component.getText() != null) sb.append(component.getText());
|
||||
for (IChatBaseComponent extra : component.getExtra()) {
|
||||
sb.append(toBukkitFormat(extra, rgbClient));
|
||||
|
||||
@@ -7,6 +7,7 @@ import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -112,4 +113,15 @@ public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpan
|
||||
public void setValue(@NotNull TabPlayer player, @NotNull String key, @NotNull String value) {
|
||||
values.computeIfAbsent(player, p -> new HashMap<>()).put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({"UnstableApiUsage", "deprecation"})
|
||||
public void unregisterExpansion() {
|
||||
if (ReflectionUtils.methodExists(PlaceholderExpansion.class, "unregister")) {
|
||||
// Added in 2.10.7 (Jul 28, 2020)
|
||||
unregister();
|
||||
} else {
|
||||
PlaceholderAPI.unregisterExpansion(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,19 +23,17 @@ public abstract class HeaderFooter {
|
||||
* Finds the best available instance for current server software.
|
||||
*/
|
||||
public static void findInstance() {
|
||||
if (PacketHeaderFooter.isAvailable()) {
|
||||
instance = new PacketHeaderFooter();
|
||||
} else if (PaperHeaderFooter.isAvailable()) {
|
||||
if (PaperHeaderFooter.isAvailable()) {
|
||||
instance = new PaperHeaderFooter();
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
|
||||
"sending Header/Footer due to a compatibility error. Using fallback solution using Paper API.");
|
||||
} else if (PacketHeaderFooter.isAvailable()) {
|
||||
instance = new PacketHeaderFooter();
|
||||
} else if (BukkitHeaderFooter.isAvailable()) {
|
||||
instance = new BukkitHeaderFooter();
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
|
||||
"sending Header/Footer due to a compatibility error. Using fallback solution using Bukkit API. " +
|
||||
"This will drastically drop performance, as well as miss support for fonts (added in 1.16). " +
|
||||
"Please update the plugin with to version proper support for your server version.");
|
||||
} else if (BukkitReflection.getMinorVersion() >= 8){
|
||||
} else if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
|
||||
"sending Header/Footer due to a compatibility error. No fallback solution was found either. This will " +
|
||||
"result in the feature not working. " +
|
||||
|
||||
@@ -66,6 +66,9 @@ public class PacketEntityView implements EntityView {
|
||||
private static Class<?> PacketPlayOutNamedEntitySpawn;
|
||||
private static Field PacketPlayOutNamedEntitySpawn_ENTITYID;
|
||||
|
||||
private static Class<?> ClientboundBundlePacket;
|
||||
private static Field ClientboundBundlePacket_packets;
|
||||
|
||||
private static Object dummyEntity;
|
||||
|
||||
@Getter
|
||||
@@ -86,6 +89,10 @@ public class PacketEntityView implements EntityView {
|
||||
loadEntityTeleport();
|
||||
loadEntityMove();
|
||||
loadEntitySpawn();
|
||||
if (BukkitReflection.is1_19_4Plus()) {
|
||||
ClientboundBundlePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundBundlePacket");
|
||||
ClientboundBundlePacket_packets = ReflectionUtils.getOnlyField(ClientboundBundlePacket.getSuperclass(), Iterable.class);
|
||||
}
|
||||
available = true;
|
||||
}
|
||||
|
||||
@@ -366,6 +373,17 @@ public class PacketEntityView implements EntityView {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBundlePacket(@NotNull Object packet) {
|
||||
return BukkitReflection.is1_19_4Plus() && ClientboundBundlePacket.isInstance(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public Iterable<Object> getPackets(@NotNull Object bundlePacket) {
|
||||
return (Iterable<Object>) ClientboundBundlePacket_packets.get(bundlePacket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns class from given potential names. Supports modded servers, such as Thermos.
|
||||
*
|
||||
|
||||
@@ -142,7 +142,7 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
@Override
|
||||
public void registerUnknownPlaceholder(@NotNull String identifier) {
|
||||
if (!placeholderAPI) {
|
||||
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, -1, () -> identifier);
|
||||
registerDummyPlaceholder(identifier);
|
||||
return;
|
||||
}
|
||||
PlaceholderManagerImpl pl = TAB.getInstance().getPlaceholderManager();
|
||||
|
||||
+5
-4
@@ -4,6 +4,7 @@ import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.Limitations;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
@@ -155,22 +156,22 @@ public class BukkitScoreboard extends Scoreboard<BukkitTabPlayer> {
|
||||
|
||||
public void newObjective(String objectiveName, String criteria, String title, @NotNull HealthDisplay display) {
|
||||
if (serverMinorVersion >= 14) {
|
||||
scoreboard.registerNewObjective(objectiveName, criteria, transform(title, 128, 32), RenderType.valueOf(display.name()));
|
||||
scoreboard.registerNewObjective(objectiveName, criteria, transform(title, 128, Limitations.SCOREBOARD_TITLE_PRE_1_13), RenderType.valueOf(display.name()));
|
||||
} else {
|
||||
setDisplayName(scoreboard.registerNewObjective(objectiveName, display == HealthDisplay.HEARTS ? "health" : "dummy"), title);
|
||||
}
|
||||
}
|
||||
|
||||
public void setDisplayName(@NotNull Objective objective, @NotNull String displayName) {
|
||||
objective.setDisplayName(transform(displayName, 128, 32));
|
||||
objective.setDisplayName(transform(displayName, 128, Limitations.SCOREBOARD_TITLE_PRE_1_13));
|
||||
}
|
||||
|
||||
public void setPrefix(@NotNull Team team, @NotNull String prefix) {
|
||||
team.setPrefix(transform(prefix, 64, 16));
|
||||
team.setPrefix(transform(prefix, 64, Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13));
|
||||
}
|
||||
|
||||
public void setSuffix(@NotNull Team team, @NotNull String suffix) {
|
||||
team.setSuffix(transform(suffix, 64, 16));
|
||||
team.setSuffix(transform(suffix, 64, Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,7 +4,6 @@ import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.shared.platform.bossbar.BossBar;
|
||||
import me.neznamy.tab.api.bossbar.BarColor;
|
||||
import me.neznamy.tab.api.bossbar.BarStyle;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
@@ -35,7 +34,7 @@ public class BungeeBossBar implements BossBar {
|
||||
|
||||
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 0);
|
||||
packet.setHealth(progress);
|
||||
packet.setTitle(IChatBaseComponent.optimizedComponent(title).toString(player.getVersion()));
|
||||
BungeeMultiVersion.setTitle(packet, title, player.getVersion());
|
||||
packet.setColor(color.ordinal());
|
||||
packet.setDivision(style.ordinal());
|
||||
player.sendPacket(packet);
|
||||
@@ -49,7 +48,7 @@ public class BungeeBossBar implements BossBar {
|
||||
if (player.getVersion().getMinorVersion() < 9) return;
|
||||
|
||||
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 3);
|
||||
packet.setTitle(IChatBaseComponent.optimizedComponent(title).toString(player.getVersion()));
|
||||
BungeeMultiVersion.setTitle(packet, title, player.getVersion());
|
||||
player.sendPacket(packet);
|
||||
}
|
||||
|
||||
|
||||
+24
-1
@@ -1,5 +1,6 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.platform.EventListener;
|
||||
@@ -25,7 +26,29 @@ public class BungeeEventListener extends EventListener<ProxiedPlayer> implements
|
||||
|
||||
@EventHandler
|
||||
public void onSwitch(ServerSwitchEvent e) {
|
||||
serverChange(e.getPlayer(), e.getPlayer().getUniqueId(), e.getPlayer().getServer().getInfo().getName(), false);
|
||||
TAB tab = TAB.getInstance();
|
||||
if (tab.isPluginDisabled()) return;
|
||||
tab.getCPUManager().runTask(() -> {
|
||||
TabPlayer player = tab.getPlayer(e.getPlayer().getUniqueId());
|
||||
if (player == null) {
|
||||
player = createPlayer(e.getPlayer());
|
||||
|
||||
// I don't think this is actually needed, but someone said it fixed geyser warn in console
|
||||
// Doing this won't hurt, so whatever
|
||||
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_20_2.getNetworkId()) {
|
||||
player.getScoreboard().freeze();
|
||||
}
|
||||
|
||||
tab.getFeatureManager().onJoin(player);
|
||||
} else {
|
||||
tab.getFeatureManager().onServerChange(player.getUniqueId(), e.getPlayer().getServer().getInfo().getName());
|
||||
if (player.getVersion().getNetworkId() < ProtocolVersion.V1_20_2.getNetworkId()) {
|
||||
// For versions below 1.20.2 the tablist is already clean when this event is called
|
||||
// For 1.20.2+ this event is called before, so we listen to Login packet instead
|
||||
tab.getFeatureManager().onTabListClear(player);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.chat.ChatModifier;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.md_5.bungee.UserConnection;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.protocol.DefinedPacket;
|
||||
import net.md_5.bungee.protocol.packet.*;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class BungeeMultiVersion {
|
||||
|
||||
/**
|
||||
* Flag tracking if the current BungeeCord version contains changes made in build 1732.
|
||||
* This build changes "ScoreboardDisplay" field "position" from "byte" to "int".
|
||||
*/
|
||||
private static final boolean b1732Plus = ReflectionUtils.getFields(ScoreboardDisplay.class, int.class).size() > 0;
|
||||
|
||||
/**
|
||||
* Flag tracking if the current BungeeCord version contains changes made in build 1738.
|
||||
* This build adds "sendPacketQueued" method to UserConnection class.
|
||||
*/
|
||||
private static final boolean b1738Plus = ReflectionUtils.methodExists(UserConnection.class, "sendPacketQueued", DefinedPacket.class);
|
||||
|
||||
/**
|
||||
* Flag tracking if the current BungeeCord version contains changes made in build 1760.
|
||||
* This build changes all packet fields for displaying information from String to BaseComponent.
|
||||
*/
|
||||
private static final boolean b1760Plus = ReflectionUtils.methodExists(BossBar.class, "setTitle", BaseComponent.class);
|
||||
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
public static ScoreboardDisplay newScoreboardDisplay(int position, @NotNull String name) {
|
||||
if (b1732Plus) {
|
||||
return ScoreboardDisplay.class.getConstructor(int.class, String.class).newInstance(position, name);
|
||||
} else {
|
||||
return new ScoreboardDisplay((byte) position, name);
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static void sendPacket(@NotNull BungeeTabPlayer player, @NotNull DefinedPacket packet) {
|
||||
if (b1738Plus) {
|
||||
try {
|
||||
UserConnection.class.getDeclaredMethod("sendPacketQueued", DefinedPacket.class).invoke(player.getPlayer(), packet);
|
||||
} catch (InvocationTargetException BungeeCordBug) { // Since we use reflection we must catch this one
|
||||
// java.lang.NullPointerException: Cannot invoke "net.md_5.bungee.protocol.MinecraftEncoder.getProtocol()" because the return value of "io.netty.channel.ChannelPipeline.get(java.lang.Class)" is null
|
||||
// at net.md_5.bungee.netty.ChannelWrapper.getEncodeProtocol(ChannelWrapper.java:51)
|
||||
// at net.md_5.bungee.UserConnection.sendPacketQueued(UserConnection.java:194)
|
||||
if (TAB.getInstance().getConfiguration().isDebugMode()) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to deliver packet to player " + player.getName() +
|
||||
" (online = " + player.getPlayer().isConnected() + "): " + packet, BungeeCordBug);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
player.getPlayer().unsafe().sendPacket(packet);
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static void setDisplayName(@NotNull Item item, @NotNull IChatBaseComponent displayName, @NotNull ProtocolVersion version) {
|
||||
if (b1760Plus) {
|
||||
Item.class.getMethod("setDisplayName", BaseComponent.class).invoke(item, toComponent(displayName, version));
|
||||
} else {
|
||||
item.setDisplayName(displayName.toString(version));
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
public static PlayerListHeaderFooter newPlayerListHeaderFooter(@NotNull IChatBaseComponent header,
|
||||
@NotNull IChatBaseComponent footer, ProtocolVersion version) {
|
||||
if (b1760Plus) {
|
||||
return PlayerListHeaderFooter.class.getConstructor(BaseComponent.class, BaseComponent.class).newInstance(
|
||||
toComponent(header, version),
|
||||
toComponent(footer, version)
|
||||
);
|
||||
} else {
|
||||
return new PlayerListHeaderFooter(
|
||||
header.toString(version),
|
||||
footer.toString(version)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static void setTitle(@NotNull BossBar bossBar, @NotNull String title, @NotNull ProtocolVersion version) {
|
||||
if (b1760Plus) {
|
||||
BossBar.class.getMethod("setTitle", BaseComponent.class).invoke(bossBar, toComponent(IChatBaseComponent.optimizedComponent(title), version));
|
||||
} else {
|
||||
bossBar.setTitle(IChatBaseComponent.optimizedComponent(title).toString(version));
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
public static ScoreboardObjective newScoreboardObjective(@NotNull String objective, @NotNull IChatBaseComponent title,
|
||||
@Nullable ScoreboardObjective.HealthDisplay healthDisplay, byte action,
|
||||
@NotNull ProtocolVersion version) {
|
||||
if (b1760Plus) {
|
||||
return ScoreboardObjective.class
|
||||
.getConstructor(String.class, BaseComponent.class, ScoreboardObjective.HealthDisplay.class, byte.class)
|
||||
.newInstance(objective, toComponent(title, version), healthDisplay, action);
|
||||
} else {
|
||||
return new ScoreboardObjective(objective, title.toString(version), healthDisplay, action);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
public static Team newTeam(@NotNull String name, byte mode, @NotNull String prefix, @NotNull String suffix,
|
||||
@NotNull String nameTagVisibility, @NotNull String collisionRule, int color,
|
||||
byte options, @Nullable String[] players, @NotNull ProtocolVersion version) {
|
||||
if (b1760Plus) {
|
||||
return Team.class.getConstructor(String.class, byte.class, BaseComponent.class, BaseComponent.class,
|
||||
BaseComponent.class, String.class, String.class, int.class, byte.class, String[].class)
|
||||
.newInstance(
|
||||
name,
|
||||
mode,
|
||||
toComponent(IChatBaseComponent.optimizedComponent(name), version),
|
||||
toComponent(IChatBaseComponent.optimizedComponent(prefix), version),
|
||||
toComponent(IChatBaseComponent.optimizedComponent(suffix), version),
|
||||
nameTagVisibility,
|
||||
collisionRule,
|
||||
color,
|
||||
options,
|
||||
players
|
||||
);
|
||||
} else {
|
||||
return new Team(
|
||||
name,
|
||||
mode,
|
||||
jsonOrRaw(name, version),
|
||||
jsonOrRaw(prefix, version),
|
||||
jsonOrRaw(suffix, version),
|
||||
nameTagVisibility,
|
||||
collisionRule,
|
||||
color,
|
||||
options,
|
||||
players
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If player's version is 1.13+, creates a component from given text and returns
|
||||
* it as a serialized component, which BungeeCord uses.
|
||||
* <p>
|
||||
* If player's version is 1.12-, the text is returned
|
||||
*
|
||||
* @param text
|
||||
* Text to convert
|
||||
* @param version
|
||||
* Protocol version to create json string for
|
||||
* @return serialized component for 1.13+ clients, cut string for 1.12-
|
||||
*/
|
||||
@NotNull
|
||||
private static String jsonOrRaw(@NotNull String text, @NotNull ProtocolVersion version) {
|
||||
if (version.getMinorVersion() >= 13) {
|
||||
return IChatBaseComponent.optimizedComponent(text).toString(version);
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts internal component class to platform's component class
|
||||
*
|
||||
* @param component
|
||||
* Component to convert
|
||||
* @param version
|
||||
* Game version to convert component for
|
||||
* @return Converted component
|
||||
*/
|
||||
private static BaseComponent toComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion version) {
|
||||
TextComponent textComponent = new TextComponent(component.getText());
|
||||
ChatModifier modifier = component.getModifier();
|
||||
if (modifier.getColor() != null) textComponent.setColor(ChatColor.of(
|
||||
modifier.getColor().toString(version.getMinorVersion() >= 16)));
|
||||
if (modifier.isBold()) textComponent.setBold(true);
|
||||
if (modifier.isItalic()) textComponent.setItalic(true);
|
||||
if (modifier.isObfuscated()) textComponent.setObfuscated(true);
|
||||
if (modifier.isStrikethrough()) textComponent.setStrikethrough(true);
|
||||
if (modifier.isUnderlined()) textComponent.setUnderlined(true);
|
||||
if (!component.getExtra().isEmpty()) textComponent.setExtra(
|
||||
component.getExtra().stream().map(c -> toComponent(c, version)).collect(Collectors.toList()));
|
||||
return textComponent;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -163,7 +163,7 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
|
||||
for (PlayerListItem.Item item : update.getItems()) {
|
||||
if (update.getActions().contains(PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME)) {
|
||||
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, item.getUuid());
|
||||
if (newDisplayName != null) item.setDisplayName(newDisplayName.toString(receiver.getVersion()));
|
||||
if (newDisplayName != null) BungeeMultiVersion.setDisplayName(item, newDisplayName, receiver.getVersion());
|
||||
}
|
||||
if (update.getActions().contains(PlayerListItemUpdate.Action.UPDATE_LATENCY)) {
|
||||
item.setPing(TAB.getInstance().getFeatureManager().onLatencyChange(receiver, item.getUuid(), item.getPing()));
|
||||
@@ -177,7 +177,7 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
|
||||
for (PlayerListItem.Item item : listItem.getItems()) {
|
||||
if (listItem.getAction() == PlayerListItem.Action.UPDATE_DISPLAY_NAME || listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
|
||||
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, item.getUuid());
|
||||
if (newDisplayName != null) item.setDisplayName(newDisplayName.toString(receiver.getVersion()));
|
||||
if (newDisplayName != null) BungeeMultiVersion.setDisplayName(item, newDisplayName, receiver.getVersion());
|
||||
}
|
||||
if (listItem.getAction() == PlayerListItem.Action.UPDATE_LATENCY || listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
|
||||
item.setPing(TAB.getInstance().getFeatureManager().onLatencyChange(receiver, item.getUuid(), item.getPing()));
|
||||
|
||||
+37
-41
@@ -4,14 +4,11 @@ import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import net.md_5.bungee.protocol.packet.ScoreboardDisplay;
|
||||
import net.md_5.bungee.protocol.packet.ScoreboardObjective;
|
||||
import net.md_5.bungee.protocol.packet.ScoreboardScore;
|
||||
import net.md_5.bungee.protocol.packet.Team;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
@@ -21,11 +18,6 @@ import java.util.Collection;
|
||||
*/
|
||||
public class BungeeScoreboard extends Scoreboard<BungeeTabPlayer> {
|
||||
|
||||
/** Constructor to support both <1.20.2 and 1.20.2+ builds */
|
||||
@SuppressWarnings("unchecked")
|
||||
private static final Constructor<ScoreboardDisplay> newScoreboardDisplay = (Constructor<ScoreboardDisplay>)
|
||||
Arrays.stream(ScoreboardDisplay.class.getConstructors()).filter(c -> c.getParameterCount() == 2).findAny().orElse(null);
|
||||
|
||||
public BungeeScoreboard(@NotNull BungeeTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
@@ -33,36 +25,39 @@ public class BungeeScoreboard extends Scoreboard<BungeeTabPlayer> {
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
|
||||
player.sendPacket(newScoreboardDisplay.newInstance((byte)slot.ordinal(), objective));
|
||||
player.sendPacket(BungeeMultiVersion.newScoreboardDisplay(slot.ordinal(), objective));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
|
||||
player.sendPacket(new ScoreboardObjective(
|
||||
player.sendPacket(BungeeMultiVersion.newScoreboardObjective(
|
||||
objectiveName,
|
||||
jsonOrRaw(title),
|
||||
IChatBaseComponent.optimizedComponent(title),
|
||||
ScoreboardObjective.HealthDisplay.valueOf(display.name()),
|
||||
(byte) 0
|
||||
(byte) 0,
|
||||
player.getVersion()
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective0(@NotNull String objectiveName) {
|
||||
player.sendPacket(new ScoreboardObjective(
|
||||
player.sendPacket(BungeeMultiVersion.newScoreboardObjective(
|
||||
objectiveName,
|
||||
"", // Empty string to prevent kick on 1.7
|
||||
new IChatBaseComponent(), // Empty value instead of null to prevent NPE kick on 1.7
|
||||
null,
|
||||
(byte) 1
|
||||
(byte) 1,
|
||||
player.getVersion()
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
|
||||
player.sendPacket(new ScoreboardObjective(
|
||||
player.sendPacket(BungeeMultiVersion.newScoreboardObjective(
|
||||
objectiveName,
|
||||
jsonOrRaw(title),
|
||||
IChatBaseComponent.optimizedComponent(title),
|
||||
ScoreboardObjective.HealthDisplay.valueOf(display.name()),
|
||||
(byte) 2
|
||||
(byte) 2,
|
||||
player.getVersion()
|
||||
));
|
||||
}
|
||||
|
||||
@@ -74,8 +69,18 @@ public class BungeeScoreboard extends Scoreboard<BungeeTabPlayer> {
|
||||
if (player.getVersion().getMinorVersion() >= 13) {
|
||||
color = EnumChatFormat.lastColorsOf(prefix).ordinal();
|
||||
}
|
||||
player.sendPacket(new Team(name, (byte) 0, jsonOrRaw(name), jsonOrRaw(prefix), jsonOrRaw(suffix),
|
||||
visibility.toString(), collision.toString(), color, (byte)options, players.toArray(new String[0])));
|
||||
player.sendPacket(BungeeMultiVersion.newTeam(
|
||||
name,
|
||||
(byte) 0,
|
||||
prefix,
|
||||
suffix,
|
||||
visibility.toString(),
|
||||
collision.toString(),
|
||||
color,
|
||||
(byte)options,
|
||||
players.toArray(new String[0]),
|
||||
player.getVersion()
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -90,8 +95,18 @@ public class BungeeScoreboard extends Scoreboard<BungeeTabPlayer> {
|
||||
if (player.getVersion().getMinorVersion() >= 13) {
|
||||
color = EnumChatFormat.lastColorsOf(prefix).ordinal();
|
||||
}
|
||||
player.sendPacket(new Team(name, (byte) 2, jsonOrRaw(name), jsonOrRaw(prefix),
|
||||
jsonOrRaw(suffix), visibility.toString(), collision.toString(), color, (byte)options, null));
|
||||
player.sendPacket(BungeeMultiVersion.newTeam(
|
||||
name,
|
||||
(byte) 2,
|
||||
prefix,
|
||||
suffix,
|
||||
visibility.toString(),
|
||||
collision.toString(),
|
||||
color,
|
||||
(byte)options,
|
||||
null,
|
||||
player.getVersion()
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -103,23 +118,4 @@ public class BungeeScoreboard extends Scoreboard<BungeeTabPlayer> {
|
||||
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
|
||||
player.sendPacket(new ScoreboardScore(playerName, (byte) 1, objective, 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* If player's version is 1.13+, creates a component from given text and returns
|
||||
* it as a serialized component, which BungeeCord uses.
|
||||
* <p>
|
||||
* If player's version is 1.12-, the text is returned
|
||||
*
|
||||
* @param text
|
||||
* Text to convert
|
||||
* @return serialized component for 1.13+ clients, cut string for 1.12-
|
||||
*/
|
||||
@NotNull
|
||||
private String jsonOrRaw(@NotNull String text) {
|
||||
if (player.getVersion().getMinorVersion() >= 13) {
|
||||
return IChatBaseComponent.optimizedComponent(text).toString(player.getVersion());
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ public class BungeeTAB extends Plugin {
|
||||
public void onEnable() {
|
||||
if (!ReflectionUtils.classExists("net.md_5.bungee.protocol.packet.PlayerListItemUpdate")) {
|
||||
getLogger().info(EnumChatFormat.color("&cThe plugin requires BungeeCord build #1671 " +
|
||||
"(with 1.19.3 support) and up (or an equivalent fork) to work."));
|
||||
"(released on December 7th, 2022) and up (or an equivalent fork) to work."));
|
||||
return;
|
||||
}
|
||||
TAB.create(new BungeePlatform(this));
|
||||
|
||||
@@ -14,8 +14,6 @@ import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.bossbar.BossBar;
|
||||
import me.neznamy.tab.shared.proxy.ProxyTabPlayer;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.md_5.bungee.UserConnection;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.chat.ComponentSerializer;
|
||||
@@ -26,8 +24,6 @@ import net.md_5.bungee.protocol.Property;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
/**
|
||||
* TabPlayer implementation for BungeeCord
|
||||
*/
|
||||
@@ -37,9 +33,6 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
|
||||
/** Flag tracking plugin presence */
|
||||
private static final boolean premiumVanish = ProxyServer.getInstance().getPluginManager().getPlugin("PremiumVanish") != null;
|
||||
|
||||
/** Flag tracking version of bungeecord with packet queue due to configuration phase */
|
||||
private static final boolean packetQueue = ReflectionUtils.methodExists(UserConnection.class, "sendPacketQueued", DefinedPacket.class);
|
||||
|
||||
/** Player's scoreboard */
|
||||
@NotNull
|
||||
private final Scoreboard<BungeeTabPlayer> scoreboard = new BungeeScoreboard(this);
|
||||
@@ -113,6 +106,7 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
|
||||
|
||||
@Override
|
||||
public void sendPluginMessage(byte[] message) {
|
||||
if (!getPlayer().isConnected()) return;
|
||||
try {
|
||||
getPlayer().getServer().sendData(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME, message);
|
||||
} catch (NullPointerException BungeeCordBug) {
|
||||
@@ -169,20 +163,7 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
|
||||
*/
|
||||
@SneakyThrows
|
||||
public void sendPacket(@NotNull DefinedPacket packet) {
|
||||
if (packetQueue) {
|
||||
try {
|
||||
UserConnection.class.getDeclaredMethod("sendPacketQueued", DefinedPacket.class).invoke(player, packet);
|
||||
} catch (InvocationTargetException BungeeCordBug) { // Since we use reflection we must catch this one
|
||||
// java.lang.NullPointerException: Cannot invoke "net.md_5.bungee.protocol.MinecraftEncoder.getProtocol()" because the return value of "io.netty.channel.ChannelPipeline.get(java.lang.Class)" is null
|
||||
// at net.md_5.bungee.netty.ChannelWrapper.getEncodeProtocol(ChannelWrapper.java:51)
|
||||
// at net.md_5.bungee.UserConnection.sendPacketQueued(UserConnection.java:194)
|
||||
if (TAB.getInstance().getConfiguration().isDebugMode()) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to deliver packet to player " + getName() +
|
||||
" (online = " + getPlayer().isConnected() + "): " + packet, BungeeCordBug);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
getPlayer().unsafe().sendPacket(packet);
|
||||
}
|
||||
if (!getPlayer().isConnected()) return;
|
||||
BungeeMultiVersion.sendPacket(this, packet);
|
||||
}
|
||||
}
|
||||
+5
-6
@@ -1,13 +1,13 @@
|
||||
package me.neznamy.tab.platforms.bungeecord.tablist;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeMultiVersion;
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.md_5.bungee.UserConnection;
|
||||
import net.md_5.bungee.protocol.Property;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListHeaderFooter;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
|
||||
import net.md_5.bungee.tab.ServerUnique;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -33,10 +33,7 @@ public abstract class BungeeTabList implements TabList {
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
|
||||
player.sendPacket(new PlayerListHeaderFooter(
|
||||
header.toString(player.getVersion()),
|
||||
footer.toString(player.getVersion())
|
||||
));
|
||||
player.sendPacket(BungeeMultiVersion.newPlayerListHeaderFooter(header, footer, player.getVersion()));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -49,7 +46,9 @@ public abstract class BungeeTabList implements TabList {
|
||||
@NotNull
|
||||
public Item entryToItem(Entry entry) {
|
||||
Item item = item(entry.getUniqueId());
|
||||
if (entry.getDisplayName() != null) item.setDisplayName(entry.getDisplayName().toString(player.getVersion()));
|
||||
if (entry.getDisplayName() != null) {
|
||||
BungeeMultiVersion.setDisplayName(item, entry.getDisplayName(), player.getVersion());
|
||||
}
|
||||
item.setGamemode(entry.getGameMode());
|
||||
item.setListed(true);
|
||||
item.setPing(entry.getLatency());
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
package me.neznamy.tab.platforms.bungeecord.tablist;
|
||||
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeMultiVersion;
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
|
||||
@@ -32,7 +33,7 @@ public class BungeeTabList1193 extends BungeeTabList {
|
||||
@Override
|
||||
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
|
||||
Item item = item(entry);
|
||||
item.setDisplayName(displayName == null ? null : displayName.toString(player.getVersion()));
|
||||
if (displayName != null) BungeeMultiVersion.setDisplayName(item, displayName, player.getVersion());
|
||||
sendPacket(EnumSet.of(PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME), item);
|
||||
}
|
||||
|
||||
|
||||
+5
-2
@@ -1,6 +1,8 @@
|
||||
package me.neznamy.tab.platforms.bungeecord.tablist;
|
||||
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeMultiVersion;
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
|
||||
import me.neznamy.tab.shared.Limitations;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListItem;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -63,7 +65,8 @@ public class BungeeTabList17 extends BungeeTabList {
|
||||
public void addEntry(@NotNull Entry entry) {
|
||||
addUuid(entry.getUniqueId());
|
||||
String displayNameString = entry.getDisplayName() == null ? String.valueOf(entry.getName()) : entry.getDisplayName().toLegacyText();
|
||||
if (displayNameString.length() > 16) displayNameString = displayNameString.substring(0, 16); // 16 character limit
|
||||
if (displayNameString.length() > Limitations.MAX_DISPLAY_NAME_LENGTH_1_7)
|
||||
displayNameString = displayNameString.substring(0, Limitations.MAX_DISPLAY_NAME_LENGTH_1_7);
|
||||
update(PlayerListItem.Action.ADD_PLAYER, createItem(entry.getName(), displayNameString, entry.getLatency()));
|
||||
|
||||
// Add to map
|
||||
@@ -81,7 +84,7 @@ public class BungeeTabList17 extends BungeeTabList {
|
||||
private PlayerListItem.Item createItem(@Nullable String username, @NotNull String displayName, int latency) {
|
||||
PlayerListItem.Item item = new PlayerListItem.Item();
|
||||
item.setUsername(username);
|
||||
item.setDisplayName(displayName);
|
||||
BungeeMultiVersion.setDisplayName(item, IChatBaseComponent.optimizedComponent(displayName), player.getVersion());
|
||||
item.setPing(latency);
|
||||
return item;
|
||||
}
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
package me.neznamy.tab.platforms.bungeecord.tablist;
|
||||
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeMultiVersion;
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListItem;
|
||||
@@ -28,7 +29,7 @@ public class BungeeTabList18 extends BungeeTabList {
|
||||
@Override
|
||||
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
|
||||
Item item = item(entry);
|
||||
item.setDisplayName(displayName == null ? null : displayName.toString(player.getVersion()));
|
||||
if (displayName != null) BungeeMultiVersion.setDisplayName(item, displayName, player.getVersion());
|
||||
sendPacket(PlayerListItem.Action.UPDATE_DISPLAY_NAME, item);
|
||||
}
|
||||
|
||||
|
||||
@@ -84,4 +84,15 @@ public record FabricEntityView(FabricTabPlayer player) implements EntityView {
|
||||
public int[] getDestroyedEntities(@NotNull Object destroyPacket) {
|
||||
return FabricMultiVersion.getDestroyedEntities(destroyPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBundlePacket(@NotNull Object packet) {
|
||||
return FabricMultiVersion.isBundlePacket(packet);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Iterable<Object> getPackets(@NotNull Object bundlePacket) {
|
||||
return (Iterable<Object>) (Object) FabricMultiVersion.getPackets(bundlePacket);
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ public class FabricEventListener extends EventListener<ServerPlayer> {
|
||||
public void register() {
|
||||
ServerPlayConnectionEvents.DISCONNECT.register((connection, $) -> quit(connection.player.getUUID()));
|
||||
ServerPlayConnectionEvents.JOIN.register((connection, $, $$) -> join(connection.player));
|
||||
FabricMultiVersion.registerEntityEvents(this::replacePlayer);
|
||||
FabricMultiVersion.registerEntityEvents(this::replacePlayer, this::worldChange);
|
||||
//TODO command preprocess
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ import net.minecraft.server.level.ServerBossEvent;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.storage.ServerLevelData;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraft.world.scores.Objective;
|
||||
import net.minecraft.world.scores.PlayerTeam;
|
||||
@@ -48,6 +50,20 @@ public class FabricMultiVersion {
|
||||
return 13;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getWorldName(Level level) {
|
||||
// 1.15.2-
|
||||
// return level.getLevelData().getLevelName() + level.dimension.getType().getFileSuffix();
|
||||
|
||||
// 1.16+
|
||||
String path = level.dimension().location().getPath();
|
||||
String dimensionSuffix = switch (path) {
|
||||
case "overworld" -> ""; // No suffix for overworld
|
||||
case "the_nether" -> "_nether";
|
||||
default -> "_" + path; // End + default behavior for other dimensions created by mods
|
||||
};
|
||||
return ((ServerLevelData)level.getLevelData()).getLevelName() + dimensionSuffix;
|
||||
}
|
||||
|
||||
public static void sendSystemMessage(CommandSourceStack source, Component message) {
|
||||
// 1.19.4-
|
||||
@@ -73,11 +89,16 @@ public class FabricMultiVersion {
|
||||
// TODO sneaking
|
||||
}
|
||||
|
||||
public static void registerEntityEvents(BiConsumer<UUID, ServerPlayer> action) {
|
||||
public static void registerEntityEvents(BiConsumer<UUID, ServerPlayer> respawnAction, BiConsumer<UUID, String> worldChangeAction) {
|
||||
// 1.16+
|
||||
net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents.AFTER_RESPAWN.register((oldPlayer, newPlayer, alive) ->
|
||||
action.accept(oldPlayer.getUUID(), newPlayer));
|
||||
// TODO world switch with proper world name
|
||||
net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents.AFTER_RESPAWN.register(
|
||||
(oldPlayer, newPlayer, alive) -> {
|
||||
respawnAction.accept(newPlayer.getUUID(), newPlayer);
|
||||
// respawning from death & taking end portal in the end do not call world change event
|
||||
worldChangeAction.accept(newPlayer.getUUID(), getWorldName(newPlayer.level));
|
||||
});
|
||||
net.fabricmc.fabric.api.entity.event.v1.ServerEntityWorldChangeEvents.AFTER_PLAYER_CHANGE_WORLD.register(
|
||||
(player, origin, destination) -> worldChangeAction.accept(player.getUUID(), getWorldName(destination)));
|
||||
}
|
||||
|
||||
public static ClientboundTabListPacket setHeaderFooter(Component header, Component footer) {
|
||||
@@ -259,6 +280,16 @@ public class FabricMultiVersion {
|
||||
return new ClientboundSetEntityDataPacket(entityId, (List<SynchedEntityData.DataValue<?>>) data.build());
|
||||
}
|
||||
|
||||
public static boolean isBundlePacket(Object packet) {
|
||||
// 1.19.4+
|
||||
return packet instanceof ClientboundBundlePacket;
|
||||
}
|
||||
|
||||
public static Iterable<Packet<ClientGamePacketListener>> getPackets(Object bundlePacket) {
|
||||
// 1.19.4+
|
||||
return ((ClientboundBundlePacket)bundlePacket).subPackets();
|
||||
}
|
||||
|
||||
public static ClientboundSetDisplayObjectivePacket newDisplayObjective(int slot, Objective objective) {
|
||||
// 1.20.1-
|
||||
// return new ClientboundSetDisplayObjectivePacket(slot, objective);
|
||||
|
||||
@@ -29,7 +29,7 @@ public record FabricPlatform(MinecraftServer server) implements BackendPlatform
|
||||
|
||||
@Override
|
||||
public void registerUnknownPlaceholder(@NotNull String identifier) {
|
||||
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, -1, () -> identifier);
|
||||
registerDummyPlaceholder(identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -31,7 +31,7 @@ public class FabricTabPlayer extends BackendTabPlayer {
|
||||
private final EntityView entityView = new FabricEntityView(this);
|
||||
|
||||
public FabricTabPlayer(@NotNull FabricPlatform platform, @NotNull ServerPlayer player) {
|
||||
super(platform, player, player.getUUID(), player.getGameProfile().getName(), "N/A");
|
||||
super(platform, player, player.getUUID(), player.getGameProfile().getName(), FabricMultiVersion.getWorldName(player.level));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -69,7 +69,7 @@ public class FeatureManager {
|
||||
((UnLoadable) f).unload();
|
||||
TAB.getInstance().debug("Feature " + f.getClass().getSimpleName() + " processed unload in " + (System.currentTimeMillis()-time) + "ms");
|
||||
}
|
||||
TAB.getInstance().getPlaceholderManager().getTabExpansion().unregister();
|
||||
TAB.getInstance().getPlaceholderManager().getTabExpansion().unregisterExpansion();
|
||||
if (TAB.getInstance().getPlatform() instanceof ProxyPlatform) {
|
||||
for (TabPlayer player : TAB.getInstance().getOnlinePlayers()) {
|
||||
((ProxyTabPlayer)player).sendPluginMessage("Unload");
|
||||
@@ -159,7 +159,7 @@ public class FeatureManager {
|
||||
((PlayerPlaceholder)TAB.getInstance().getPlaceholderManager().getPlaceholder(TabConstants.Placeholder.WORLD)).updateValue(changed, to);
|
||||
}
|
||||
|
||||
public void onServerChange(@NotNull UUID playerUUID, @NotNull String to, boolean callOnLoginPacket) {
|
||||
public void onServerChange(@NotNull UUID playerUUID, @NotNull String to) {
|
||||
TabPlayer changed = TAB.getInstance().getPlayer(playerUUID);
|
||||
if (changed == null) return;
|
||||
String from = changed.getServer();
|
||||
@@ -172,7 +172,6 @@ public class FeatureManager {
|
||||
TAB.getInstance().getCPUManager().addTime(f, TabConstants.CpuUsageCategory.SERVER_SWITCH, System.nanoTime()-time);
|
||||
}
|
||||
((PlayerPlaceholder)TAB.getInstance().getPlaceholderManager().getPlaceholder(TabConstants.Placeholder.SERVER)).updateValue(changed, to);
|
||||
if (callOnLoginPacket) onLoginPacket(changed);
|
||||
}
|
||||
|
||||
public boolean onCommand(@Nullable TabPlayer sender, @NotNull String command) {
|
||||
@@ -272,7 +271,7 @@ public class FeatureManager {
|
||||
}
|
||||
|
||||
public void onLoginPacket(TabPlayer packetReceiver) {
|
||||
packetReceiver.getScoreboard().clearRegisteredObjectives();
|
||||
packetReceiver.getScoreboard().unfreeze();
|
||||
for (TabFeature f : values) {
|
||||
if (!(f instanceof LoginPacketListener)) continue;
|
||||
long time = System.nanoTime();
|
||||
@@ -281,6 +280,15 @@ public class FeatureManager {
|
||||
}
|
||||
}
|
||||
|
||||
public void onTabListClear(TabPlayer packetReceiver) {
|
||||
for (TabFeature f : values) {
|
||||
if (!(f instanceof TabListClearListener)) continue;
|
||||
long time = System.nanoTime();
|
||||
((TabListClearListener)f).onTabListClear(packetReceiver);
|
||||
TAB.getInstance().getCPUManager().addTime(f, TabConstants.CpuUsageCategory.TABLIST_CLEAR, System.nanoTime() - time);
|
||||
}
|
||||
}
|
||||
|
||||
public void registerFeature(@NotNull String featureName, @NotNull TabFeature featureHandler) {
|
||||
features.put(featureName, featureHandler);
|
||||
values = features.values().toArray(new TabFeature[0]);
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package me.neznamy.tab.shared;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Class containing constants representing value length
|
||||
* limits on different versions.
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class Limitations {
|
||||
|
||||
/** Player name length limit for scoreboard score on 1.7 and lower */
|
||||
public static final int SCOREBOARD_SCORE_LENGTH_1_7 = 16;
|
||||
|
||||
/** Player name length limit for scoreboard score on 1.8 and higher */
|
||||
public static final int SCOREBOARD_SCORE_LENGTH_1_8 = 40;
|
||||
|
||||
/** Max scoreboard title length for versions below 1.13 */
|
||||
public static final int SCOREBOARD_TITLE_PRE_1_13 = 32;
|
||||
|
||||
/** Max team prefix/suffix length for versions below 1.13 */
|
||||
public static final int TEAM_PREFIX_SUFFIX_PRE_1_13 = 16;
|
||||
|
||||
/** Max tablist entry length for 1.7 and lower */
|
||||
public static final int MAX_DISPLAY_NAME_LENGTH_1_7 = 16;
|
||||
}
|
||||
@@ -181,7 +181,7 @@ public class TAB extends TabAPI {
|
||||
kill();
|
||||
return (configuration == null ? "&4Failed to reload, file %file% has broken syntax. Check console for more info."
|
||||
: configuration.getMessages().getReloadFailBrokenFile()).replace("%file%", brokenFile);
|
||||
} catch (Exception e) {
|
||||
} catch (Throwable e) {
|
||||
errorManager.criticalError("Failed to enable. Did you just invent a new way to break the plugin by misconfiguring it?", e);
|
||||
kill();
|
||||
return "&cFailed to enable due to an internal plugin error. Check console for more info.";
|
||||
@@ -199,7 +199,7 @@ public class TAB extends TabAPI {
|
||||
if (configuration.getMysql() != null) configuration.getMysql().closeConnection();
|
||||
featureManager.unload();
|
||||
platform.logInfo(IChatBaseComponent.fromColoredText("&aDisabled in " + (System.currentTimeMillis()-time) + "ms"));
|
||||
} catch (Exception | NoClassDefFoundError e) {
|
||||
} catch (Throwable e) {
|
||||
errorManager.criticalError("Failed to disable", e);
|
||||
}
|
||||
kill();
|
||||
|
||||
@@ -53,6 +53,7 @@ public class TabConstants {
|
||||
public static final String PACKET_ENTITY_DESTROY = "PacketPlayOutEntityDestroy";
|
||||
public static final String RAW_PACKET_OUT = "Packet reading (out)";
|
||||
public static final String PACKET_LOGIN = "Login packet";
|
||||
public static final String TABLIST_CLEAR = "TabList entry re-add";
|
||||
|
||||
public static final String PLACEHOLDER_REFRESHING = "Refreshing placeholders";
|
||||
|
||||
|
||||
@@ -37,6 +37,21 @@ public interface BackendPlatform extends Platform {
|
||||
registry.registerPlaceholders(manager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a dummy placeholder implementation for specified identifier in case
|
||||
* no placeholder plugin was found.
|
||||
*
|
||||
* @param identifier
|
||||
* Placeholder identifier to register
|
||||
*/
|
||||
default void registerDummyPlaceholder(@NotNull String identifier) {
|
||||
if (identifier.startsWith("%rel_")) { // To prevent placeholder identifier check from throwing
|
||||
TAB.getInstance().getPlaceholderManager().registerRelationalPlaceholder(identifier, -1, (viewer, target) -> identifier);
|
||||
} else {
|
||||
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, -1, () -> identifier);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns server's TPS for {@link TabConstants.Placeholder#TPS} placeholder
|
||||
*
|
||||
|
||||
@@ -72,4 +72,14 @@ public class DummyEntityView implements EntityView {
|
||||
public int[] getDestroyedEntities(Object destroyPacket) {
|
||||
throw new UnsupportedOperationException("Not available on this platform");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBundlePacket(Object packet) {
|
||||
throw new UnsupportedOperationException("Not available on this platform");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Object> getPackets(Object bundlePacket) {
|
||||
throw new UnsupportedOperationException("Not available on this platform");
|
||||
}
|
||||
}
|
||||
@@ -33,4 +33,8 @@ public interface EntityView {
|
||||
int getSpawnedPlayer(Object playerSpawnPacket);
|
||||
|
||||
int[] getDestroyedEntities(Object destroyPacket);
|
||||
|
||||
boolean isBundlePacket(Object packet);
|
||||
|
||||
Iterable<Object> getPackets(Object bundlePacket);
|
||||
}
|
||||
+10
@@ -163,6 +163,16 @@ public abstract class BackendNameTagX extends NameTagX implements GameModeListen
|
||||
if (receiver.getVersion().getMinorVersion() < 8) return;
|
||||
if (!receiver.isLoaded() || getDisableChecker().isDisabledPlayer(receiver) || getUnlimitedDisableChecker().isDisabledPlayer(receiver)) return;
|
||||
BackendTabPlayer player = (BackendTabPlayer) receiver;
|
||||
if (player.getEntityView().isBundlePacket(packet)) {
|
||||
for (Object wrappedPacket : player.getEntityView().getPackets(packet)) {
|
||||
checkPacket(player, wrappedPacket);
|
||||
}
|
||||
} else {
|
||||
checkPacket(player, packet);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkPacket(@NotNull BackendTabPlayer player, @NotNull Object packet) {
|
||||
if (player.getEntityView().isMovePacket(packet) && !player.getEntityView().isLookPacket(packet)) { //ignoring head rotation only packets
|
||||
packetListener.onEntityMove(player, player.getEntityView().getMoveEntityId(packet));
|
||||
} else if (player.getEntityView().isTeleportPacket(packet)) {
|
||||
|
||||
@@ -43,14 +43,20 @@ public class ChatModifier {
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a String consisting of magic codes (color symbol + character) of
|
||||
* each magic code used. If none are used, empty String is returned.
|
||||
*
|
||||
* @return Magic codes of this modifier as String
|
||||
*/
|
||||
@NotNull
|
||||
public String getMagicCodes() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (isBold()) builder.append(EnumChatFormat.BOLD.getFormat());
|
||||
if (isItalic()) builder.append(EnumChatFormat.ITALIC.getFormat());
|
||||
if (isUnderlined()) builder.append(EnumChatFormat.UNDERLINE.getFormat());
|
||||
if (isStrikethrough()) builder.append(EnumChatFormat.STRIKETHROUGH.getFormat());
|
||||
if (isObfuscated()) builder.append(EnumChatFormat.OBFUSCATED.getFormat());
|
||||
if (bold) builder.append(EnumChatFormat.BOLD.getFormat());
|
||||
if (italic) builder.append(EnumChatFormat.ITALIC.getFormat());
|
||||
if (underlined) builder.append(EnumChatFormat.UNDERLINE.getFormat());
|
||||
if (strikethrough) builder.append(EnumChatFormat.STRIKETHROUGH.getFormat());
|
||||
if (obfuscated) builder.append(EnumChatFormat.OBFUSCATED.getFormat());
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -232,6 +232,8 @@ public class PlaceholderManagerImpl extends TabFeature implements PlaceholderMan
|
||||
tabExpansion.setPlaceholderValue(connectedPlayer, p.getIdentifier(), ((ServerPlaceholderImpl) p).getLastValue());
|
||||
}
|
||||
}
|
||||
// Initialize to avoid onVanishStatusChange being called in the loop after joining because previous value was null
|
||||
((PlayerPlaceholderImpl)registeredPlaceholders.get(TabConstants.Placeholder.VANISHED)).update(connectedPlayer);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-3
@@ -16,7 +16,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
* Feature handler for global PlayerList feature
|
||||
*/
|
||||
public class GlobalPlayerList extends TabFeature implements JoinListener, QuitListener, VanishListener, GameModeListener,
|
||||
Loadable, UnLoadable, ServerSwitchListener, LoginPacketListener {
|
||||
Loadable, UnLoadable, ServerSwitchListener, TabListClearListener {
|
||||
|
||||
// config options
|
||||
private final List<String> spyServers = TAB.getInstance().getConfiguration().getConfig().getStringList("global-playerlist.spy-servers", Collections.singletonList("spyserver1"));
|
||||
@@ -118,8 +118,7 @@ public class GlobalPlayerList extends TabFeature implements JoinListener, QuitLi
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoginPacket(TabPlayer player) {
|
||||
// Login packet itself does not clear this, but BungeeCord does when the packet is received
|
||||
public void onTabListClear(TabPlayer player) {
|
||||
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
|
||||
// Ignore players on the same server, since the server already sends add packet
|
||||
if (!all.getServer().equals(player.getServer()) && shouldSee(player, all)) {
|
||||
|
||||
+10
-3
@@ -9,6 +9,7 @@ import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
@@ -113,10 +114,16 @@ public abstract class NettyPipelineInjector extends PipelineInjector {
|
||||
TAB.getInstance().getCPUManager().addTime("NameTags", TabConstants.CpuUsageCategory.ANTI_OVERRIDE, System.nanoTime()-time);
|
||||
}
|
||||
if (isLogin(packet)) {
|
||||
// Logic must be processed after packet is actually sent
|
||||
player.getScoreboard().freeze();
|
||||
super.write(context, packet, channelPromise);
|
||||
TAB.getInstance().getCPUManager().runTaskLater(200, getFeatureName(), TabConstants.CpuUsageCategory.PACKET_LOGIN,
|
||||
() -> TAB.getInstance().getFeatureManager().onLoginPacket(player));
|
||||
TAB.getInstance().getCPUManager().runTaskLater(200, getFeatureName(),
|
||||
TabConstants.CpuUsageCategory.PACKET_LOGIN, () -> {
|
||||
TAB.getInstance().getFeatureManager().onLoginPacket(player);
|
||||
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_20_2.getNetworkId()) {
|
||||
// For 1.20.2+ we need to do this, because server switch event is called before tablist is cleared
|
||||
TAB.getInstance().getFeatureManager().onTabListClear(player);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
TAB.getInstance().getFeatureManager().onPacketSend(player, packet);
|
||||
|
||||
@@ -24,7 +24,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@Getter
|
||||
public class LayoutManagerImpl extends TabFeature implements LayoutManager, JoinListener, QuitListener, VanishListener, Loadable,
|
||||
UnLoadable, Refreshable, LoginPacketListener {
|
||||
UnLoadable, Refreshable, TabListClearListener {
|
||||
|
||||
/** Config options */
|
||||
private final Direction direction = parseDirection(TAB.getInstance().getConfig().getString("layout.direction", "COLUMNS"));
|
||||
@@ -199,7 +199,7 @@ public class LayoutManagerImpl extends TabFeature implements LayoutManager, Join
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoginPacket(TabPlayer player) {
|
||||
public void onTabListClear(TabPlayer player) {
|
||||
LayoutView view = views.get(player);
|
||||
if (view != null) view.send();
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.util.*;
|
||||
|
||||
public class NameTag extends TabFeature implements NameTagManager, JoinListener, QuitListener,
|
||||
Loadable, UnLoadable, WorldSwitchListener, ServerSwitchListener, Refreshable, LoginPacketListener {
|
||||
Loadable, UnLoadable, WorldSwitchListener, ServerSwitchListener, Refreshable, LoginPacketListener,
|
||||
VanishListener {
|
||||
|
||||
@Getter private final String featureName = "NameTags";
|
||||
@Getter private final String refreshDisplayName = "Updating prefix/suffix";
|
||||
@@ -77,7 +78,7 @@ public class NameTag extends TabFeature implements NameTagManager, JoinListener,
|
||||
public void unload() {
|
||||
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
|
||||
for (TabPlayer target : TAB.getInstance().getOnlinePlayers()) {
|
||||
if (hasTeamHandlingPaused(target) || disableChecker.isDisabledPlayer(target)) return;
|
||||
if (hasTeamHandlingPaused(target) || disableChecker.isDisabledPlayer(target)) continue;
|
||||
viewer.getScoreboard().unregisterTeam(sorting.getShortTeamName(target));
|
||||
}
|
||||
}
|
||||
@@ -121,6 +122,7 @@ public class NameTag extends TabFeature implements NameTagManager, JoinListener,
|
||||
if (!disableChecker.isDisabledPlayer(disconnectedPlayer) && !hasTeamHandlingPaused(disconnectedPlayer)) {
|
||||
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
|
||||
if (viewer == disconnectedPlayer) continue; //player who just disconnected
|
||||
if (disconnectedPlayer.isVanished() && !viewer.hasPermission(TabConstants.Permission.SEE_VANISHED)) continue;
|
||||
viewer.getScoreboard().unregisterTeam(sorting.getShortTeamName(disconnectedPlayer));
|
||||
}
|
||||
}
|
||||
@@ -227,6 +229,7 @@ public class NameTag extends TabFeature implements NameTagManager, JoinListener,
|
||||
}
|
||||
|
||||
public void updateTeamData(@NonNull TabPlayer p, @NonNull TabPlayer viewer) {
|
||||
if (p.isVanished() && !viewer.hasPermission(TabConstants.Permission.SEE_VANISHED)) return;
|
||||
boolean visible = getTeamVisibility(p, viewer);
|
||||
String currentPrefix = p.getProperty(TabConstants.Property.TAGPREFIX).getFormat(viewer);
|
||||
String currentSuffix = p.getProperty(TabConstants.Property.TAGSUFFIX).getFormat(viewer);
|
||||
@@ -243,6 +246,7 @@ public class NameTag extends TabFeature implements NameTagManager, JoinListener,
|
||||
public void unregisterTeam(@NonNull TabPlayer p, @NonNull String teamName) {
|
||||
if (hasTeamHandlingPaused(p)) return;
|
||||
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
|
||||
if (p.isVanished() && !viewer.hasPermission(TabConstants.Permission.SEE_VANISHED)) continue;
|
||||
viewer.getScoreboard().unregisterTeam(teamName);
|
||||
}
|
||||
}
|
||||
@@ -255,6 +259,7 @@ public class NameTag extends TabFeature implements NameTagManager, JoinListener,
|
||||
|
||||
private void registerTeam(@NonNull TabPlayer p, @NonNull TabPlayer viewer) {
|
||||
if (hasTeamHandlingPaused(p)) return;
|
||||
if (p.isVanished() && !viewer.hasPermission(TabConstants.Permission.SEE_VANISHED)) return;
|
||||
String replacedPrefix = p.getProperty(TabConstants.Property.TAGPREFIX).getFormat(viewer);
|
||||
String replacedSuffix = p.getProperty(TabConstants.Property.TAGSUFFIX).getFormat(viewer);
|
||||
viewer.getScoreboard().registerTeam(
|
||||
@@ -345,4 +350,16 @@ public class NameTag extends TabFeature implements NameTagManager, JoinListener,
|
||||
if (!disableChecker.isDisabledPlayer(all) && all.isLoaded()) registerTeam(all, player);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVanishStatusChange(@NotNull TabPlayer player) {
|
||||
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
|
||||
if (viewer.hasPermission(TabConstants.Permission.SEE_VANISHED)) continue;
|
||||
if (player.isVanished()) {
|
||||
viewer.getScoreboard().unregisterTeam(sorting.getShortTeamName(player));
|
||||
} else {
|
||||
registerTeam(player, viewer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,8 @@ import java.util.function.Supplier;
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
@Getter
|
||||
public abstract class RedisSupport extends TabFeature implements JoinListener, QuitListener,
|
||||
DisplayNameListener, Loadable, UnLoadable, ServerSwitchListener, LoginPacketListener {
|
||||
DisplayNameListener, Loadable, UnLoadable, ServerSwitchListener, LoginPacketListener,
|
||||
VanishListener, TabListClearListener {
|
||||
|
||||
@NotNull private final String featureName = "RedisSupport";
|
||||
|
||||
@@ -56,6 +57,7 @@ public abstract class RedisSupport extends TabFeature implements JoinListener, Q
|
||||
registerMessage("join", PlayerJoin.class, PlayerJoin::new);
|
||||
registerMessage("quit", PlayerQuit.class, PlayerQuit::new);
|
||||
registerMessage("server", ServerSwitch.class, ServerSwitch::new);
|
||||
registerMessage("vanish", UpdateVanishStatus.class, UpdateVanishStatus::new);
|
||||
}
|
||||
|
||||
public void updateTabFormat(@NotNull TabPlayer p, @NotNull String format) {
|
||||
@@ -212,4 +214,14 @@ public abstract class RedisSupport extends TabFeature implements JoinListener, Q
|
||||
public void onLoginPacket(TabPlayer player) {
|
||||
features.forEach(f -> f.onLoginPacket(player));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabListClear(TabPlayer player) {
|
||||
features.forEach(f -> f.onTabListClear(player));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVanishStatusChange(@NotNull TabPlayer player) {
|
||||
sendMessage(new UpdateVanishStatus(player.getUniqueId(), player.isVanished()));
|
||||
}
|
||||
}
|
||||
@@ -23,4 +23,8 @@ public abstract class RedisFeature {
|
||||
public abstract void read(@NotNull ByteArrayDataInput in, @NotNull RedisPlayer player);
|
||||
|
||||
public void onLoginPacket(@NotNull TabPlayer player) {/* Do nothing by default */}
|
||||
|
||||
public void onTabListClear(@NotNull TabPlayer player) {/* Do nothing by default */}
|
||||
|
||||
public void onVanishStatusChange(@NotNull RedisPlayer player) {/* Do nothing by default */}
|
||||
}
|
||||
+18
-1
@@ -89,7 +89,7 @@ public class RedisGlobalPlayerList extends RedisFeature {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoginPacket(@NotNull TabPlayer player) {
|
||||
public void onTabListClear(@NotNull TabPlayer player) {
|
||||
onJoin(player);
|
||||
}
|
||||
|
||||
@@ -104,4 +104,21 @@ public class RedisGlobalPlayerList extends RedisFeature {
|
||||
redisSupport.getRedisPlayerList() == null ? null :
|
||||
IChatBaseComponent.optimizedComponent(redisSupport.getRedisPlayerList().getFormat(player)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVanishStatusChange(@NotNull RedisPlayer player) {
|
||||
if (player.isVanished()) {
|
||||
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
|
||||
if (!shouldSee(all, player)) {
|
||||
all.getTabList().removeEntry(player.getUniqueId());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
|
||||
if (shouldSee(viewer, player)) {
|
||||
viewer.getTabList().addEntry(getEntry(player));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,6 +66,15 @@ public class RedisPlayerList extends RedisFeature {
|
||||
return values.get(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVanishStatusChange(@NotNull RedisPlayer player) {
|
||||
if (player.isVanished()) return;
|
||||
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
|
||||
if (viewer.getVersion().getMinorVersion() < 8) continue;
|
||||
viewer.getTabList().updateDisplayName(player.getUniqueId(), IChatBaseComponent.optimizedComponent(getFormat(player)));
|
||||
}
|
||||
}
|
||||
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Update extends RedisMessage {
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package me.neznamy.tab.shared.features.redis.message;
|
||||
|
||||
import com.google.common.io.ByteArrayDataInput;
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.NoArgsConstructor;
|
||||
import me.neznamy.tab.shared.features.redis.RedisPlayer;
|
||||
import me.neznamy.tab.shared.features.redis.RedisSupport;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class UpdateVanishStatus extends RedisMessage {
|
||||
|
||||
private UUID playerId;
|
||||
private boolean vanished;
|
||||
|
||||
@Override
|
||||
public void write(@NotNull ByteArrayDataOutput out) {
|
||||
writeUUID(out, playerId);
|
||||
out.writeBoolean(vanished);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(@NotNull ByteArrayDataInput in) {
|
||||
playerId = readUUID(in);
|
||||
vanished = in.readBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(@NotNull RedisSupport redisSupport) {
|
||||
RedisPlayer target = redisSupport.getRedisPlayers().get(playerId);
|
||||
if (target == null) return; // Print warn?
|
||||
target.setVanished(vanished);
|
||||
redisSupport.getFeatures().forEach(f -> f.onVanishStatusChange(target));
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package me.neznamy.tab.shared.features.scoreboard.lines;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.shared.Limitations;
|
||||
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
@@ -46,7 +47,11 @@ public class LongLine extends ScoreboardLine implements Refreshable {
|
||||
"", Scoreboard.NameVisibility.ALWAYS, Scoreboard.CollisionRule.ALWAYS, 0);
|
||||
} else {
|
||||
removeLine(refreshed, refreshed.getProperty(nameProperty).get());
|
||||
String[] values = splitText(getPlayerName(lineNumber), RGBUtils.getInstance().convertRGBtoLegacy(refreshed.getProperty(textProperty).get()), refreshed.getVersion().getMinorVersion() >= 8 ? 40 : 16);
|
||||
String[] values = splitText(
|
||||
getPlayerName(lineNumber),
|
||||
RGBUtils.getInstance().convertRGBtoLegacy(refreshed.getProperty(textProperty).get()),
|
||||
refreshed.getVersion().getMinorVersion() >= 8 ? Limitations.SCOREBOARD_SCORE_LENGTH_1_8 : Limitations.SCOREBOARD_SCORE_LENGTH_1_7
|
||||
);
|
||||
addLine(refreshed, values[1], values[0], values[2]);
|
||||
refreshed.setProperty(this, nameProperty, values[1]);
|
||||
}
|
||||
@@ -61,7 +66,11 @@ public class LongLine extends ScoreboardLine implements Refreshable {
|
||||
addLine(p, playerName, value, "");
|
||||
p.setProperty(this, nameProperty, playerName);
|
||||
} else {
|
||||
String[] values = splitText(playerName, RGBUtils.getInstance().convertRGBtoLegacy(value), p.getVersion().getMinorVersion() >= 8 ? 40 : 16);
|
||||
String[] values = splitText(
|
||||
playerName,
|
||||
RGBUtils.getInstance().convertRGBtoLegacy(value),
|
||||
p.getVersion().getMinorVersion() >= 8 ? Limitations.SCOREBOARD_SCORE_LENGTH_1_8 : Limitations.SCOREBOARD_SCORE_LENGTH_1_7
|
||||
);
|
||||
addLine(p, values[1], values[0], values[2]);
|
||||
p.setProperty(this, nameProperty, values[1]);
|
||||
}
|
||||
|
||||
+3
-2
@@ -2,6 +2,7 @@ package me.neznamy.tab.shared.features.scoreboard.lines;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.shared.Limitations;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.api.scoreboard.Line;
|
||||
import me.neznamy.tab.shared.features.types.TabFeature;
|
||||
@@ -164,7 +165,7 @@ public abstract class ScoreboardLine extends TabFeature implements Line {
|
||||
* @param text
|
||||
* text to display
|
||||
* @param maxNameLength
|
||||
* maximum length of name field, used values are 16 characters for <1.8 and 40 for 1.8+
|
||||
* maximum length of name field
|
||||
* @return Split text as an array of 3 elements
|
||||
*/
|
||||
protected String[] splitText(@NonNull String playerNameStart, @NonNull String text, int maxNameLength) {
|
||||
@@ -176,7 +177,7 @@ public abstract class ScoreboardLine extends TabFeature implements Line {
|
||||
nameValue = playerNameStart + text;
|
||||
suffixValue = "";
|
||||
} else {
|
||||
String[] prefixOther = split(text, 16);
|
||||
String[] prefixOther = split(text, Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13);
|
||||
prefixValue = prefixOther[0];
|
||||
String other = prefixOther[1];
|
||||
if (playerNameStart.length() > 0) {
|
||||
|
||||
+5
-4
@@ -1,6 +1,7 @@
|
||||
package me.neznamy.tab.shared.features.scoreboard.lines;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.shared.Limitations;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
|
||||
@@ -15,12 +16,12 @@ import me.neznamy.tab.shared.features.scoreboard.ScoreboardImpl;
|
||||
*/
|
||||
public class StaticLine extends ScoreboardLine {
|
||||
|
||||
//values for 1.7 clients with 16-character limit for player name
|
||||
//values for 1.7 clients
|
||||
protected String prefix17;
|
||||
protected String name17;
|
||||
protected String suffix17;
|
||||
|
||||
//values for 1.8-1.12 clients with 40-character limit for player name
|
||||
//values for 1.8-1.12 clients
|
||||
protected String prefix;
|
||||
protected String name;
|
||||
protected String suffix;
|
||||
@@ -35,12 +36,12 @@ public class StaticLine extends ScoreboardLine {
|
||||
super.text = text;
|
||||
String legacy = RGBUtils.getInstance().convertRGBtoLegacy(this.text);
|
||||
//1.8+
|
||||
String[] v18 = splitText(getPlayerName(lineNumber), legacy, 40);
|
||||
String[] v18 = splitText(getPlayerName(lineNumber), legacy, Limitations.SCOREBOARD_SCORE_LENGTH_1_8);
|
||||
prefix = v18[0];
|
||||
name = v18[1];
|
||||
suffix = v18[2];
|
||||
//1.7-
|
||||
String[] v17 = splitText(getPlayerName(lineNumber), legacy, 16);
|
||||
String[] v17 = splitText(getPlayerName(lineNumber), legacy, Limitations.SCOREBOARD_SCORE_LENGTH_1_7);
|
||||
prefix17 = v17[0];
|
||||
name17 = v17[1];
|
||||
suffix17 = v17[2];
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package me.neznamy.tab.shared.features.types;
|
||||
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
|
||||
/**
|
||||
* Interface for features listening to tablist entry clear
|
||||
* on server switch to re-add entries.
|
||||
*/
|
||||
public interface TabListClearListener {
|
||||
|
||||
/**
|
||||
* Called after player's TabList is cleared by the proxy
|
||||
* on server switch.
|
||||
*
|
||||
* @param player
|
||||
* Player who got their TabList entries cleared
|
||||
*/
|
||||
void onTabListClear(TabPlayer player);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package me.neznamy.tab.shared.hook;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.chat.ChatModifier;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.util.ComponentCache;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
@@ -15,6 +16,10 @@ import java.util.stream.Collectors;
|
||||
|
||||
public class AdventureHook {
|
||||
|
||||
/** Component cache for adventure components */
|
||||
private static final ComponentCache<IChatBaseComponent, Component> cache =
|
||||
new ComponentCache<>(1000, AdventureHook::toAdventureComponent0);
|
||||
|
||||
/**
|
||||
* Converts component to adventure component
|
||||
*
|
||||
@@ -26,8 +31,22 @@ public class AdventureHook {
|
||||
*/
|
||||
@NotNull
|
||||
public static Component toAdventureComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion clientVersion) {
|
||||
return cache.get(component, clientVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts component to adventure component
|
||||
*
|
||||
* @param component
|
||||
* Component to convert
|
||||
* @param clientVersion
|
||||
* Version to create component for
|
||||
* @return Adventure component from this component.
|
||||
*/
|
||||
@NotNull
|
||||
private static Component toAdventureComponent0(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion clientVersion) {
|
||||
ChatModifier modifier = component.getModifier();
|
||||
net.kyori.adventure.text.format.TextColor color = null;
|
||||
TextColor color = null;
|
||||
if (modifier.getColor() != null) {
|
||||
if (clientVersion.getMinorVersion() >= 16) {
|
||||
color = TextColor.color(modifier.getColor().getRgb());
|
||||
@@ -41,9 +60,14 @@ public class AdventureHook {
|
||||
if (modifier.isObfuscated()) decorations.add(TextDecoration.OBFUSCATED);
|
||||
if (modifier.isStrikethrough()) decorations.add(TextDecoration.STRIKETHROUGH);
|
||||
if (modifier.isUnderlined()) decorations.add(TextDecoration.UNDERLINED);
|
||||
Key font = modifier.getFont() == null ? null : Key.key(modifier.getFont());
|
||||
return Component.text(component.getText(), color, decorations)
|
||||
.font(font)
|
||||
.children(component.getExtra().stream().map(c -> toAdventureComponent(c, clientVersion)).collect(Collectors.toList()));
|
||||
Component adventureComponent = Component.text(component.getText(), color, decorations);
|
||||
if (modifier.getFont() != null) {
|
||||
adventureComponent = adventureComponent.font(Key.key(modifier.getFont()));
|
||||
}
|
||||
if (!component.getExtra().isEmpty()) {
|
||||
adventureComponent = adventureComponent.children(component.getExtra().stream().map(
|
||||
c -> toAdventureComponent(c, clientVersion)).collect(Collectors.toList()));
|
||||
}
|
||||
return adventureComponent;
|
||||
}
|
||||
}
|
||||
+4
-3
@@ -129,11 +129,12 @@ public class RelationalPlaceholderImpl extends TabPlaceholder implements Relatio
|
||||
* @return last known value for entered player duo
|
||||
*/
|
||||
public String getLastValue(@NonNull TabPlayer viewer, @NonNull TabPlayer target) {
|
||||
if (!lastValues.computeIfAbsent(viewer, v -> new WeakHashMap<>()).containsKey(target)) {
|
||||
lastValues.get(viewer).put(target, getReplacements().findReplacement(identifier));
|
||||
WeakHashMap<me.neznamy.tab.api.TabPlayer, String> viewerMap = lastValues.computeIfAbsent(viewer, v -> new WeakHashMap<>());
|
||||
if (!viewerMap.containsKey(target)) {
|
||||
viewerMap.put(target, getReplacements().findReplacement(identifier));
|
||||
update(viewer, target);
|
||||
}
|
||||
return setPlaceholders(EnumChatFormat.color(lastValues.computeIfAbsent(viewer, v -> new WeakHashMap<>()).get(target)), target);
|
||||
return setPlaceholders(EnumChatFormat.color(viewerMap.get(target)), target);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+1
-1
@@ -12,5 +12,5 @@ public class EmptyTabExpansion implements TabExpansion {
|
||||
public void setValue(@NonNull TabPlayer player, @NonNull String key, @NonNull String value) {/*Do nothing*/}
|
||||
|
||||
@Override
|
||||
public boolean unregister() { return false; }
|
||||
public void unregisterExpansion() {/* Do nothing */}
|
||||
}
|
||||
@@ -39,5 +39,5 @@ public interface TabExpansion {
|
||||
|
||||
void setValue(@NotNull TabPlayer player, @NotNull String key, @NotNull String value);
|
||||
|
||||
boolean unregister();
|
||||
void unregisterExpansion();
|
||||
}
|
||||
@@ -50,29 +50,6 @@ public abstract class EventListener<T> {
|
||||
TAB.getInstance().getFeatureManager().onWorldChange(player, world));
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes server change by forwarding it to all features.
|
||||
*
|
||||
* @param player
|
||||
* Player who switched server or joined
|
||||
* @param uuid
|
||||
* UUID of the player
|
||||
* @param server
|
||||
* New server
|
||||
* @param callOnLoginPacket
|
||||
* Whether onLoginPacket method should be called as well or not
|
||||
*/
|
||||
public void serverChange(@NotNull T player, @NotNull UUID uuid, @NotNull String server, boolean callOnLoginPacket) {
|
||||
if (TAB.getInstance().isPluginDisabled()) return;
|
||||
TAB.getInstance().getCPUManager().runTask(() -> {
|
||||
if (TAB.getInstance().getPlayer(uuid) == null) {
|
||||
TAB.getInstance().getFeatureManager().onJoin(createPlayer(player));
|
||||
} else {
|
||||
TAB.getInstance().getFeatureManager().onServerChange(uuid, server, callOnLoginPacket);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes plugin message.
|
||||
*
|
||||
|
||||
@@ -2,6 +2,7 @@ package me.neznamy.tab.shared.platform;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.shared.Limitations;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
|
||||
@@ -23,7 +24,11 @@ public abstract class Scoreboard<T extends TabPlayer> {
|
||||
/** Scoreboard objectives player has registered */
|
||||
private final Set<String> registeredObjectives = new HashSet<>();
|
||||
|
||||
/** Flag tracking time between Login packet send and its processing */
|
||||
private boolean frozen;
|
||||
|
||||
public void setScore(@NotNull String objective, @NotNull String playerName, int score) {
|
||||
if (frozen) return;
|
||||
if (!registeredObjectives.contains(objective)) {
|
||||
error("Tried to update score (%s) without the existence of its requested objective '%s' to player ", playerName, objective);
|
||||
return;
|
||||
@@ -32,6 +37,7 @@ public abstract class Scoreboard<T extends TabPlayer> {
|
||||
}
|
||||
|
||||
public void removeScore(@NotNull String objective, @NotNull String playerName) {
|
||||
if (frozen) return;
|
||||
if (!registeredObjectives.contains(objective)) {
|
||||
error("Tried to remove score (%s) without the existence of its requested objective '%s' to player ", playerName, objective);
|
||||
return;
|
||||
@@ -40,14 +46,16 @@ public abstract class Scoreboard<T extends TabPlayer> {
|
||||
}
|
||||
|
||||
public void registerObjective(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
|
||||
if (frozen) return;
|
||||
if (!registeredObjectives.add(objectiveName)) {
|
||||
error("Tried to register duplicated objective %s to player ", objectiveName);
|
||||
return;
|
||||
}
|
||||
registerObjective0(objectiveName, cutTo(title, 32), display);
|
||||
registerObjective0(objectiveName, cutTo(title, Limitations.SCOREBOARD_TITLE_PRE_1_13), display);
|
||||
}
|
||||
|
||||
public void unregisterObjective(@NotNull String objectiveName) {
|
||||
if (frozen) return;
|
||||
if (!registeredObjectives.remove(objectiveName)) {
|
||||
error("Tried to unregister non-existing objective %s for player ", objectiveName);
|
||||
return;
|
||||
@@ -56,23 +64,34 @@ public abstract class Scoreboard<T extends TabPlayer> {
|
||||
}
|
||||
|
||||
public void updateObjective(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
|
||||
if (frozen) return;
|
||||
if (!registeredObjectives.contains(objectiveName)) {
|
||||
error("Tried to modify non-existing objective %s for player ", objectiveName);
|
||||
return;
|
||||
}
|
||||
updateObjective0(objectiveName, cutTo(title, 32), display);
|
||||
updateObjective0(objectiveName, cutTo(title, Limitations.SCOREBOARD_TITLE_PRE_1_13), display);
|
||||
}
|
||||
|
||||
public void registerTeam(@NotNull String name, @NotNull String prefix, @NotNull String suffix, @NotNull NameVisibility visibility,
|
||||
@NotNull CollisionRule collision, @NotNull Collection<String> players, int options) {
|
||||
if (frozen) return;
|
||||
if (!registeredTeams.add(name)) {
|
||||
error("Tried to register duplicated team %s to player ", name);
|
||||
return;
|
||||
}
|
||||
registerTeam0(name, cutTo(prefix, 16), cutTo(suffix, 16), visibility, collision, players, options);
|
||||
registerTeam0(
|
||||
name,
|
||||
cutTo(prefix, Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13),
|
||||
cutTo(suffix, Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13),
|
||||
visibility,
|
||||
collision,
|
||||
players,
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
public void unregisterTeam(@NotNull String name) {
|
||||
if (frozen) return;
|
||||
if (!registeredTeams.remove(name)) {
|
||||
error("Tried to unregister non-existing team %s for player ", name);
|
||||
return;
|
||||
@@ -82,11 +101,19 @@ public abstract class Scoreboard<T extends TabPlayer> {
|
||||
|
||||
public void updateTeam(@NotNull String name, @NotNull String prefix, @NotNull String suffix, @NotNull NameVisibility visibility,
|
||||
@NotNull CollisionRule collision, int options) {
|
||||
if (frozen) return;
|
||||
if (!registeredTeams.contains(name)) {
|
||||
error("Tried to modify non-existing team %s for player ", name);
|
||||
return;
|
||||
}
|
||||
updateTeam0(name, cutTo(prefix, 16), cutTo(suffix, 16), visibility, collision, options);
|
||||
updateTeam0(
|
||||
name,
|
||||
cutTo(prefix, Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13),
|
||||
cutTo(suffix, Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13),
|
||||
visibility,
|
||||
collision,
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
private void error(@NotNull String format, @NotNull Object... args) {
|
||||
@@ -94,11 +121,19 @@ public abstract class Scoreboard<T extends TabPlayer> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears maps of registered teams and objectives on server switch, as proxy sends Login packet
|
||||
* Marks for freeze.
|
||||
*/
|
||||
public void clearRegisteredObjectives() {
|
||||
public void freeze() {
|
||||
frozen = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears frozen flag and clears maps of registered teams and objectives.
|
||||
*/
|
||||
public void unfreeze() {
|
||||
registeredTeams.clear();
|
||||
registeredObjectives.clear();
|
||||
frozen = false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -70,9 +70,13 @@ public class PluginMessageHandler {
|
||||
}
|
||||
|
||||
public void vanished(@NotNull ProxyTabPlayer player, @NotNull ByteArrayDataInput in) {
|
||||
player.setVanished(in.readBoolean());
|
||||
TAB.getInstance().getFeatureManager().onVanishStatusChange(player);
|
||||
((PlayerPlaceholderImpl) TAB.getInstance().getPlaceholderManager().getPlaceholder(TabConstants.Placeholder.VANISHED)).updateValue(player, player.isVanished());
|
||||
boolean oldVanish = player.isVanished();
|
||||
boolean newVanish = in.readBoolean();
|
||||
if (oldVanish != newVanish) {
|
||||
player.setVanished(newVanish);
|
||||
TAB.getInstance().getFeatureManager().onVanishStatusChange(player);
|
||||
((PlayerPlaceholderImpl) TAB.getInstance().getPlaceholderManager().getPlaceholder(TabConstants.Placeholder.VANISHED)).updateValue(player, player.isVanished());
|
||||
}
|
||||
}
|
||||
|
||||
public void disguised(@NotNull ProxyTabPlayer player, @NotNull ByteArrayDataInput in) {
|
||||
@@ -105,7 +109,10 @@ public class PluginMessageHandler {
|
||||
if (TAB.getInstance().getGroupManager().getPermissionPlugin().contains("Vault") &&
|
||||
!TAB.getInstance().getGroupManager().isGroupsByPermissions()) player.setGroup(in.readUTF());
|
||||
// reset attributes from previous server to default false values, new server will send separate update packets if needed
|
||||
player.setVanished(false);
|
||||
if (player.vanished) { // Only trigger if bridge says player is vanished, do not trigger on proxy vanish
|
||||
player.vanished = false;
|
||||
TAB.getInstance().getFeatureManager().onVanishStatusChange(player);
|
||||
}
|
||||
player.setDisguised(false);
|
||||
player.setInvisibilityPotion(false);
|
||||
int placeholderCount = in.readInt();
|
||||
|
||||
@@ -20,9 +20,8 @@ public class ProxyTabExpansion implements TabExpansion {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unregister() {
|
||||
public void unregisterExpansion() {
|
||||
// Don't do anything on proxy side
|
||||
return false;
|
||||
}
|
||||
|
||||
public void resendAllValues(@NotNull TabPlayer player) {
|
||||
|
||||
@@ -26,7 +26,7 @@ import java.util.UUID;
|
||||
public abstract class ProxyTabPlayer extends TabPlayer {
|
||||
|
||||
/** Player's vanish status from backend server */
|
||||
private boolean vanished;
|
||||
public boolean vanished;
|
||||
|
||||
/** Player's disguise status from backend server */
|
||||
private boolean disguised;
|
||||
|
||||
@@ -34,7 +34,7 @@ public class SpongePlatform implements BackendPlatform {
|
||||
|
||||
@Override
|
||||
public void registerUnknownPlaceholder(@NotNull String identifier) {
|
||||
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, -1, () -> identifier);
|
||||
registerDummyPlaceholder(identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -34,7 +34,7 @@ public class SpongePlatform implements BackendPlatform {
|
||||
|
||||
@Override
|
||||
public void registerUnknownPlaceholder(@NotNull String identifier) {
|
||||
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, -1, () -> identifier);
|
||||
registerDummyPlaceholder(identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+15
-5
@@ -28,11 +28,21 @@ public class VelocityEventListener extends EventListener<Player> {
|
||||
|
||||
@Subscribe
|
||||
public void onConnect(@NotNull ServerPostConnectEvent e) {
|
||||
serverChange(
|
||||
e.getPlayer(), e.getPlayer().getUniqueId(),
|
||||
e.getPlayer().getCurrentServer().map(s -> s.getServerInfo().getName()).orElse("null"),
|
||||
true
|
||||
);
|
||||
TAB tab = TAB.getInstance();
|
||||
if (tab.isPluginDisabled()) return;
|
||||
tab.getCPUManager().runTask(() -> {
|
||||
TabPlayer player = tab.getPlayer(e.getPlayer().getUniqueId());
|
||||
if (player == null) {
|
||||
tab.getFeatureManager().onJoin(createPlayer(e.getPlayer()));
|
||||
} else {
|
||||
tab.getFeatureManager().onServerChange(
|
||||
player.getUniqueId(),
|
||||
e.getPlayer().getCurrentServer().map(s -> s.getServerInfo().getName()).orElse("null")
|
||||
);
|
||||
tab.getFeatureManager().onTabListClear(player);
|
||||
tab.getFeatureManager().onLoginPacket(player);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
|
||||
@@ -62,6 +62,7 @@ public class VelocityTAB {
|
||||
*/
|
||||
@Subscribe
|
||||
public void onProxyShutdown(@Nullable ProxyShutdownEvent event) {
|
||||
TAB.getInstance().unload();
|
||||
// Fix race condition as Velocity calls DisconnectEvent for each player before this event
|
||||
TAB.getInstance().getCPUManager().runTask(() -> TAB.getInstance().unload());
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,6 @@ public class VelocityTabList implements TabList {
|
||||
|
||||
@Override
|
||||
public void addEntry(@NotNull Entry entry) {
|
||||
if (player.getPlayer().getTabList().containsEntry(entry.getUniqueId())) return;
|
||||
Component displayName = entry.getDisplayName() == null ? null : AdventureHook.toAdventureComponent(entry.getDisplayName(), player.getVersion());
|
||||
TabListEntry e = TabListEntry.builder()
|
||||
.tabList(player.getPlayer().getTabList())
|
||||
@@ -77,8 +76,17 @@ public class VelocityTabList implements TabList {
|
||||
.gameMode(entry.getGameMode())
|
||||
.displayName(displayName)
|
||||
.build();
|
||||
player.getPlayer().getTabList().addEntry(e);
|
||||
expectedDisplayNames.put(e, displayName);
|
||||
|
||||
// Remove entry because:
|
||||
// #1 - If player is 1.8 - 1.19.2, KeyedVelocityTabList#addEntry will throw IllegalArgumentException
|
||||
// if the entry is already present (most likely due to an accident trying to add existing player in global playerlist)
|
||||
// #2 - If player is 1.20.2+, tablist is cleared by the client itself without requirement to remove
|
||||
// manually by the proxy, however velocity's tablist entry tracker still thinks they are present
|
||||
// and therefore will refuse to add them
|
||||
removeEntry(entry.getUniqueId());
|
||||
|
||||
player.getPlayer().getTabList().addEntry(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in new issue
Block a user