mirror of
https://github.com/NEZNAMY/TAB.git
synced 2026-11-12 20:22:01 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd4704f75e | ||
|
|
5400ca637f | ||
|
|
79d2610117 | ||
|
|
6a51f4cb9d | ||
|
|
c190776665 | ||
|
|
3765a1232e | ||
|
|
28866eba21 | ||
|
|
08adee3dc3 | ||
|
|
8a1adaf2cd | ||
|
|
fdb9accaa6 | ||
|
|
4f7656748b | ||
|
|
03e4495ac7 | ||
|
|
01af5cb9e1 | ||
|
|
8d49f32df2 | ||
|
|
8a955b3bc8 | ||
|
|
ead40cb413 | ||
|
|
5e07f78d4f | ||
|
|
c7d98795bc | ||
|
|
9f637e3d6a | ||
|
|
2d5f074c78 | ||
|
|
3be6ba484d | ||
|
|
d9d74e7a32 | ||
|
|
887306a5ed | ||
|
|
4b2542eb4f | ||
|
|
66bd3baa3e | ||
|
|
bf32004f8f | ||
|
|
bfb2df9c95 | ||
|
|
ddeb05e408 | ||
|
|
9312d61258 | ||
|
|
d44909bc42 | ||
|
|
1d77df335c | ||
|
|
a268dc25f4 | ||
|
|
8c5696faa0 | ||
|
|
94f3ccfb7d | ||
|
|
576329214d | ||
|
|
5c13c2e225 | ||
|
|
1070f85617 | ||
|
|
7c9dc87fcb | ||
|
|
363e58bf91 | ||
|
|
daca3f5b71 | ||
|
|
a7d2ec084b | ||
|
|
167f8cd0ff | ||
|
|
170307f45f | ||
|
|
2f36377780 | ||
|
|
dc09e77190 | ||
|
|
d5b0ff7f40 | ||
|
|
e4f33b58f7 |
No files matched your search
@@ -1,5 +1,9 @@
|
||||
package me.neznamy.tab.api;
|
||||
|
||||
/**
|
||||
* Interface representing an armor stand attached to player
|
||||
* for displaying text in game.
|
||||
*/
|
||||
public interface ArmorStand {
|
||||
|
||||
/**
|
||||
@@ -92,6 +96,13 @@ public interface ArmorStand {
|
||||
* player to spawn for
|
||||
*/
|
||||
void spawn(TabPlayer viewer);
|
||||
|
||||
|
||||
/**
|
||||
* Sends respawn packets to specified player to make position
|
||||
* change instant instead of teleport animation
|
||||
*
|
||||
* @param viewer
|
||||
* Player to send armor stand respawn packets to
|
||||
*/
|
||||
void respawn(TabPlayer viewer);
|
||||
}
|
||||
@@ -10,5 +10,11 @@ public interface ArmorStandManager {
|
||||
*/
|
||||
void destroy();
|
||||
|
||||
/**
|
||||
* Refreshes text of all armor stands for all nearby players
|
||||
*
|
||||
* @param force
|
||||
* If refresh should be force despite no update seemingly being needed
|
||||
*/
|
||||
void refresh(boolean force);
|
||||
}
|
||||
@@ -91,7 +91,26 @@ public interface FeatureManager {
|
||||
*/
|
||||
boolean onCommand(TabPlayer sender, String command);
|
||||
|
||||
/**
|
||||
* Internal function that marks objective packet deserialization
|
||||
* when at least 1 feature uses it.
|
||||
* <p>
|
||||
* Internal use only.
|
||||
*/
|
||||
void markObjective();
|
||||
|
||||
/**
|
||||
* Internal function that marks display objective packet deserialization
|
||||
* when at least 1 feature uses it.
|
||||
* <p>
|
||||
* Internal use only.
|
||||
*/
|
||||
void markDisplayObjective();
|
||||
|
||||
/**
|
||||
* Returns array of all currently enabled features
|
||||
*
|
||||
* @return array of all currently enabled features
|
||||
*/
|
||||
TabFeature[] getValues();
|
||||
}
|
||||
@@ -1,16 +1,82 @@
|
||||
package me.neznamy.tab.api;
|
||||
|
||||
/**
|
||||
* Interface allowing to send header/footer to players.
|
||||
* <p>
|
||||
* Instance can be obtained using {@link TabAPI#getHeaderFooterManager()}.
|
||||
* This requires the Header/footer feature to be enabled in config, otherwise
|
||||
* the method will return {@code null}.
|
||||
*/
|
||||
public interface HeaderFooterManager {
|
||||
|
||||
/**
|
||||
* Sets raw value of header to specified value. This overrides plugin
|
||||
* configuration until reset is called. Has full placeholder support
|
||||
* and will send update packet whenever a placeholder used inside
|
||||
* changes its value.
|
||||
*
|
||||
* @param player
|
||||
* Player to set header for
|
||||
* @param header
|
||||
* Raw header value
|
||||
*/
|
||||
void setHeader(TabPlayer player, String header);
|
||||
|
||||
/**
|
||||
* Sets raw value of footer to specified value. This overrides plugin
|
||||
* configuration until reset is called. Has full placeholder support
|
||||
* and will send update packet whenever a placeholder used inside
|
||||
* changes its value.
|
||||
*
|
||||
* @param player
|
||||
* Player to set header for
|
||||
* @param footer
|
||||
* Raw footer value
|
||||
*/
|
||||
void setFooter(TabPlayer player, String footer);
|
||||
|
||||
/**
|
||||
* Sets raw value of header and footer to specified values. This overrides plugin
|
||||
* configuration until reset is called. Has full placeholder support
|
||||
* and will send update packet whenever a placeholder used inside
|
||||
* changes its value.
|
||||
*
|
||||
* @param player
|
||||
* Player to set header for
|
||||
* @param header
|
||||
* Raw header value
|
||||
* @param footer
|
||||
* Raw footer value
|
||||
*/
|
||||
void setHeaderAndFooter(TabPlayer player, String header, String footer);
|
||||
|
||||
/**
|
||||
* Resets header value previously set using {@link #setHeader(TabPlayer, String)}
|
||||
* or {@link #setHeaderAndFooter(TabPlayer, String, String)} and restores internal
|
||||
* logic using configuration files.
|
||||
*
|
||||
* @param player
|
||||
* Player to reset header for
|
||||
*/
|
||||
void resetHeader(TabPlayer player);
|
||||
|
||||
/**
|
||||
* Resets footer value previously set using {@link #setFooter(TabPlayer, String)}
|
||||
* or {@link #setHeaderAndFooter(TabPlayer, String, String)} and restores internal
|
||||
* logic using configuration files.
|
||||
*
|
||||
* @param player
|
||||
* Player to reset header for
|
||||
*/
|
||||
void resetFooter(TabPlayer player);
|
||||
|
||||
/**
|
||||
* Resets values previously set using {@link #setHeader(TabPlayer, String)},
|
||||
* {@link #setFooter(TabPlayer, String)} or {@link #setHeaderAndFooter(TabPlayer, String, String)}
|
||||
* and restores internal logic using configuration files.
|
||||
*
|
||||
* @param player
|
||||
* Player to reset header and footer for
|
||||
*/
|
||||
void resetHeaderAndFooter(TabPlayer player);
|
||||
}
|
||||
@@ -8,6 +8,7 @@ public enum ProtocolVersion {
|
||||
UNKNOWN_SERVER_VERSION ("Unknown"),
|
||||
UNKNOWN_CLIENT_VERSION ("Unknown"),
|
||||
PROXY ("Proxy"),
|
||||
V1_19_2 (760),
|
||||
V1_19_1 (760),
|
||||
V1_19 (759),
|
||||
V1_18_2 (758),
|
||||
|
||||
@@ -18,6 +18,13 @@ public abstract class TabAPI {
|
||||
/** Instance of the API */
|
||||
private static TabAPI instance;
|
||||
|
||||
/**
|
||||
* Constructs new instance and sets it as new API instance
|
||||
*/
|
||||
public TabAPI() {
|
||||
instance = this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns API instance. If instance was not set by the plugin, throws
|
||||
* {@code IllegalStateException}. This is usually caused by shading the API
|
||||
@@ -34,16 +41,6 @@ public abstract class TabAPI {
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instance setter for internal use by the plugin only.
|
||||
*
|
||||
* @param instance
|
||||
* API instance
|
||||
*/
|
||||
public static void setInstance(TabAPI instance) {
|
||||
TabAPI.instance = instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns player object from given UUID
|
||||
*
|
||||
|
||||
@@ -116,7 +116,11 @@ public class TabConstants {
|
||||
public static final String COMMAND_MYSQL_DOWNLOAD = "tab.mysql.download";
|
||||
public static final String COMMAND_MYSQL_UPLOAD = "tab.mysql.upload";
|
||||
public static final String COMMAND_NAMETAG_TOGGLE = "tab.nametag.toggle";
|
||||
|
||||
public static final String COMMAND_NAMETAG_TOGGLE_OTHER = "tab.nametag.toggle.other";
|
||||
public static final String COMMAND_NAMETAG_PREVIEW = "tab.nametag.preview";
|
||||
|
||||
public static final String COMMAND_NAMETAG_PREVIEW_OTHER = "tab.nametag.preview.other";
|
||||
public static final String COMMAND_PARSE = "tab.parse";
|
||||
public static final String COMMAND_SCOREBOARD_ANNOUNCE = "tab.announce.scoreboard";
|
||||
public static final String COMMAND_SCOREBOARD_TOGGLE = "tab.scoreboard.toggle";
|
||||
|
||||
@@ -310,4 +310,12 @@ public interface TabPlayer {
|
||||
* @return player's game profile name as seen by other players
|
||||
*/
|
||||
String getNickname();
|
||||
|
||||
/**
|
||||
* Returns player's chat message signing key. Returned object is a direct
|
||||
* object from the server, which is different per platform.
|
||||
*
|
||||
* @return Player's direct chat message signing key
|
||||
*/
|
||||
Object getProfilePublicKey();
|
||||
}
|
||||
@@ -2,21 +2,23 @@ package me.neznamy.tab.api.chat;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
|
||||
public class ChatComponentEntity extends IChatBaseComponent {
|
||||
|
||||
private final String type;
|
||||
private final UUID id;
|
||||
private final String name;
|
||||
|
||||
private final String CONTENTS;
|
||||
private final String VALUE_1_13;
|
||||
private final String VALUE_1_12;
|
||||
private final String VALUE_1_8;
|
||||
|
||||
public ChatComponentEntity(String type, UUID id, String name) {
|
||||
Preconditions.checkNotNull(type, "type");
|
||||
Preconditions.checkNotNull(id, "id");
|
||||
this.type = type;
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.CONTENTS = String.format("{\"type\":\"%s\",\"id\":\"%s\",\"name\":{\"text\":\"%s\"}}", type, id, name);
|
||||
this.VALUE_1_13 = String.format("{type:\"%s\",id:\"%s\",name:\"{\\\"text\\\":\\\"%s\\\"}\"}", type, id, name);
|
||||
this.VALUE_1_12 = String.format("{type:\"%s\",id:\"%s\",name:\"%s\"}", type, id, name);
|
||||
this.VALUE_1_8 = String.format("{type:%s,id:%s,name:%s}", type, id, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -26,16 +28,10 @@ public class ChatComponentEntity extends IChatBaseComponent {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("{\"type\":\"%s\",\"id\":\"%s\",\"name\":{\"text\":\"%s\"}}", type, id, name);
|
||||
}
|
||||
|
||||
public JsonObject toJson() {
|
||||
JsonObject json = new JsonObject();
|
||||
json.addProperty("type", type);
|
||||
json.addProperty("id", id.toString());
|
||||
JsonObject json2 = new JsonObject();
|
||||
json2.addProperty("text", name);
|
||||
json.add("name", json2);
|
||||
return json;
|
||||
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 16) return CONTENTS;
|
||||
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 13) return VALUE_1_13;
|
||||
if (TabAPI.getInstance().getServerVersion().getMinorVersion() == 12) return VALUE_1_12;
|
||||
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 8) return VALUE_1_8;
|
||||
throw new IllegalStateException("show_entity hover action is not supported on <1.8");
|
||||
}
|
||||
}
|
||||
@@ -53,8 +53,18 @@ public class ChatHoverable {
|
||||
*/
|
||||
public enum EnumHoverAction {
|
||||
|
||||
SHOW_TEXT,
|
||||
SHOW_ITEM,
|
||||
SHOW_ENTITY
|
||||
SHOW_TEXT("contents"),
|
||||
SHOW_ITEM("value"),
|
||||
SHOW_ENTITY("contents");
|
||||
|
||||
private final String preferredKey;
|
||||
|
||||
EnumHoverAction(String preferredKey) {
|
||||
this.preferredKey = preferredKey;
|
||||
}
|
||||
|
||||
public String getPreferredKey() {
|
||||
return preferredKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -249,17 +249,7 @@ public class ChatModifier {
|
||||
public void onHoverShowEntity(String type, UUID id, String name) {
|
||||
Preconditions.checkNotNull(type, "type");
|
||||
Preconditions.checkNotNull(id, "id");
|
||||
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 16) {
|
||||
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_ENTITY, new ChatComponentEntity(type, id, name));
|
||||
} else if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 13) {
|
||||
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_ENTITY, new IChatBaseComponent(String.format("{type:\"%s\",id:\"%s\",name:\"{\\\"text\\\":\\\"%s\\\"}\"}", type, id, name)));
|
||||
} else if (TabAPI.getInstance().getServerVersion().getMinorVersion() == 12) {
|
||||
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_ENTITY, new IChatBaseComponent(String.format("{type:\"%s\",id:\"%s\",name:\"%s\"}", type, id, name)));
|
||||
} else if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 8) {
|
||||
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_ENTITY, new IChatBaseComponent(String.format("{type:%s,id:%s,name:%s}", type, id, name)));
|
||||
} else {
|
||||
throw new IllegalStateException("show_entity hover action is not supported on <1.8");
|
||||
}
|
||||
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_ENTITY, new ChatComponentEntity(type, id, name));
|
||||
}
|
||||
|
||||
public void onHover(EnumHoverAction action, IChatBaseComponent value) {
|
||||
@@ -295,7 +285,7 @@ public class ChatModifier {
|
||||
JSONObject hover = new JSONObject();
|
||||
hover.put("action", hoverEvent.getAction().toString().toLowerCase());
|
||||
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 16) {
|
||||
hover.put("contents", hoverEvent.getValue());
|
||||
hover.put(hoverEvent.getAction().getPreferredKey(), hoverEvent.getValue());
|
||||
} else {
|
||||
hover.put("value", TabAPI.getInstance().getServerVersion().getMinorVersion() >= 9 ?
|
||||
hoverEvent.getValue() : hoverEvent.getValue().toRawText());
|
||||
|
||||
@@ -4,7 +4,7 @@ import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Formatter for #<RRGGBB>
|
||||
* Formatter for #<RRGGBB>
|
||||
*/
|
||||
public class HtmlFormat implements RGBFormatter {
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Formatter for <#RRGGBB>
|
||||
* Formatter for <#RRGGBB>
|
||||
*/
|
||||
public class KyoriFormat implements RGBFormatter {
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
|
||||
/**
|
||||
* Formatter for <#RRGGBB>
|
||||
* Formatter for <#RRGGBB>
|
||||
*/
|
||||
public class MiniMessageFormat implements RGBFormatter {
|
||||
|
||||
@@ -15,7 +15,7 @@ public class MiniMessageFormat implements RGBFormatter {
|
||||
String format = text.replace(EnumChatFormat.RESET.getFormat(), ""); //remove &r from header/footer newline inserts
|
||||
try {
|
||||
return LegacyComponentSerializer.legacySection().serialize(MiniMessage.miniMessage().deserialize(format));
|
||||
} catch (Exception ignored) {
|
||||
} catch (Throwable ignored) {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import me.neznamy.tab.api.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.api.chat.TextColor;
|
||||
|
||||
/**
|
||||
* Gradient applier for <gradient:#RRGGBB:#RRGGBB>Text</gradient>
|
||||
* and <gradient:#RRGGBB|L:#RRGGBB>Text</gradient>
|
||||
* Gradient applier for <gradient:#RRGGBB:#RRGGBB>Text</gradient>
|
||||
* and <gradient:#RRGGBB|L:#RRGGBB>Text</gradient>
|
||||
*/
|
||||
public class KyoriGradient implements GradientPattern {
|
||||
|
||||
|
||||
@@ -202,7 +202,7 @@ public class PacketBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* If {@code clientVersion} is >= 1.13, creates a component from given text and returns
|
||||
* If {@code clientVersion} is >= 1.13, creates a component from given text and returns
|
||||
* it as a serialized component, which BungeeCord uses.
|
||||
* <p>
|
||||
* If {@code clientVersion} is < 1.12, the text is cut to {@code length} characters if
|
||||
|
||||
@@ -97,6 +97,9 @@ public class PacketPlayOutPlayerInfo implements TabPacket {
|
||||
/** Player's skin, null for empty skin */
|
||||
private Skin skin;
|
||||
|
||||
/** Player's chat signing key */
|
||||
private Object profilePublicKey;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters. Suitable for
|
||||
* {@link EnumPlayerInfoAction}.ADD_PLAYER action
|
||||
@@ -113,8 +116,10 @@ public class PacketPlayOutPlayerInfo implements TabPacket {
|
||||
* Player's GameMode
|
||||
* @param displayName
|
||||
* Player's display name
|
||||
* @param profilePublicKey
|
||||
* Player's chat signing key
|
||||
*/
|
||||
public PlayerInfoData(String name, UUID uniqueId, Skin skin, int latency, EnumGamemode gameMode, IChatBaseComponent displayName) {
|
||||
public PlayerInfoData(String name, UUID uniqueId, Skin skin, int latency, EnumGamemode gameMode, IChatBaseComponent displayName, Object profilePublicKey) {
|
||||
Preconditions.checkNotNull(uniqueId, "uuid");
|
||||
this.name = name;
|
||||
this.uniqueId = uniqueId;
|
||||
@@ -122,6 +127,7 @@ public class PacketPlayOutPlayerInfo implements TabPacket {
|
||||
this.latency = latency;
|
||||
this.gameMode = gameMode;
|
||||
this.displayName = displayName;
|
||||
this.profilePublicKey = profilePublicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -182,8 +188,8 @@ public class PacketPlayOutPlayerInfo implements TabPacket {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("PlayerInfoData{latency=%s,gameMode=%s,displayName=%s,name=%s,uniqueId=%s,skin=%s}",
|
||||
latency, gameMode, displayName, name, uniqueId, skin);
|
||||
return String.format("PlayerInfoData{latency=%s,gameMode=%s,displayName=%s,name=%s,uniqueId=%s,skin=%s,profilePublicKey=%s}",
|
||||
latency, gameMode, displayName, name, uniqueId, skin, profilePublicKey);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -299,6 +305,25 @@ public class PacketPlayOutPlayerInfo implements TabPacket {
|
||||
public void setSkin(Skin skin) {
|
||||
this.skin = skin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #profilePublicKey}
|
||||
*
|
||||
* @return profilePublicKey
|
||||
*/
|
||||
public Object getProfilePublicKey() {
|
||||
return profilePublicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets {@link #profilePublicKey} to specified value
|
||||
*
|
||||
* @param profilePublicKey
|
||||
* profilePublicKey to use
|
||||
*/
|
||||
public void setProfilePublicKey(Object profilePublicKey) {
|
||||
this.profilePublicKey = profilePublicKey;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,10 +20,10 @@ public class PacketPlayOutScoreboardTeam implements TabPacket {
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/** Team prefix, limited to 16 characters, on <1.13 */
|
||||
/** Team prefix, limited to 16 characters, on <1.13 */
|
||||
private String playerPrefix;
|
||||
|
||||
/** Team suffix, limited to 16 characters, on <1.13 */
|
||||
/** Team suffix, limited to 16 characters, on <1.13 */
|
||||
private String playerSuffix;
|
||||
|
||||
/**
|
||||
@@ -46,7 +46,7 @@ public class PacketPlayOutScoreboardTeam implements TabPacket {
|
||||
|
||||
/**
|
||||
* Affected entities. For players, it is their name, for other entities their UUID
|
||||
* Player name length is limited to 16 characters for <1.8, 40 characters since 1.8.
|
||||
* Player name length is limited to 16 characters for <1.8, 40 characters since 1.8.
|
||||
* Entity UUID option was added in 1.8.
|
||||
*/
|
||||
private Collection<String> players = Collections.emptyList();
|
||||
|
||||
@@ -74,7 +74,7 @@ public interface Scoreboard {
|
||||
* @param index
|
||||
* Index of line to remove, starting at 0
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if the index is out of range (index < 0 || index >= {@link #getLines()}.size())
|
||||
* if the index is out of range (index < 0 || index >= {@link #getLines()}.size())
|
||||
* @see #getLines()
|
||||
* @see #addLine(String)
|
||||
*/
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
package me.neznamy.tab.api.task;
|
||||
|
||||
public interface RepeatingTask {
|
||||
|
||||
int getInterval();
|
||||
|
||||
void setInterval(int paramInt);
|
||||
|
||||
void cancel();
|
||||
}
|
||||
@@ -4,6 +4,10 @@ import java.util.concurrent.Future;
|
||||
|
||||
import me.neznamy.tab.api.TabFeature;
|
||||
|
||||
/**
|
||||
* Manager for scheduling and executing tasks in TAB's
|
||||
* main thread to avoid concurrency issues.
|
||||
*/
|
||||
public interface ThreadManager {
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,7 +2,7 @@ package me.neznamy.tab.api.util;
|
||||
|
||||
/**
|
||||
* An interface for BiFunction which can throw an exception.
|
||||
* Used in {@link me.neznamy.tab.api.protocol.PacketBuilder} to
|
||||
* Used in {@link me.neznamy.tab.api.protocol.PacketBuilder} to
|
||||
* call build method for packet classes, which may throw an
|
||||
* exception due to reflection.
|
||||
* @param <A>
|
||||
@@ -14,5 +14,16 @@ package me.neznamy.tab.api.util;
|
||||
*/
|
||||
public interface BiFunctionWithException<A, B, C> {
|
||||
|
||||
/**
|
||||
* Runs the function and returns the result
|
||||
*
|
||||
* @param a
|
||||
* First argument
|
||||
* @param b
|
||||
* Second argument
|
||||
* @return Output from function
|
||||
* @throws ReflectiveOperationException
|
||||
* If reflective operation fails
|
||||
*/
|
||||
C apply(A a, B b) throws ReflectiveOperationException;
|
||||
}
|
||||
@@ -2,24 +2,76 @@ package me.neznamy.tab.api.util;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
/**
|
||||
* Class with utility methods used in the plugin
|
||||
*/
|
||||
public class Preconditions {
|
||||
|
||||
private Preconditions(){}
|
||||
/**
|
||||
* Private constructor preventing the class from being instantiated
|
||||
*/
|
||||
private Preconditions() {}
|
||||
|
||||
public static void checkNotNull(Object obj, String name){
|
||||
/**
|
||||
* Checks object and throws {@code IllegalArgumentException} if it is {@code null}.
|
||||
*
|
||||
* @param obj
|
||||
* Object to check
|
||||
* @param name
|
||||
* Name of the variable to use in exception message
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code obj} is {@code null}
|
||||
*/
|
||||
public static void checkNotNull(Object obj, String name) {
|
||||
if (obj == null) throw new IllegalArgumentException(name + " cannot be null");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks length of String and throws {@code IllegalArgumentException} if it is
|
||||
* longer than limit.
|
||||
*
|
||||
* @param string
|
||||
* String to check
|
||||
* @param maxLength
|
||||
* Maximum length of the string
|
||||
* @param name
|
||||
* Name of the variable to use in exception message
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code string} is {@code null} or longer than {@code maxLength} characters
|
||||
*/
|
||||
public static void checkMaxLength(String string, int maxLength, String name) {
|
||||
checkNotNull(string, name);
|
||||
if (string.length() > maxLength) throw new IllegalArgumentException(name + " is longer than " + maxLength + " characters (" + string.length() + ")");
|
||||
}
|
||||
|
||||
public static void checkRange(Number number, Number min, Number max, String variable){
|
||||
/**
|
||||
* Checks number range and throws {@code IllegalArgumentException} if number
|
||||
* is out of range.
|
||||
*
|
||||
* @param number
|
||||
* Number to check
|
||||
* @param min
|
||||
* Minimum allowed value
|
||||
* @param max
|
||||
* Maximum allowed value
|
||||
* @param variable
|
||||
* Variable name to use in exception message
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code number} is out of range ({@code number < min || number > max})
|
||||
*/
|
||||
public static void checkRange(Number number, Number min, Number max, String variable) {
|
||||
if (number.doubleValue() < min.doubleValue() || number.doubleValue() > max.doubleValue())
|
||||
throw new IllegalArgumentException(variable + " index out of range (" + min + " - " + max + ")");
|
||||
throw new IllegalArgumentException(variable + " index out of range (" + min + " - " + max + "), was " + number);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if player is loaded and throws {@code IllegalStateException} if not.
|
||||
*
|
||||
* @param player
|
||||
* Player to check
|
||||
* @throws IllegalStateException
|
||||
* If player is not loaded
|
||||
*/
|
||||
public static void checkLoaded(TabPlayer player) {
|
||||
if (!player.isLoaded()) throw new IllegalStateException("Player is not loaded yet. Try again later.");
|
||||
}
|
||||
|
||||
@@ -2,11 +2,16 @@ plugins {
|
||||
`kotlin-dsl`
|
||||
}
|
||||
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
mavenCentral()
|
||||
dependencies {
|
||||
implementation(libs.plugin.indra)
|
||||
implementation(libs.plugin.shadow)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("gradle.plugin.com.github.johnrengelman", "shadow", "7.1.0")
|
||||
compileOnly(files(libs::class.java.protectionDomain.codeSource.location))
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
rootProject.name = "build-logic"
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
mavenCentral()
|
||||
}
|
||||
// You're a great IDE IntelliJ, but sometimes you really annoy me. >:(
|
||||
// repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
|
||||
versionCatalogs {
|
||||
register("libs") {
|
||||
from(files("../gradle/libs.versions.toml"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,16 @@
|
||||
import org.apache.tools.ant.filters.ReplaceTokens
|
||||
|
||||
plugins {
|
||||
`java-library`
|
||||
id("net.kyori.indra")
|
||||
}
|
||||
|
||||
indra {
|
||||
javaVersions {
|
||||
target(8)
|
||||
}
|
||||
github("NEZNAMY", "TAB") {
|
||||
ci(true)
|
||||
}
|
||||
}
|
||||
|
||||
tasks {
|
||||
@@ -18,15 +27,4 @@ tasks {
|
||||
options.encoding = Charsets.UTF_8.name()
|
||||
(options as StandardJavadocDocletOptions).addStringOption("Xdoclint:none", "-quiet")
|
||||
}
|
||||
compileJava {
|
||||
options.encoding = Charsets.UTF_8.name()
|
||||
options.release.set(8)
|
||||
options.compilerArgs.addAll(listOf("-nowarn", "-Xlint:-unchecked", "-Xlint:-deprecation"))
|
||||
}
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion.set(JavaLanguageVersion.of(17))
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,9 @@
|
||||
import gradle.kotlin.dsl.accessors._4cbb0020bd75d48acc42cabded5fdbb7.publishing
|
||||
import org.gradle.api.artifacts.repositories.PasswordCredentials
|
||||
import org.gradle.api.publish.maven.MavenPublication
|
||||
import org.gradle.kotlin.dsl.get
|
||||
import org.gradle.kotlin.dsl.repositories
|
||||
|
||||
plugins {
|
||||
id("tab.base-conventions")
|
||||
`maven-publish`
|
||||
id("net.kyori.indra.publishing")
|
||||
}
|
||||
|
||||
java {
|
||||
withSourcesJar()
|
||||
withJavadocJar()
|
||||
}
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
maven {
|
||||
name = "krypton"
|
||||
url = uri("https://repo.kryptonmc.org/releases")
|
||||
credentials(PasswordCredentials::class)
|
||||
}
|
||||
}
|
||||
publications.create<MavenPublication>("mavenJava") {
|
||||
groupId = rootProject.group as String
|
||||
artifactId = "tab-${project.name}"
|
||||
version = rootProject.version as String
|
||||
from(components["java"])
|
||||
}
|
||||
indra {
|
||||
publishReleasesTo("krypton-repo", "https://repo.kryptonmc.org/releases")
|
||||
publishSnapshotsTo("krypton-repo", "https://repo.kryptonmc.org/snapshots")
|
||||
}
|
||||
@@ -48,7 +48,8 @@ public class BukkitEventListener implements Listener {
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onJoin(PlayerJoinEvent e) {
|
||||
if (TabAPI.getInstance().isPluginDisabled()) return;
|
||||
TabAPI.getInstance().getThreadManager().runTask(() -> TabAPI.getInstance().getFeatureManager().onJoin(new BukkitTabPlayer(e.getPlayer(), platform.getProtocolVersion(e.getPlayer()))));
|
||||
TabAPI.getInstance().getThreadManager().runTask(() ->
|
||||
TabAPI.getInstance().getFeatureManager().onJoin(new BukkitTabPlayer(e.getPlayer(), platform.getProtocolVersion(e.getPlayer()))));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,7 +61,8 @@ public class BukkitEventListener implements Listener {
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onWorldChange(PlayerChangedWorldEvent e){
|
||||
if (TabAPI.getInstance().isPluginDisabled()) return;
|
||||
TabAPI.getInstance().getThreadManager().runTask(() -> TabAPI.getInstance().getFeatureManager().onWorldChange(e.getPlayer().getUniqueId(), e.getPlayer().getWorld().getName()));
|
||||
TabAPI.getInstance().getThreadManager().runTask(() ->
|
||||
TabAPI.getInstance().getFeatureManager().onWorldChange(e.getPlayer().getUniqueId(), e.getPlayer().getWorld().getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,6 +74,7 @@ public class BukkitEventListener implements Listener {
|
||||
@EventHandler
|
||||
public void onCommand(PlayerCommandPreprocessEvent e) {
|
||||
if (TabAPI.getInstance().isPluginDisabled()) return;
|
||||
if (TabAPI.getInstance().getFeatureManager().onCommand(TabAPI.getInstance().getPlayer(e.getPlayer().getUniqueId()), e.getMessage())) e.setCancelled(true);
|
||||
if (TabAPI.getInstance().getFeatureManager().onCommand(
|
||||
TabAPI.getInstance().getPlayer(e.getPlayer().getUniqueId()), e.getMessage())) e.setCancelled(true);
|
||||
}
|
||||
}
|
||||
@@ -102,7 +102,7 @@ public class BukkitPacketBuilder extends PacketBuilder {
|
||||
parameters.add(data.getLatency());
|
||||
parameters.add(data.getGameMode() == null ? null : nms.EnumGamemode_values[nms.EnumGamemode_values.length-EnumGamemode.VALUES.length+data.getGameMode().ordinal()]); //not_set was removed in 1.17
|
||||
parameters.add(data.getDisplayName() == null ? null : toNMSComponent(data.getDisplayName(), clientVersion));
|
||||
if (nms.getMinorVersion() >= 19) parameters.add(null);
|
||||
if (nms.getMinorVersion() >= 19) parameters.add(data.getProfilePublicKey());
|
||||
items.add(nms.newPlayerInfoData.newInstance(parameters.toArray()));
|
||||
}
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutPlayerInfo_PLAYERS, items);
|
||||
@@ -364,8 +364,9 @@ public class BukkitPacketBuilder extends PacketBuilder {
|
||||
Property pr = profile.getProperties().get("textures").iterator().next();
|
||||
skin = new Skin(pr.getValue(), pr.getSignature());
|
||||
}
|
||||
Object profilePublicKey = nms.getMinorVersion() >= 19 ? nms.PlayerInfoData_getProfilePublicKeyRecord.invoke(nmsData) : null;
|
||||
listData.add(new PlayerInfoData(profile.getName(), profile.getId(), skin,
|
||||
(int) nms.PlayerInfoData_getLatency.invoke(nmsData), gameMode, listName));
|
||||
(int) nms.PlayerInfoData_getLatency.invoke(nmsData), gameMode, listName, profilePublicKey));
|
||||
}
|
||||
return new PacketPlayOutPlayerInfo(action, listData);
|
||||
}
|
||||
@@ -387,7 +388,7 @@ public class BukkitPacketBuilder extends PacketBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds entity BossBar packet
|
||||
* Builds entity packet representing requested BossBar packet using Wither on 1.8- clients.
|
||||
*
|
||||
* @param packet
|
||||
* packet to build
|
||||
@@ -435,13 +436,15 @@ public class BukkitPacketBuilder extends PacketBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts TAB's IChatBaseComponent into minecraft's component.
|
||||
* Converts TAB's IChatBaseComponent into minecraft's component using String deserialization.
|
||||
* If the requested component is found in cache, it is returned. If not, it is created, added into cache and returned.
|
||||
* If {@code component} is {@code null}, returns {@code null}
|
||||
*
|
||||
* @param component
|
||||
* component to convert
|
||||
* @param clientVersion
|
||||
* client version used to decide RGB conversion
|
||||
* @return converted component
|
||||
* @return converted component or {@code null} if {@code component} is {@code null}
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
|
||||
+30
-4
@@ -33,15 +33,33 @@ public class BukkitPlaceholderRegistry extends UniversalPlaceholderRegistry {
|
||||
/** Number formatter for 2 decimal places */
|
||||
private final NumberFormat numberFormat = NumberFormat.getNumberInstance(Locale.US);
|
||||
|
||||
/** JavaPlugin reference for registering events */
|
||||
private final JavaPlugin plugin;
|
||||
|
||||
/** Vault Chat hook */
|
||||
private Chat chat;
|
||||
private final Plugin essentials = Bukkit.getPluginManager().getPlugin(TabConstants.Plugin.ESSENTIALS);
|
||||
|
||||
/** NMS server to get TPS from on spigot */
|
||||
private Object server;
|
||||
|
||||
/** TPS field*/
|
||||
private Field recentTps;
|
||||
|
||||
/** Detection for presence of Paper's TPS getter */
|
||||
private Method paperTps;
|
||||
|
||||
/** Detection for presence of Paper's MSPT getter */
|
||||
private Method paperMspt;
|
||||
|
||||
/** Detection for presence of Purpur's AFK getter */
|
||||
private Method purpurIsAfk;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameter and loads hooks
|
||||
*
|
||||
* @param plugin
|
||||
* reference to the plugin's main class
|
||||
*/
|
||||
public BukkitPlaceholderRegistry(JavaPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
numberFormat.setMaximumFractionDigits(2);
|
||||
@@ -63,7 +81,6 @@ public class BukkitPlaceholderRegistry extends UniversalPlaceholderRegistry {
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void registerPlaceholders(PlaceholderManager manager) {
|
||||
super.registerPlaceholders(manager);
|
||||
manager.registerPlayerPlaceholder(TabConstants.Placeholder.DISPLAY_NAME, 500, p -> ((Player) p.getPlayer()).getDisplayName());
|
||||
if (paperTps != null) {
|
||||
manager.registerServerPlaceholder(TabConstants.Placeholder.TPS, 1000, () -> formatTPS(Bukkit.getTPS()[0]));
|
||||
@@ -81,6 +98,7 @@ public class BukkitPlaceholderRegistry extends UniversalPlaceholderRegistry {
|
||||
if (paperMspt != null) {
|
||||
manager.registerServerPlaceholder(TabConstants.Placeholder.MSPT, 1000, () -> numberFormat.format(Bukkit.getAverageTickTime()));
|
||||
}
|
||||
Plugin essentials = Bukkit.getPluginManager().getPlugin(TabConstants.Plugin.ESSENTIALS);
|
||||
manager.registerPlayerPlaceholder(TabConstants.Placeholder.AFK, 500, p -> {
|
||||
if (essentials != null && ((Essentials)essentials).getUser(p.getUniqueId()).isAfk()) return true;
|
||||
return purpurIsAfk != null && ((Player)p.getPlayer()).isAfk();
|
||||
@@ -93,8 +111,8 @@ public class BukkitPlaceholderRegistry extends UniversalPlaceholderRegistry {
|
||||
Listener nickListener = new Listener() {
|
||||
@EventHandler
|
||||
public void onNickChange(NickChangeEvent e) {
|
||||
String name = e.getValue() == null ? e.getAffected().getName() : e.getValue();
|
||||
TabPlayer player = TAB.getInstance().getPlayer(e.getAffected().getUUID());
|
||||
String name = e.getValue() == null ? e.getController().getName() : e.getValue();
|
||||
TabPlayer player = TAB.getInstance().getPlayer(e.getController().getUUID());
|
||||
if (player == null) return;
|
||||
nick.updateValue(player, name);
|
||||
}
|
||||
@@ -113,8 +131,16 @@ public class BukkitPlaceholderRegistry extends UniversalPlaceholderRegistry {
|
||||
manager.registerServerPlaceholder(TabConstants.Placeholder.VAULT_SUFFIX, -1, () -> "");
|
||||
}
|
||||
manager.registerPlayerPlaceholder(TabConstants.Placeholder.HEALTH, 100, p -> (int) Math.ceil(((Player) p.getPlayer()).getHealth()));
|
||||
super.registerPlaceholders(manager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats TPS using number formatter with 2 decimal places.
|
||||
*
|
||||
* @param tps
|
||||
* TPS to format
|
||||
* @return Formatted TPS as a String
|
||||
*/
|
||||
private String formatTPS(double tps) {
|
||||
return numberFormat.format(Math.min(20, tps));
|
||||
}
|
||||
|
||||
@@ -289,6 +289,19 @@ public class BukkitTabPlayer extends ITabPlayer {
|
||||
return getPlayer().getGameMode().getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getProfilePublicKey() {
|
||||
if (NMSStorage.getInstance().getMinorVersion() < 19) return null;
|
||||
try {
|
||||
Object key = NMSStorage.getInstance().EntityHuman_ProfilePublicKey.get(handle);
|
||||
if (key == null) return null;
|
||||
return NMSStorage.getInstance().ProfilePublicKey_getRecord.invoke(key);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to get profile key of " + getName(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Channel getChannel() {
|
||||
try {
|
||||
|
||||
@@ -55,7 +55,7 @@ public class Main extends JavaPlugin {
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
//null check due to L34 return making L37 not run
|
||||
//null check due to compatibility check making instance not get set on unsupported versions
|
||||
if (TAB.getInstance() != null) TAB.getInstance().unload();
|
||||
}
|
||||
|
||||
@@ -90,14 +90,14 @@ public class Main extends JavaPlugin {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("[TAB] Loaded NMS hook in " + (System.currentTimeMillis()-time) + "ms"));
|
||||
return true;
|
||||
} else {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] No compatibility issue was found, but this plugin version does not claim to support your server package (" + serverPackage + "). This jar has only been tested on 1.5.x - 1.19. Disabling just to stay safe."));
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] No compatibility issue was found, but this plugin version does not claim to support your server package (" + serverPackage + "). This jar has only been tested on 1.5.x - 1.19.2. Disabling just to stay safe."));
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
if (supportedVersions.contains(serverPackage)) {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Your server version is marked as compatible, but a compatibility issue was found. Please report the error below (include your server version & fork too)"));
|
||||
getLogger().log(Level.SEVERE, "", ex);
|
||||
} else {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Your server version is completely unsupported. This plugin version only supports 1.5.x - 1.19. Disabling."));
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Your server version is completely unsupported. This plugin version only supports 1.5.x - 1.19.2. Disabling."));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ import java.util.WeakHashMap;
|
||||
*/
|
||||
public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpansion {
|
||||
|
||||
/** Map holding all placeholder values for all players */
|
||||
/** Map holding all values for all players for easy and high-performance access */
|
||||
private final Map<TabPlayer, Map<String, String>> values = new WeakHashMap<>();
|
||||
|
||||
@Override
|
||||
|
||||
+19
-3
@@ -25,10 +25,10 @@ import me.neznamy.tab.shared.TAB;
|
||||
@SuppressWarnings("deprecation")
|
||||
public class PerWorldPlayerList extends TabFeature implements Listener {
|
||||
|
||||
//plugin instance
|
||||
/** Reference to main class */
|
||||
private final JavaPlugin plugin;
|
||||
|
||||
//config options
|
||||
/** Config options */
|
||||
private final boolean allowBypass = TabAPI.getInstance().getConfig().getBoolean("per-world-playerlist.allow-bypass-permission", false);
|
||||
private final List<String> ignoredWorlds = TabAPI.getInstance().getConfig().getStringList("per-world-playerlist.ignore-effect-in-worlds", Arrays.asList("ignoredworld", "build"));
|
||||
private final Map<String, List<String>> sharedWorlds = TabAPI.getInstance().getConfig().getConfigurationSection("per-world-playerlist.shared-playerlist-world-groups");
|
||||
@@ -42,8 +42,8 @@ public class PerWorldPlayerList extends TabFeature implements Listener {
|
||||
public PerWorldPlayerList(JavaPlugin plugin) {
|
||||
super("Per world PlayerList", null);
|
||||
this.plugin = plugin;
|
||||
TabAPI.getInstance().debug(String.format("Loaded PerWorldPlayerList feature with parameters allowBypass=%s, ignoredWorlds=%s, sharedWorlds=%s", allowBypass, ignoredWorlds, sharedWorlds));
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin);
|
||||
TabAPI.getInstance().debug(String.format("Loaded PerWorldPlayerList feature with parameters allowBypass=%s, ignoredWorlds=%s, sharedWorlds=%s", allowBypass, ignoredWorlds, sharedWorlds));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -75,6 +75,14 @@ public class PerWorldPlayerList extends TabFeature implements Listener {
|
||||
TAB.getInstance().getCPUManager().addTime(getFeatureName(), TabConstants.CpuUsageCategory.WORLD_SWITCH, System.nanoTime()-time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs visibility check on the player. Shows players this player should see and does not,
|
||||
* hides players the player should not see, shows the player to those who should see player and hides
|
||||
* from those, who should not see.
|
||||
*
|
||||
* @param p
|
||||
* Player to update
|
||||
*/
|
||||
private void checkPlayer(Player p) {
|
||||
for (Player all : Bukkit.getOnlinePlayers()){
|
||||
if (all == p) continue;
|
||||
@@ -85,6 +93,14 @@ public class PerWorldPlayerList extends TabFeature implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if viewer should see target player, {@code false} if not.
|
||||
* @param viewer
|
||||
* Player viewing the TabList
|
||||
* @param target
|
||||
* Target displayed in the TabList
|
||||
* @return {@code true} if viewer should see target, {@code false} if not.
|
||||
*/
|
||||
private boolean shouldSee(Player viewer, Player target) {
|
||||
if (target == viewer) return true;
|
||||
if ((allowBypass && viewer.hasPermission(TabConstants.Permission.PER_WORLD_PLAYERLIST_BYPASS)) || ignoredWorlds.contains(viewer.getWorld().getName())) return true;
|
||||
|
||||
@@ -22,13 +22,13 @@ import java.util.WeakHashMap;
|
||||
*/
|
||||
public class PetFix extends TabFeature {
|
||||
|
||||
//nms storage
|
||||
/** NMS Storage reference for quick access */
|
||||
private final NMSStorage nms = NMSStorage.getInstance();
|
||||
|
||||
//DataWatcher position of pet owner field
|
||||
/** DataWatcher position of pet owner field */
|
||||
private final int petOwnerPosition = getPetOwnerPosition();
|
||||
|
||||
//logger of last interacts to prevent feature not working on 1.16
|
||||
/** Logger of last interacts to prevent feature not working on 1.16 */
|
||||
private final WeakHashMap<TabPlayer, Long> lastInteractFix = new WeakHashMap<>();
|
||||
|
||||
/**
|
||||
@@ -94,6 +94,13 @@ public class PetFix extends TabFeature {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the provided entity use action is INTERACT or not.
|
||||
*
|
||||
* @param action
|
||||
* Action to check
|
||||
* @return {@code true} if action is INTERACT, {@code false} if not.
|
||||
*/
|
||||
private boolean isInteract(Object action) {
|
||||
if (nms.getMinorVersion() >= 17) {
|
||||
return nms.PacketPlayInUseEntity$d.isInstance(action);
|
||||
|
||||
@@ -21,11 +21,11 @@ import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
|
||||
*/
|
||||
public class WitherBossBar extends BossBarManagerImpl implements Listener {
|
||||
|
||||
//distance of wither in blocks
|
||||
/** Distance of the Wither in blocks */
|
||||
private static final int WITHER_DISTANCE = 60;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of the class
|
||||
* Constructs a new instance of the class and registers events and task
|
||||
*
|
||||
* @param plugin
|
||||
* plugin instance
|
||||
@@ -44,6 +44,9 @@ public class WitherBossBar extends BossBarManagerImpl implements Listener {
|
||||
teleport();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates Wither's location for all online players
|
||||
*/
|
||||
private void teleport() {
|
||||
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
|
||||
if (p.getVersion().getMinorVersion() > 8) continue; //sending VV packets to those
|
||||
|
||||
+50
-28
@@ -18,48 +18,63 @@ import org.bukkit.entity.Pose;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A class representing an armor stand attached to a player (if the feature is enabled)
|
||||
* A class representing an armor stand attached to a player
|
||||
*/
|
||||
public class BukkitArmorStand implements ArmorStand {
|
||||
|
||||
//entity id counter to pick unique entity IDs
|
||||
/** Entity id counter to pick unique entity ID for each armor stand */
|
||||
private static int idCounter = 2000000000;
|
||||
|
||||
//NameTag feature
|
||||
/** NameTag feature */
|
||||
private final BukkitNameTagX manager = (BukkitNameTagX) TabAPI.getInstance().getFeatureManager().getFeature(TabConstants.Feature.UNLIMITED_NAME_TAGS);
|
||||
|
||||
/** Armor stand manager which this armor stand belongs to */
|
||||
private final BukkitArmorStandManager asm;
|
||||
|
||||
//owner of the armor stand
|
||||
/** Owner of the armor stand */
|
||||
private final TabPlayer owner;
|
||||
|
||||
//instance of Bukkit player
|
||||
/** Owner as a Bukkit player */
|
||||
private final Player player;
|
||||
|
||||
//offset in blocks, 0 for original height
|
||||
/** Offset in blocks, 0 for original height */
|
||||
private double yOffset;
|
||||
|
||||
//entity ID of this armor stand
|
||||
private final int entityId = idCounter++;
|
||||
|
||||
//unique ID of this armor stand
|
||||
private final UUID uuid = UUID.randomUUID();
|
||||
|
||||
//sneaking flag of armor stands
|
||||
private boolean sneaking;
|
||||
|
||||
//armor stand visibility
|
||||
private boolean visible;
|
||||
|
||||
//property dedicated to this armor stand
|
||||
private final Property property;
|
||||
|
||||
//if offset is static or dynamic based on other armor stands
|
||||
/** If offset is static, or dynamic based on other armor stands */
|
||||
private final boolean staticOffset;
|
||||
|
||||
//entity destroy packet
|
||||
/** Entity ID of this armor stand */
|
||||
private final int entityId = idCounter++;
|
||||
|
||||
/** Unique ID of this armor stand */
|
||||
private final UUID uuid = UUID.randomUUID();
|
||||
|
||||
/** Sneaking flag of armor stands */
|
||||
private boolean sneaking;
|
||||
|
||||
/** Armor stand visibility */
|
||||
private boolean visible;
|
||||
|
||||
/** Refresh property dedicated to this armor stand */
|
||||
private final Property property;
|
||||
|
||||
/** Entity destroy packet */
|
||||
private final PacketPlayOutEntityDestroy destroyPacket = new PacketPlayOutEntityDestroy(entityId);
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters.
|
||||
*
|
||||
* @param asm
|
||||
* Armor stand manager which this armor stand belongs to
|
||||
* @param owner
|
||||
* Owner of the armor stand
|
||||
* @param propertyName
|
||||
* Name of refresh property to use
|
||||
* @param yOffset
|
||||
* Offset in blocks
|
||||
* @param staticOffset
|
||||
* {@code true} if offset is static, {@code false} if not
|
||||
*/
|
||||
public BukkitArmorStand(BukkitArmorStandManager asm, TabPlayer owner, String propertyName, double yOffset, boolean staticOffset) {
|
||||
this.asm = asm;
|
||||
this.owner = owner;
|
||||
@@ -189,7 +204,7 @@ public class BukkitArmorStand implements ArmorStand {
|
||||
/**
|
||||
* Returns general visibility rule for everyone with limited info
|
||||
*
|
||||
* @return true if armor stand should be visible, false if not
|
||||
* @return {@code true} if armor stand should be visible, {@code false} if not
|
||||
*/
|
||||
public boolean getVisibility() {
|
||||
if (manager.isArmorStandsAlwaysVisible()) return true;
|
||||
@@ -198,11 +213,13 @@ public class BukkitArmorStand implements ArmorStand {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns general location where armor stand should be at time of calling
|
||||
* Returns location where armor stand should be at time of calling.
|
||||
* This takes into account everything that affects height, including
|
||||
* viewer's game version.
|
||||
*
|
||||
* @param viewer
|
||||
* Player looking at the armor stand
|
||||
* @return Location where armor stand should be for everyone
|
||||
* @return Location where armor stand should be for specified viewer
|
||||
*/
|
||||
public Location getLocation(TabPlayer viewer) {
|
||||
double x = player.getLocation().getX();
|
||||
@@ -255,6 +272,10 @@ public class BukkitArmorStand implements ArmorStand {
|
||||
return player.getLocation().getY();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if owner is swimming, {@code false} if not
|
||||
* @return {@code true} if owner is swimming, {@code false} if not
|
||||
*/
|
||||
private boolean isSwimming() {
|
||||
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 14 && player.getPose() == Pose.SWIMMING) return true;
|
||||
return TabAPI.getInstance().getServerVersion().getMinorVersion() == 13 && player.isSwimming();
|
||||
@@ -291,11 +312,12 @@ public class BukkitArmorStand implements ArmorStand {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if display name is in fact empty, for example only containing color codes
|
||||
* Returns {@code true} if display name is in fact empty,
|
||||
* for example only containing color codes, {@code false} if not.
|
||||
*
|
||||
* @param displayName
|
||||
* string to check
|
||||
* @return true if it's empty, false if not
|
||||
* @return {@code true} if it's empty, {@code false} if not
|
||||
*/
|
||||
private boolean isNameVisiblyEmpty(String displayName) {
|
||||
if (displayName.length() == 0) return true;
|
||||
|
||||
+17
-8
@@ -16,17 +16,26 @@ import java.util.Map;
|
||||
*/
|
||||
public class BukkitArmorStandManager implements ArmorStandManager {
|
||||
|
||||
//map of registered armor stands
|
||||
/** Map of registered armor stands by name */
|
||||
private final Map<String, ArmorStand> armorStands = new LinkedHashMap<>();
|
||||
|
||||
//players in entity tracking range
|
||||
private final List<TabPlayer> nearbyPlayers = new ArrayList<>();
|
||||
|
||||
//array to iterate over to avoid concurrent modification and slightly boost performance & memory
|
||||
/** Armor stands in an array for speed while iterating */
|
||||
private ArmorStand[] armorStandArray = new ArmorStand[0];
|
||||
|
||||
/** Players in entity tracking range of owner */
|
||||
private final List<TabPlayer> nearbyPlayers = new ArrayList<>();
|
||||
|
||||
/** Nearby players in an array for speed while iterating */
|
||||
private TabPlayer[] nearbyPlayerArray = new TabPlayer[0];
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and loads armor stands.
|
||||
*
|
||||
* @param nameTagX
|
||||
* Main feature
|
||||
* @param owner
|
||||
* Owner of this armor stand manager
|
||||
*/
|
||||
public BukkitArmorStandManager(NameTagX nameTagX, TabPlayer owner) {
|
||||
owner.setProperty(nameTagX, TabConstants.Property.NAMETAG, owner.getProperty(TabConstants.Property.TAGPREFIX).getCurrentRawValue()
|
||||
+ owner.getProperty(TabConstants.Property.CUSTOMTAGNAME).getCurrentRawValue()
|
||||
@@ -61,7 +70,7 @@ public class BukkitArmorStandManager implements ArmorStandManager {
|
||||
|
||||
/**
|
||||
* Returns array of nearby players
|
||||
* @return array of nearby players
|
||||
* @return array of nearby players
|
||||
*/
|
||||
public TabPlayer[] getNearbyPlayers(){
|
||||
return nearbyPlayerArray;
|
||||
@@ -79,11 +88,11 @@ public class BukkitArmorStandManager implements ArmorStandManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if manager contains armor stand with specified entity id, false if not
|
||||
* Returns {@code true} if manager contains armor stand with specified entity id, {@code false} if not
|
||||
*
|
||||
* @param entityId
|
||||
* entity id
|
||||
* @return true if armor stand with specified entity id exists, false if not
|
||||
* @return {@code true} if armor stand with specified entity id exists, {@code false} if not
|
||||
*/
|
||||
public boolean hasArmorStandWithID(int entityId) {
|
||||
for (ArmorStand a : armorStandArray) {
|
||||
|
||||
+22
-3
@@ -11,17 +11,19 @@ import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
/**
|
||||
* The core class for unlimited NameTag mode
|
||||
* The core class for unlimited NameTag mode on Bukkit
|
||||
*/
|
||||
public class BukkitNameTagX extends NameTagX {
|
||||
|
||||
//bukkit event listener
|
||||
/** Bukkit event listener */
|
||||
private final EventListener eventListener = new EventListener(this);
|
||||
|
||||
/** Vehicle manager reference */
|
||||
private final VehicleRefresher vehicleManager = new VehicleRefresher(this);
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and loads config options
|
||||
* Constructs new instance with given parameter, loads config options, registers events
|
||||
* and registers sub-features.
|
||||
*
|
||||
* @param plugin
|
||||
* plugin instance
|
||||
@@ -45,6 +47,9 @@ public class BukkitNameTagX extends NameTagX {
|
||||
startVisibilityRefreshTask();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts task checking for player visibility to hide armor stands of invisible players.
|
||||
*/
|
||||
private void startVisibilityRefreshTask() {
|
||||
TabAPI.getInstance().getThreadManager().startRepeatingMeasuredTask(500, this, TabConstants.CpuUsageCategory.REFRESHING_NAME_TAG_VISIBILITY, () -> {
|
||||
|
||||
@@ -76,6 +81,16 @@ public class BukkitNameTagX extends NameTagX {
|
||||
return vehicleManager != null && vehicleManager.isOnBoat(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawns armor stands of target player to viewer if all requirements are met.
|
||||
* These include players being in the same world, distance being less than 48 blocks
|
||||
* and target player being visible to viewer.
|
||||
*
|
||||
* @param viewer
|
||||
* Player viewing armor stands
|
||||
* @param target
|
||||
* Target player with armor stands
|
||||
*/
|
||||
private void spawnArmorStands(TabPlayer viewer, TabPlayer target) {
|
||||
if (viewer.getVersion().getMinorVersion() < 8) return;
|
||||
if (target == viewer || isPlayerDisabled(target)) return;
|
||||
@@ -163,6 +178,10 @@ public class BukkitNameTagX extends NameTagX {
|
||||
return (BukkitArmorStandManager) armorStandManagerMap.get(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #vehicleManager}
|
||||
* @return {@link #vehicleManager}
|
||||
*/
|
||||
public VehicleRefresher getVehicleManager() {
|
||||
return vehicleManager;
|
||||
}
|
||||
|
||||
+4
-3
@@ -10,15 +10,16 @@ import org.bukkit.event.player.PlayerRespawnEvent;
|
||||
import org.bukkit.event.player.PlayerToggleSneakEvent;
|
||||
|
||||
/**
|
||||
* The event listener part for securing proper functionality of armor stands
|
||||
* Class listening to Bukkit events which the feature requires and are
|
||||
* sufficient, without packets being requires.
|
||||
*/
|
||||
public class EventListener implements Listener {
|
||||
|
||||
//the NameTag feature handler
|
||||
/** Reference to the main feature */
|
||||
private final BukkitNameTagX feature;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters
|
||||
* Constructs new instance with given parameter
|
||||
*
|
||||
* @param feature
|
||||
* NameTag feature handler
|
||||
|
||||
+51
-9
@@ -13,22 +13,25 @@ import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* The packet listening part for securing proper functionality of armor stands
|
||||
* The packet listening part for securing proper functionality of armor stands.
|
||||
* Bukkit events are too unreliable and delayed/ahead which causes de-sync
|
||||
* if trying to listen to move event.
|
||||
* For entering/leaving tracking range there are no events and
|
||||
* periodic / move-triggered distance checks would cause high CPU usage.
|
||||
*/
|
||||
public class PacketListener extends TabFeature {
|
||||
|
||||
//main feature
|
||||
/** Reference to the main feature */
|
||||
private final BukkitNameTagX nameTagX;
|
||||
|
||||
//player data by entityId, used for better performance
|
||||
/** A player map by entity id, used for better performance */
|
||||
private final Map<Integer, TabPlayer> entityIdMap = new ConcurrentHashMap<>();
|
||||
|
||||
//nms storage
|
||||
/** Reference to NMS storage for quick access */
|
||||
private final NMSStorage nms = NMSStorage.getInstance();
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and loads config options
|
||||
* Constructs new instance with given parameter
|
||||
*
|
||||
* @param nameTagX
|
||||
* main feature
|
||||
@@ -100,7 +103,10 @@ public class PacketListener extends TabFeature {
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes entity move packet
|
||||
* Processes entity move packet. If entity ID belongs to a player,
|
||||
* armor stands of that player are teleported to player who received the packet.
|
||||
* If it belongs to a vehicle carrying a player, that player's armor stands are
|
||||
* teleported as well.
|
||||
*
|
||||
* @param receiver
|
||||
* packet receiver
|
||||
@@ -126,7 +132,16 @@ public class PacketListener extends TabFeature {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Processes named entity spawn packet and spawns armor stands if
|
||||
* entity ID belongs to an online player.
|
||||
*
|
||||
* @param receiver
|
||||
* packet receiver
|
||||
* @param entityId
|
||||
* spawned entity
|
||||
*/
|
||||
private void onEntitySpawn(TabPlayer receiver, int entityId) {
|
||||
TabPlayer spawnedPlayer = entityIdMap.get(entityId);
|
||||
if (spawnedPlayer != null && spawnedPlayer.isLoaded() && !nameTagX.isPlayerDisabled(spawnedPlayer)) {
|
||||
@@ -135,18 +150,45 @@ public class PacketListener extends TabFeature {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes entity destroy packet and destroys armor stands if
|
||||
* entity ID belongs to an online player.
|
||||
*
|
||||
* @param receiver
|
||||
* packet receiver
|
||||
* @param entities
|
||||
* de-spawned entities
|
||||
*/
|
||||
private void onEntityDestroy(TabPlayer receiver, List<Integer> entities) {
|
||||
for (int entity : entities) {
|
||||
onEntityDestroy(receiver, entity);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Processes entity destroy packet and destroys armor stands if
|
||||
* entity ID belongs to an online player.
|
||||
*
|
||||
* @param receiver
|
||||
* packet receiver
|
||||
* @param entities
|
||||
* de-spawned entities
|
||||
*/
|
||||
private void onEntityDestroy(TabPlayer receiver, int... entities) {
|
||||
for (int entity : entities) {
|
||||
onEntityDestroy(receiver, entity);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Processes entity destroy packet and destroys armor stands if
|
||||
* entity ID belongs to an online player.
|
||||
*
|
||||
* @param receiver
|
||||
* packet receiver
|
||||
* @param entity
|
||||
* de-spawned entity
|
||||
*/
|
||||
private void onEntityDestroy(TabPlayer receiver, int entity) {
|
||||
TabPlayer deSpawnedPlayer = entityIdMap.get(entity);
|
||||
if (deSpawnedPlayer != null && deSpawnedPlayer.isLoaded() && !nameTagX.isPlayerDisabled(deSpawnedPlayer)) {
|
||||
|
||||
+35
-7
@@ -10,19 +10,35 @@ import org.bukkit.entity.Player;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Sub-feature for unlimited name tag mode to secure
|
||||
* proper functionality when using vehicles.
|
||||
* A config option allows to disable the feature on boats,
|
||||
* where it would cause a massive de-sync due to boat movement
|
||||
* animation.
|
||||
* Additionally, when entering a vehicle, no move packet is sent
|
||||
* and therefore manual teleporting of armor stands is required.
|
||||
*/
|
||||
public class VehicleRefresher extends TabFeature {
|
||||
|
||||
//map of players currently in a vehicle
|
||||
/** Map of players currently in a vehicle */
|
||||
private final WeakHashMap<TabPlayer, Entity> playersInVehicle = new WeakHashMap<>();
|
||||
|
||||
//map of vehicles carrying players
|
||||
/** Map of vehicles carrying players */
|
||||
private final Map<Integer, List<Entity>> vehicles = new ConcurrentHashMap<>();
|
||||
|
||||
//set of players currently on boats
|
||||
/** set of players currently on boats */
|
||||
private final Set<TabPlayer> playersOnBoats = Collections.newSetFromMap(new WeakHashMap<>());
|
||||
|
||||
|
||||
/** Reference to the main feature */
|
||||
private final BukkitNameTagX feature;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameter and starts tasks.
|
||||
*
|
||||
* @param feature
|
||||
* Main feature
|
||||
*/
|
||||
public VehicleRefresher(BukkitNameTagX feature) {
|
||||
super(feature.getFeatureName(), "Refreshing vehicles");
|
||||
this.feature = feature;
|
||||
@@ -96,10 +112,22 @@ public class VehicleRefresher extends TabFeature {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the player is in a boat, {@code false} if not
|
||||
*
|
||||
* @param p
|
||||
* Player to check
|
||||
* @return {@code true} if in a boat, {@code false} if not
|
||||
*/
|
||||
public boolean isOnBoat(TabPlayer p) {
|
||||
return playersOnBoats.contains(p);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns vehicle map
|
||||
*
|
||||
* @return vehicle map
|
||||
*/
|
||||
public Map<Integer, List<Entity>> getVehicles() {
|
||||
return vehicles;
|
||||
}
|
||||
@@ -119,7 +147,7 @@ public class VehicleRefresher extends TabFeature {
|
||||
if (vehicle.getPassenger() != null) {
|
||||
return Collections.singletonList(vehicle.getPassenger());
|
||||
} else {
|
||||
return new ArrayList<>();
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +75,8 @@ public class BukkitModernNMSStorage extends ModernNMSStorage {
|
||||
if (minorVersion >= 19) {
|
||||
Registry = Class.forName("net.minecraft.core.Registry");
|
||||
IRegistry = Class.forName("net.minecraft.core.IRegistry");
|
||||
ProfilePublicKey = Class.forName("net.minecraft.world.entity.player.ProfilePublicKey");
|
||||
ProfilePublicKey$a = Class.forName("net.minecraft.world.entity.player.ProfilePublicKey$a");
|
||||
}
|
||||
|
||||
// Player Info
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.util.Arrays;
|
||||
*/
|
||||
public abstract class ModernNMSStorage extends NMSStorage {
|
||||
|
||||
/** Additional classes used in 1.19+ */
|
||||
protected Class<?> IRegistry;
|
||||
protected Class<?> Registry;
|
||||
|
||||
|
||||
@@ -75,6 +75,8 @@ public class MojangModernNMSStorage extends ModernNMSStorage {
|
||||
if (minorVersion >= 19) {
|
||||
Registry = Class.forName("net.minecraft.core.IdMap");
|
||||
IRegistry = Class.forName("net.minecraft.core.Registry");
|
||||
ProfilePublicKey = Class.forName("net.minecraft.world.entity.player.ProfilePublicKey");
|
||||
ProfilePublicKey$a = Class.forName("net.minecraft.world.entity.player.ProfilePublicKey$Data");
|
||||
}
|
||||
|
||||
// Player Info
|
||||
|
||||
@@ -9,22 +9,22 @@ import org.bukkit.Bukkit;
|
||||
import java.lang.reflect.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Class holding all NMS classes, methods, fields and constructors used by TAB.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public abstract class NMSStorage {
|
||||
|
||||
//instance of this class
|
||||
/** Instance of this class */
|
||||
private static NMSStorage instance;
|
||||
|
||||
//server package, such as "v1_16_R3"
|
||||
/** Server's NMS/CraftBukkit package */
|
||||
protected final String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
|
||||
|
||||
//server minor version such as "16"
|
||||
/** Server's minor version */
|
||||
protected final int minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
|
||||
|
||||
//another field due to fabric which uses 1.16 package on 1.17 server
|
||||
private final ProtocolVersion serverVersion = ProtocolVersion.fromFriendlyName(Bukkit.getBukkitVersion().split("-")[0]);
|
||||
|
||||
//base
|
||||
/** Basic universal values */
|
||||
protected Class<?> Packet;
|
||||
protected Class<?> NetworkManager;
|
||||
protected Class<?> EntityArmorStand;
|
||||
@@ -39,23 +39,24 @@ public abstract class NMSStorage {
|
||||
public Field PLAYER_CONNECTION;
|
||||
public Field NETWORK_MANAGER;
|
||||
public Field CHANNEL;
|
||||
public Field EntityHuman_ProfilePublicKey;
|
||||
public Method getHandle;
|
||||
public Method sendPacket;
|
||||
public Method getProfile;
|
||||
public Enum[] EnumChatFormat_values;
|
||||
|
||||
//chat
|
||||
/** Chat components */
|
||||
protected Class<?> IChatBaseComponent;
|
||||
protected Class<?> ChatSerializer;
|
||||
public Method ChatSerializer_DESERIALIZE;
|
||||
|
||||
//PacketPlayOutChat
|
||||
/** PacketPlayOutChat */
|
||||
protected Class<?> PacketPlayOutChat;
|
||||
public Class<?> ChatMessageType;
|
||||
public Constructor<?> newPacketPlayOutChat;
|
||||
public Enum[] ChatMessageType_values;
|
||||
|
||||
//DataWatcher
|
||||
/** DataWatcher */
|
||||
protected Class<?> DataWatcher;
|
||||
protected Class<?> DataWatcherItem;
|
||||
protected Class<?> DataWatcherObject;
|
||||
@@ -70,7 +71,7 @@ public abstract class NMSStorage {
|
||||
public Method DataWatcher_REGISTER;
|
||||
private final DataWatcherRegistry registry;
|
||||
|
||||
//PacketPlayOutSpawnEntityLiving
|
||||
/** PacketPlayOutSpawnEntityLiving */
|
||||
public Class<?> PacketPlayOutSpawnEntityLiving;
|
||||
public Constructor<?> newPacketPlayOutSpawnEntityLiving;
|
||||
public Field PacketPlayOutSpawnEntityLiving_ENTITYID;
|
||||
@@ -85,7 +86,7 @@ public abstract class NMSStorage {
|
||||
public Method Registry_a;
|
||||
public Object IRegistry_X;
|
||||
|
||||
//PacketPlayOutEntityTeleport
|
||||
/** PacketPlayOutEntityTeleport */
|
||||
public Class<?> PacketPlayOutEntityTeleport;
|
||||
public Constructor<?> newPacketPlayOutEntityTeleport;
|
||||
public Field PacketPlayOutEntityTeleport_ENTITYID;
|
||||
@@ -95,13 +96,13 @@ public abstract class NMSStorage {
|
||||
public Field PacketPlayOutEntityTeleport_YAW;
|
||||
public Field PacketPlayOutEntityTeleport_PITCH;
|
||||
|
||||
//PacketPlayOutPlayerListHeaderFooter
|
||||
/** PacketPlayOutPlayerListHeaderFooter */
|
||||
protected Class<?> PacketPlayOutPlayerListHeaderFooter;
|
||||
public Constructor<?> newPacketPlayOutPlayerListHeaderFooter;
|
||||
public Field PacketPlayOutPlayerListHeaderFooter_HEADER;
|
||||
public Field PacketPlayOutPlayerListHeaderFooter_FOOTER;
|
||||
|
||||
//other entity packets
|
||||
/** Other entity packets */
|
||||
public Class<?> PacketPlayInUseEntity;
|
||||
public Class<?> PacketPlayInUseEntity$d;
|
||||
protected Class<?> EnumEntityUseAction;
|
||||
@@ -124,11 +125,13 @@ public abstract class NMSStorage {
|
||||
public Class<?> PacketPlayOutNamedEntitySpawn;
|
||||
public Field PacketPlayOutNamedEntitySpawn_ENTITYID;
|
||||
|
||||
//PacketPlayOutPlayerInfo
|
||||
/** PacketPlayOutPlayerInfo */
|
||||
public Class<?> PacketPlayOutPlayerInfo;
|
||||
protected Class<?> EnumPlayerInfoAction;
|
||||
protected Class<?> PlayerInfoData;
|
||||
protected Class<?> EnumGamemode;
|
||||
protected Class<?> ProfilePublicKey;
|
||||
protected Class<?> ProfilePublicKey$a;
|
||||
public Constructor<?> newPacketPlayOutPlayerInfo;
|
||||
public Constructor<?> newPlayerInfoData;
|
||||
public Field PacketPlayOutPlayerInfo_ACTION;
|
||||
@@ -137,10 +140,12 @@ public abstract class NMSStorage {
|
||||
public Method PlayerInfoData_getLatency;
|
||||
public Method PlayerInfoData_getGamemode;
|
||||
public Method PlayerInfoData_getDisplayName;
|
||||
public Method PlayerInfoData_getProfilePublicKeyRecord;
|
||||
public Method ProfilePublicKey_getRecord;
|
||||
public Enum[] EnumPlayerInfoAction_values;
|
||||
public Enum[] EnumGamemode_values;
|
||||
|
||||
//scoreboard objectives
|
||||
/** Scoreboard objectives */
|
||||
public Class<?> PacketPlayOutScoreboardDisplayObjective;
|
||||
public Class<?> PacketPlayOutScoreboardObjective;
|
||||
protected Class<?> Scoreboard;
|
||||
@@ -169,7 +174,7 @@ public abstract class NMSStorage {
|
||||
public Enum[] EnumScoreboardHealthDisplay_values;
|
||||
public Enum[] EnumScoreboardAction_values;
|
||||
|
||||
//PacketPlayOutScoreboardTeam
|
||||
/** PacketPlayOutScoreboardTeam */
|
||||
public Class<?> PacketPlayOutScoreboardTeam;
|
||||
protected Class<?> ScoreboardTeam;
|
||||
protected Class<?> EnumNameTagVisibility;
|
||||
@@ -195,10 +200,16 @@ public abstract class NMSStorage {
|
||||
public Enum[] EnumTeamPush_values;
|
||||
public Enum[] PacketPlayOutScoreboardTeam_PlayerAction_values;
|
||||
|
||||
//other
|
||||
/** Other */
|
||||
public Object emptyScoreboard;
|
||||
public Object dummyEntity;
|
||||
|
||||
/**
|
||||
* Constructs new instance and attempts to load all fields, methods and constructors.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If some field, constructor or method was not found
|
||||
*/
|
||||
public NMSStorage() throws ReflectiveOperationException {
|
||||
ProtocolVersion.UNKNOWN_SERVER_VERSION.setMinorVersion(minorVersion); //fixing compatibility with forks that set version field value to "Unknown"
|
||||
loadClasses();
|
||||
@@ -248,6 +259,12 @@ public abstract class NMSStorage {
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads PacketPlayOutChat's methods, fields and constructors
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If some method, field or constructor was not found
|
||||
*/
|
||||
protected void chat() throws ReflectiveOperationException {
|
||||
if (minorVersion >= 12 && minorVersion <= 18) {
|
||||
ChatMessageType_values = getEnumValues(ChatMessageType);
|
||||
@@ -270,6 +287,12 @@ public abstract class NMSStorage {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads PacketPlayOutPlayerInfo's methods, fields and constructors
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If some method, field or constructor was not found
|
||||
*/
|
||||
protected void playerInfo() throws ReflectiveOperationException {
|
||||
if (minorVersion < 8) return;
|
||||
newPacketPlayOutPlayerInfo = PacketPlayOutPlayerInfo.getConstructor(EnumPlayerInfoAction, Array.newInstance(EntityPlayer, 0).getClass());
|
||||
@@ -282,10 +305,21 @@ public abstract class NMSStorage {
|
||||
PlayerInfoData_getDisplayName = getMethods(PlayerInfoData, IChatBaseComponent).get(0);
|
||||
EnumPlayerInfoAction_values = getEnumValues(EnumPlayerInfoAction);
|
||||
EnumGamemode_values = getEnumValues(EnumGamemode);
|
||||
if (minorVersion >= 19) {
|
||||
EntityHuman_ProfilePublicKey = getFields(EntityHuman, ProfilePublicKey).get(0);
|
||||
ProfilePublicKey_getRecord = getMethods(ProfilePublicKey, ProfilePublicKey$a).get(0);
|
||||
PlayerInfoData_getProfilePublicKeyRecord = getMethods(PlayerInfoData, ProfilePublicKey$a).get(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads PacketPlayOutSpawnEntityLiving's methods, fields and constructors
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If some method, field or constructor was not found
|
||||
*/
|
||||
protected void spawnEntityLiving() throws ReflectiveOperationException {
|
||||
if (minorVersion >= 17 || serverVersion.getMinorVersion() >= 17) {
|
||||
if (minorVersion >= 17) {
|
||||
newPacketPlayOutSpawnEntityLiving = PacketPlayOutSpawnEntityLiving.getConstructor(EntityLiving);
|
||||
} else {
|
||||
newPacketPlayOutSpawnEntityLiving = PacketPlayOutSpawnEntityLiving.getConstructor();
|
||||
@@ -317,11 +351,17 @@ public abstract class NMSStorage {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads PacketPlayOutEntityTeleport's methods, fields and constructors
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If some method, field or constructor was not found
|
||||
*/
|
||||
protected void entityTeleport() throws ReflectiveOperationException {
|
||||
PacketPlayOutEntityTeleport_ENTITYID = getFields(PacketPlayOutEntityTeleport, int.class).get(0);
|
||||
PacketPlayOutEntityTeleport_YAW = getFields(PacketPlayOutEntityTeleport, byte.class).get(0);
|
||||
PacketPlayOutEntityTeleport_PITCH = getFields(PacketPlayOutEntityTeleport, byte.class).get(1);
|
||||
if (minorVersion >= 17 || serverVersion.getMinorVersion() >= 17) {
|
||||
if (minorVersion >= 17) {
|
||||
newPacketPlayOutEntityTeleport = PacketPlayOutEntityTeleport.getConstructor(Entity);
|
||||
} else {
|
||||
newPacketPlayOutEntityTeleport = PacketPlayOutEntityTeleport.getConstructor();
|
||||
@@ -337,17 +377,29 @@ public abstract class NMSStorage {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads PacketPlayOutPlayerListHeaderFooter's methods, fields and constructors
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If some method, field or constructor was not found
|
||||
*/
|
||||
protected void playerListHeaderFooter() throws ReflectiveOperationException {
|
||||
if (minorVersion < 8) return;
|
||||
PacketPlayOutPlayerListHeaderFooter_HEADER = getFields(PacketPlayOutPlayerListHeaderFooter, IChatBaseComponent).get(0);
|
||||
PacketPlayOutPlayerListHeaderFooter_FOOTER = getFields(PacketPlayOutPlayerListHeaderFooter, IChatBaseComponent).get(1);
|
||||
if (minorVersion >= 17 || serverVersion.getMinorVersion() >= 17) {
|
||||
if (minorVersion >= 17) {
|
||||
newPacketPlayOutPlayerListHeaderFooter = PacketPlayOutPlayerListHeaderFooter.getConstructor(IChatBaseComponent, IChatBaseComponent);
|
||||
} else {
|
||||
newPacketPlayOutPlayerListHeaderFooter = PacketPlayOutPlayerListHeaderFooter.getConstructor();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads methods, fields and constructors for other entity packets
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If some method, field or constructor was not found
|
||||
*/
|
||||
protected void otherEntity() throws ReflectiveOperationException {
|
||||
PacketPlayOutEntity_ENTITYID = getFields(PacketPlayOutEntity, int.class).get(0);
|
||||
PacketPlayOutEntityDestroy_ENTITIES = setAccessible(PacketPlayOutEntityDestroy.getDeclaredFields()[0]);
|
||||
@@ -366,6 +418,12 @@ public abstract class NMSStorage {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads DataWatcher's methods, fields and constructors
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If some method, field or constructor was not found
|
||||
*/
|
||||
protected void dataWatcher() throws ReflectiveOperationException {
|
||||
newDataWatcher = DataWatcher.getConstructors()[0];
|
||||
DataWatcherItem_VALUE = getFields(DataWatcherItem, Object.class).get(0);
|
||||
@@ -379,6 +437,12 @@ public abstract class NMSStorage {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads Scoreboard methods, fields and constructors
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If some method, field or constructor was not found
|
||||
*/
|
||||
protected void scoreboard() throws ReflectiveOperationException {
|
||||
newScoreboardObjective = ScoreboardObjective.getConstructors()[0];
|
||||
newScoreboard = Scoreboard.getConstructor();
|
||||
@@ -412,6 +476,12 @@ public abstract class NMSStorage {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads PacketPlayOutScoreboardTeam's methods, fields and constructors
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If some method, field or constructor was not found
|
||||
*/
|
||||
protected void scoreboardTeam() throws ReflectiveOperationException {
|
||||
newScoreboardTeam = ScoreboardTeam.getConstructor(Scoreboard, String.class);
|
||||
PacketPlayOutScoreboardTeam_NAME = getFields(PacketPlayOutScoreboardTeam, String.class).get(0);
|
||||
@@ -438,8 +508,21 @@ public abstract class NMSStorage {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all classes used by the plugin
|
||||
*
|
||||
* @throws ClassNotFoundException
|
||||
* If a class was not found
|
||||
*/
|
||||
public abstract void loadClasses() throws ClassNotFoundException;
|
||||
|
||||
/**
|
||||
* Loads all fields and methods which must be loaded by their name due to
|
||||
* lack of other methods of reliably retrieving them.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If a field or method was not found
|
||||
*/
|
||||
public abstract void loadNamedFieldsAndMethods() throws ReflectiveOperationException;
|
||||
|
||||
/**
|
||||
@@ -480,6 +563,21 @@ public abstract class NMSStorage {
|
||||
Arrays.toString(parameterTypes) + " in class " + clazz.getName() + ". Methods with matching parameters: " + list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Method from class with given name and parameter types. This should be equal to
|
||||
* simply calling Class#getMethod, which however does not work with a few specific methods on
|
||||
* CatServer fork. This workaround works for them.
|
||||
*
|
||||
* @param clazz
|
||||
* Class to get method from
|
||||
* @param name
|
||||
* Name of the method
|
||||
* @param parameterTypes
|
||||
* Parameter types of the method
|
||||
* @return Method with specified name and parameter types
|
||||
* @throws NoSuchMethodException
|
||||
* If no such method was found
|
||||
*/
|
||||
protected Method getMethod(Class<?> clazz, String name, Class<?>... parameterTypes) throws NoSuchMethodException {
|
||||
List<Method> list = new ArrayList<>();
|
||||
for (Method m : clazz.getMethods()) {
|
||||
@@ -498,7 +596,18 @@ public abstract class NMSStorage {
|
||||
throw new NoSuchMethodException("No method found with name " + name + " in class " + clazz.getName() + " with parameters " + Arrays.toString(parameterTypes));
|
||||
}
|
||||
|
||||
protected List<Method> getMethods(Class<?> clazz, Class<?> returnType, Class<?>... parameterTypes){
|
||||
/**
|
||||
* Returns all methods from class which return specified class type and have specified parameter types.
|
||||
*
|
||||
* @param clazz
|
||||
* Class to get methods from
|
||||
* @param returnType
|
||||
* Return type of methods
|
||||
* @param parameterTypes
|
||||
* Parameter types of methods
|
||||
* @return List of found methods matching requirements. If nothing is found, empty list is returned.
|
||||
*/
|
||||
protected List<Method> getMethods(Class<?> clazz, Class<?> returnType, Class<?>... parameterTypes) {
|
||||
List<Method> list = new ArrayList<>();
|
||||
for (Method m : clazz.getDeclaredMethods()) {
|
||||
if (m.getReturnType() != returnType || m.getParameterCount() != parameterTypes.length || !Modifier.isPublic(m.getModifiers())) continue;
|
||||
@@ -524,8 +633,7 @@ public abstract class NMSStorage {
|
||||
* field type to check for
|
||||
* @return list of all fields with specified class type
|
||||
*/
|
||||
protected List<Field> getFields(Class<?> clazz, Class<?> type){
|
||||
if (clazz == null) throw new IllegalArgumentException("Source class cannot be null");
|
||||
protected List<Field> getFields(Class<?> clazz, Class<?> type) {
|
||||
List<Field> list = new ArrayList<>();
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
if (field.getType() == type) {
|
||||
@@ -544,8 +652,7 @@ public abstract class NMSStorage {
|
||||
* field type to check for
|
||||
* @return list of all fields with specified class type
|
||||
*/
|
||||
public List<Field> getInstanceFields(Class<?> clazz, Class<?> type){
|
||||
if (clazz == null) throw new IllegalArgumentException("Source class cannot be null");
|
||||
public List<Field> getInstanceFields(Class<?> clazz, Class<?> type) {
|
||||
List<Field> list = new ArrayList<>();
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
if (field.getType() == type && !Modifier.isStatic(field.getModifiers())) {
|
||||
@@ -575,6 +682,19 @@ public abstract class NMSStorage {
|
||||
throw new NoSuchFieldException("Field \"" + name + "\" was not found in class " + clazz.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns field from class with specified possible names of the field.
|
||||
* Names are checked in specified order and first match is returned.
|
||||
* If no match is found, {@link NoSuchFieldException} is thrown.
|
||||
*
|
||||
* @param clazz
|
||||
* Class to get field from
|
||||
* @param potentialNames
|
||||
* Potential names of the field
|
||||
* @return Field with specified name
|
||||
* @throws NoSuchFieldException
|
||||
* If no match is found
|
||||
*/
|
||||
protected Field getField(Class<?> clazz, String... potentialNames) throws NoSuchFieldException {
|
||||
for (String name : potentialNames) {
|
||||
try {
|
||||
@@ -586,29 +706,61 @@ public abstract class NMSStorage {
|
||||
throw new NoSuchFieldException("No field found in class " + clazz.getName() + " with potential names " + Arrays.toString(potentialNames));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all Enum values of specified class
|
||||
*
|
||||
* @param enumClass
|
||||
* Class to get values of
|
||||
* @return Array of all values of the enum class
|
||||
*/
|
||||
protected Enum[] getEnumValues(Class<?> enumClass) {
|
||||
if (enumClass == null) throw new IllegalArgumentException("Class cannot be null");
|
||||
if (!enumClass.isEnum()) throw new IllegalArgumentException(enumClass.getName() + " is not an enum class");
|
||||
try {
|
||||
return (Enum[]) enumClass.getMethod("values").invoke(null);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
//this should never happen
|
||||
return new Enum[0];
|
||||
} catch (ReflectiveOperationException ignored) {
|
||||
return new Enum[0]; // this will never happen
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns server's minor version
|
||||
* @return server's minor version
|
||||
*/
|
||||
public int getMinorVersion() {
|
||||
return minorVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets value of a field in object
|
||||
*
|
||||
* @param obj
|
||||
* Instance of object where field should be set
|
||||
* @param field
|
||||
* Field to set value of
|
||||
* @param value
|
||||
* Value to set field to
|
||||
* @throws IllegalAccessException
|
||||
* If thrown by reflective operation
|
||||
*/
|
||||
public void setField(Object obj, Field field, Object value) throws IllegalAccessException {
|
||||
field.set(obj, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns data watcher registry
|
||||
* @return data watcher registry
|
||||
*/
|
||||
public DataWatcherRegistry getDataWatcherRegistry() {
|
||||
return registry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes object accessible and returns it for chaining.
|
||||
*
|
||||
* @param o
|
||||
* Object to make accessible
|
||||
* @return Entered object, for chaining
|
||||
*/
|
||||
public <T extends AccessibleObject> T setAccessible(T o) {
|
||||
o.setAccessible(true);
|
||||
return o;
|
||||
|
||||
+14
@@ -4,10 +4,20 @@ import java.util.Arrays;
|
||||
|
||||
import me.neznamy.tab.api.protocol.TabPacket;
|
||||
|
||||
/**
|
||||
* Custom class for holding data used in PacketPlayOutEntityDestroy minecraft packet.
|
||||
*/
|
||||
public class PacketPlayOutEntityDestroy implements TabPacket {
|
||||
|
||||
/** Destroyed entities */
|
||||
private final int[] entities;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameter
|
||||
*
|
||||
* @param entities
|
||||
* Destroyed entities
|
||||
*/
|
||||
public PacketPlayOutEntityDestroy(int... entities) {
|
||||
this.entities = entities;
|
||||
}
|
||||
@@ -17,6 +27,10 @@ public class PacketPlayOutEntityDestroy implements TabPacket {
|
||||
return String.format("PacketPlayOutEntityDestroy{entities=%s}", Arrays.toString(entities));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #entities}
|
||||
* @return {@link #entities}
|
||||
*/
|
||||
public int[] getEntities() {
|
||||
return entities;
|
||||
}
|
||||
|
||||
+21
@@ -4,12 +4,25 @@ import me.neznamy.tab.api.protocol.TabPacket;
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
|
||||
|
||||
/**
|
||||
* Custom class for holding data used in PacketPlayOutEntityMetadata minecraft packet.
|
||||
*/
|
||||
public class PacketPlayOutEntityMetadata implements TabPacket {
|
||||
|
||||
/** Entity's id */
|
||||
private final int entityId;
|
||||
|
||||
/** Entity's data that changed */
|
||||
private final DataWatcher dataWatcher;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters
|
||||
*
|
||||
* @param entityId
|
||||
* Entity's id
|
||||
* @param dataWatcher
|
||||
* Entity's data that changed
|
||||
*/
|
||||
public PacketPlayOutEntityMetadata(int entityId, DataWatcher dataWatcher) {
|
||||
Preconditions.checkNotNull(dataWatcher, "data watcher");
|
||||
this.entityId = entityId;
|
||||
@@ -21,10 +34,18 @@ public class PacketPlayOutEntityMetadata implements TabPacket {
|
||||
return String.format("PacketPlayOutEntityMetadata{entityId=%s,dataWatcher=%s}", entityId, dataWatcher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #entityId}
|
||||
* @return {@link #entityId}
|
||||
*/
|
||||
public int getEntityId() {
|
||||
return entityId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #dataWatcher}
|
||||
* @return {@link #dataWatcher}
|
||||
*/
|
||||
public DataWatcher getDataWatcher() {
|
||||
return dataWatcher;
|
||||
}
|
||||
|
||||
+21
@@ -5,12 +5,25 @@ import org.bukkit.Location;
|
||||
|
||||
import me.neznamy.tab.api.protocol.TabPacket;
|
||||
|
||||
/**
|
||||
* Custom class for holding data used in PacketPlayOutEntityTeleport minecraft packet.
|
||||
*/
|
||||
public class PacketPlayOutEntityTeleport implements TabPacket {
|
||||
|
||||
/** Entity's id */
|
||||
private final int entityId;
|
||||
|
||||
/** Entity's new location */
|
||||
private final Location location;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters
|
||||
*
|
||||
* @param entityId
|
||||
* Entity's id
|
||||
* @param location
|
||||
* Entity's spawn location
|
||||
*/
|
||||
public PacketPlayOutEntityTeleport(int entityId, Location location) {
|
||||
Preconditions.checkNotNull(location, "location");
|
||||
this.entityId = entityId;
|
||||
@@ -22,10 +35,18 @@ public class PacketPlayOutEntityTeleport implements TabPacket {
|
||||
return String.format("PacketPlayOutEntityTeleport{entityId=%s,location=%s}", entityId, location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #entityId}
|
||||
* @return {@link #entityId}
|
||||
*/
|
||||
public int getEntityId() {
|
||||
return entityId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #location}
|
||||
* @return {@link #location}
|
||||
*/
|
||||
public Location getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
+42
@@ -9,18 +9,40 @@ import org.bukkit.entity.EntityType;
|
||||
import me.neznamy.tab.api.protocol.TabPacket;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
|
||||
|
||||
/**
|
||||
* Custom class for holding data used in PacketPlayOutSpawnEntityLiving minecraft packet.
|
||||
*/
|
||||
public class PacketPlayOutSpawnEntityLiving implements TabPacket {
|
||||
|
||||
/** Entity's id */
|
||||
private final int entityId;
|
||||
|
||||
/** Entity's UUID (for 1.9+ servers) */
|
||||
private final UUID uuid;
|
||||
|
||||
/** Type of entity */
|
||||
private final EntityType entityType;
|
||||
|
||||
/** Entity's spawn location */
|
||||
private final Location location;
|
||||
|
||||
/** Entity's data for 1.14- servers */
|
||||
private final DataWatcher dataWatcher;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters
|
||||
*
|
||||
* @param entityId
|
||||
* Entity's id
|
||||
* @param uuid
|
||||
* Entity's UUID (for 1.9+ servers)
|
||||
* @param entityType
|
||||
* Type of entity
|
||||
* @param location
|
||||
* Entity's spawn location
|
||||
* @param dataWatcher
|
||||
* Entity's data for 1.14- servers
|
||||
*/
|
||||
public PacketPlayOutSpawnEntityLiving(int entityId, UUID uuid, EntityType entityType, Location location, DataWatcher dataWatcher) {
|
||||
Preconditions.checkNotNull(uuid, "uuid");
|
||||
Preconditions.checkNotNull(entityType, "entityType");
|
||||
@@ -38,22 +60,42 @@ public class PacketPlayOutSpawnEntityLiving implements TabPacket {
|
||||
entityId, uuid, entityType, location, dataWatcher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #entityId}
|
||||
* @return {@link #entityId}
|
||||
*/
|
||||
public int getEntityId() {
|
||||
return entityId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #uuid}
|
||||
* @return {@link #uuid}
|
||||
*/
|
||||
public UUID getUniqueId() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #entityType}
|
||||
* @return {@link #entityType}
|
||||
*/
|
||||
public EntityType getEntityType() {
|
||||
return entityType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #location}
|
||||
* @return {@link #location}
|
||||
*/
|
||||
public Location getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #dataWatcher}
|
||||
* @return {@link #dataWatcher}
|
||||
*/
|
||||
public DataWatcher getDataWatcher() {
|
||||
return dataWatcher;
|
||||
}
|
||||
|
||||
+4
-5
@@ -12,10 +12,10 @@ import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
*/
|
||||
public class DataWatcher {
|
||||
|
||||
//DataWatcher data
|
||||
/** Watched data */
|
||||
private final Map<Integer, DataWatcherItem> dataValues = new HashMap<>();
|
||||
|
||||
//a helper for easier data write
|
||||
/** Helper for easier data write */
|
||||
private final DataWatcherHelper helper = new DataWatcherHelper(this);
|
||||
|
||||
/**
|
||||
@@ -73,15 +73,14 @@ public class DataWatcher {
|
||||
NMSStorage nms = NMSStorage.getInstance();
|
||||
Object nmsWatcher;
|
||||
if (nms.newDataWatcher.getParameterCount() == 1) { //1.7+
|
||||
Object[] args = new Object[] {null};
|
||||
nmsWatcher = nms.newDataWatcher.newInstance(args);
|
||||
nmsWatcher = nms.newDataWatcher.newInstance(new Object[] {null});
|
||||
} else {
|
||||
nmsWatcher = nms.newDataWatcher.newInstance();
|
||||
}
|
||||
for (DataWatcherItem item : dataValues.values()) {
|
||||
Object position;
|
||||
if (nms.getMinorVersion() >= 9) {
|
||||
position = nms.newDataWatcherObject.newInstance(item.getType().getPosition(), item.getType().getClassType());
|
||||
position = nms.newDataWatcherObject.newInstance(item.getType().getPosition(), item.getType().getSerializer());
|
||||
} else {
|
||||
position = item.getType().getPosition();
|
||||
}
|
||||
|
||||
+3
-3
@@ -14,13 +14,13 @@ import java.util.Optional;
|
||||
*/
|
||||
public class DataWatcherHelper {
|
||||
|
||||
//position of armor stand flags
|
||||
/** Position of armor stand flags */
|
||||
private final int armorStandFlagsPosition = getArmorStandFlagsPosition();
|
||||
|
||||
//original DataWatcher to write to
|
||||
/** DataWatcher to write to */
|
||||
private final DataWatcher data;
|
||||
|
||||
//data watcher registry
|
||||
/** Data Watcher registry reference */
|
||||
private final DataWatcherRegistry registry;
|
||||
|
||||
/**
|
||||
|
||||
+13
-2
@@ -4,12 +4,15 @@ import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
|
||||
/**
|
||||
* Class representing NMS Data Watcher Item
|
||||
*/
|
||||
public class DataWatcherItem {
|
||||
|
||||
//type of value (position + data type (1.9+))
|
||||
/** Value type */
|
||||
private final DataWatcherObject type;
|
||||
|
||||
//actual data value
|
||||
/** Data value */
|
||||
private final Object value;
|
||||
|
||||
/**
|
||||
@@ -46,10 +49,18 @@ public class DataWatcherItem {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #type}
|
||||
* @return {@link #type}
|
||||
*/
|
||||
public DataWatcherObject getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #value}
|
||||
* @return {@link #value}
|
||||
*/
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
+17
-9
@@ -5,30 +5,38 @@ package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
|
||||
*/
|
||||
public class DataWatcherObject {
|
||||
|
||||
//position in DataWatcher
|
||||
/** Object's position in DataWatcher */
|
||||
private final int position;
|
||||
|
||||
//value class type used since 1.9
|
||||
private final Object classType;
|
||||
/** Value encoder for 1.9+ */
|
||||
private final Object serializer;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of this class with given parameters
|
||||
*
|
||||
* @param position
|
||||
* position in DataWatcher
|
||||
* @param classType
|
||||
* value class type
|
||||
* @param serializer
|
||||
* Serializer for value in this slot
|
||||
*/
|
||||
public DataWatcherObject(int position, Object classType){
|
||||
public DataWatcherObject(int position, Object serializer){
|
||||
this.position = position;
|
||||
this.classType = classType;
|
||||
this.serializer = serializer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #position}
|
||||
* @return {@link #position}
|
||||
*/
|
||||
public int getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public Object getClassType() {
|
||||
return classType;
|
||||
/**
|
||||
* Returns {@link #serializer}
|
||||
* @return {@link #serializer}
|
||||
*/
|
||||
public Object getSerializer() {
|
||||
return serializer;
|
||||
}
|
||||
}
|
||||
+29
@@ -9,10 +9,19 @@ import java.util.Map;
|
||||
*/
|
||||
public class DataWatcherRegistry {
|
||||
|
||||
/** Byte encoder */
|
||||
private Object registryByte;
|
||||
|
||||
/** Float encoder */
|
||||
private Object registryFloat;
|
||||
|
||||
/** String encoder */
|
||||
private Object registryString;
|
||||
|
||||
/** Encoder for optional component */
|
||||
private Object registryOptionalIChatBaseComponent;
|
||||
|
||||
/** Boolean encoder */
|
||||
private Object registryBoolean;
|
||||
|
||||
/**
|
||||
@@ -43,22 +52,42 @@ public class DataWatcherRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #registryByte}
|
||||
* @return {@link #registryByte}
|
||||
*/
|
||||
public Object getByte() {
|
||||
return registryByte;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #registryFloat}
|
||||
* @return {@link #registryFloat}
|
||||
*/
|
||||
public Object getFloat() {
|
||||
return registryFloat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #registryString}
|
||||
* @return {@link #registryString}
|
||||
*/
|
||||
public Object getString() {
|
||||
return registryString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #registryOptionalIChatBaseComponent}
|
||||
* @return {@link #registryOptionalIChatBaseComponent}
|
||||
*/
|
||||
public Object getOptionalComponent() {
|
||||
return registryOptionalIChatBaseComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #registryBoolean}
|
||||
* @return {@link #registryBoolean}
|
||||
*/
|
||||
public Object getBoolean() {
|
||||
return registryBoolean;
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ scoreboard-teams:
|
||||
- "GROUPS:owner,admin,mod,helper,builder,vip,default"
|
||||
- "PLACEHOLDER_A_TO_Z:%player%"
|
||||
case-sensitive-sorting: true
|
||||
can-see-friendly-invisibles: false
|
||||
disable-in-worlds:
|
||||
- disabledworld
|
||||
# https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Unlimited-nametag-mode
|
||||
|
||||
@@ -15,8 +15,15 @@ import net.md_5.bungee.event.EventHandler;
|
||||
*/
|
||||
public class BungeeEventListener implements Listener {
|
||||
|
||||
/** Reference to BungeeCord platform implementation */
|
||||
private final BungeePlatform platform;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameter
|
||||
*
|
||||
* @param platform
|
||||
* BungeeCord implementation of platform
|
||||
*/
|
||||
public BungeeEventListener(BungeePlatform platform) {
|
||||
this.platform = platform;
|
||||
}
|
||||
|
||||
+3
-1
@@ -10,6 +10,7 @@ import me.neznamy.tab.api.protocol.*;
|
||||
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.EnumGamemode;
|
||||
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
|
||||
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.PlayerInfoData;
|
||||
import net.md_5.bungee.protocol.PlayerPublicKey;
|
||||
import net.md_5.bungee.protocol.Property;
|
||||
import net.md_5.bungee.protocol.packet.*;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
|
||||
@@ -64,6 +65,7 @@ public class BungeePacketBuilder extends PacketBuilder {
|
||||
}
|
||||
item.setUsername(data.getName());
|
||||
item.setUuid(data.getUniqueId());
|
||||
item.setPublicKey((PlayerPublicKey) data.getProfilePublicKey());
|
||||
items.add(item);
|
||||
}
|
||||
PlayerListItem bungeePacket = new PlayerListItem();
|
||||
@@ -108,7 +110,7 @@ public class BungeePacketBuilder extends PacketBuilder {
|
||||
List<PlayerInfoData> listData = new ArrayList<>();
|
||||
for (Item i : item.getItems()) {
|
||||
Skin skin = i.getProperties() == null || i.getProperties().length == 0 ? null : new Skin(i.getProperties()[0].getValue(), i.getProperties()[0].getSignature());
|
||||
listData.add(new PlayerInfoData(i.getUsername(), i.getUuid(), skin, i.getPing(), EnumGamemode.VALUES[i.getGamemode()+1], IChatBaseComponent.deserialize(i.getDisplayName())));
|
||||
listData.add(new PlayerInfoData(i.getUsername(), i.getUuid(), skin, i.getPing(), EnumGamemode.VALUES[i.getGamemode()+1], IChatBaseComponent.deserialize(i.getDisplayName()), i.getPublicKey()));
|
||||
}
|
||||
return new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.valueOf(item.getAction().toString().replace("GAMEMODE", "GAME_MODE")), listData);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import net.md_5.bungee.api.plugin.Plugin;
|
||||
public class BungeePlatform extends ProxyPlatform {
|
||||
|
||||
/**
|
||||
* Constructs new instance
|
||||
* Constructs new instance with BungeeCord packet builder
|
||||
*/
|
||||
public BungeePlatform() {
|
||||
super(new BungeePacketBuilder());
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import de.myzelyam.api.vanish.BungeeVanishAPI;
|
||||
import io.netty.channel.Channel;
|
||||
import me.neznamy.tab.api.ProtocolVersion;
|
||||
import me.neznamy.tab.api.TabConstants;
|
||||
import me.neznamy.tab.api.protocol.Skin;
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.proxy.ProxyTabPlayer;
|
||||
import net.md_5.bungee.api.ProxyConfig;
|
||||
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.connection.InitialHandler;
|
||||
import net.md_5.bungee.connection.LoginResult;
|
||||
import net.md_5.bungee.netty.ChannelWrapper;
|
||||
import net.md_5.bungee.protocol.DefinedPacket;
|
||||
import net.md_5.bungee.protocol.Property;
|
||||
import net.md_5.bungee.protocol.Protocol;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
@@ -24,47 +26,22 @@ import java.lang.reflect.Method;
|
||||
public class BungeeTabPlayer extends ProxyTabPlayer {
|
||||
|
||||
/** Inaccessible bungee internals */
|
||||
private static Method InitialHandler_getLoginProfile;
|
||||
private static Method ChannelWrapper_getHandle;
|
||||
private static Method LoginResult_Property_getValue;
|
||||
private static Method LoginResult_Property_getSignature;
|
||||
private static Method LoginResult_getProperties;
|
||||
private static Method UserConnection_getGamemode;
|
||||
private static Field wrapperField;
|
||||
private static Object directionData;
|
||||
private static Method getId;
|
||||
|
||||
/** Rewriting offline tablist uuids coming from backend servers to online uuids on online servers */
|
||||
private static boolean tablistRewriteEnabled = true;
|
||||
|
||||
static {
|
||||
try {
|
||||
Class<?> initialHandler = Class.forName("net.md_5.bungee.connection.InitialHandler");
|
||||
InitialHandler_getLoginProfile = initialHandler.getMethod("getLoginProfile");
|
||||
Class<?> channelWrapper = Class.forName("net.md_5.bungee.netty.ChannelWrapper");
|
||||
ChannelWrapper_getHandle = channelWrapper.getMethod("getHandle");
|
||||
Class<?> loginResult = Class.forName("net.md_5.bungee.connection.LoginResult");
|
||||
Class<?> loginResult_Property = Class.forName("net.md_5.bungee.protocol.Property");
|
||||
LoginResult_Property_getValue = loginResult_Property.getMethod("getValue");
|
||||
LoginResult_Property_getSignature = loginResult_Property.getMethod("getSignature");
|
||||
LoginResult_getProperties = loginResult.getMethod("getProperties");
|
||||
Class<?> userConnection = Class.forName("net.md_5.bungee.UserConnection");
|
||||
UserConnection_getGamemode = userConnection.getMethod("getGamemode");
|
||||
Field f = Protocol.class.getDeclaredField("TO_CLIENT");
|
||||
f.setAccessible(true);
|
||||
directionData = f.get(Protocol.GAME);
|
||||
getId = directionData.getClass().getDeclaredMethod("getId", Class.class, int.class);
|
||||
getId.setAccessible(true);
|
||||
wrapperField = initialHandler.getDeclaredField("ch");
|
||||
wrapperField = InitialHandler.class.getDeclaredField("ch");
|
||||
wrapperField.setAccessible(true);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
TAB.getInstance().getErrorManager().criticalError("Failed to initialize bungee internal fields", e);
|
||||
}
|
||||
try {
|
||||
tablistRewriteEnabled = !(boolean) ProxyConfig.class.getMethod("isDisableTabListRewrite").invoke(ProxyServer.getInstance().getConfig());
|
||||
} catch (ReflectiveOperationException e) {
|
||||
// not Waterfall, always true on BungeeCord
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,10 +51,10 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
|
||||
* BungeeCord player
|
||||
*/
|
||||
public BungeeTabPlayer(ProxiedPlayer p) {
|
||||
super(p, p.getUniqueId(), p.getName(), p.getServer() != null ? p.getServer().getInfo().getName() : "-", -1, tablistRewriteEnabled);
|
||||
super(p, p.getUniqueId(), p.getName(), p.getServer() != null ? p.getServer().getInfo().getName() : "-", -1, true);
|
||||
try {
|
||||
channel = (Channel) ChannelWrapper_getHandle.invoke(wrapperField.get(getPlayer().getPendingConnection()));
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
channel = ((ChannelWrapper)wrapperField.get(getPlayer().getPendingConnection())).getHandle();
|
||||
} catch (IllegalAccessException e) {
|
||||
TAB.getInstance().getErrorManager().criticalError("Failed to get channel of " + getPlayer().getName(), e);
|
||||
}
|
||||
}
|
||||
@@ -105,17 +82,11 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
|
||||
|
||||
@Override
|
||||
public Skin getSkin() {
|
||||
try {
|
||||
Object loginResult = InitialHandler_getLoginProfile.invoke(getPlayer().getPendingConnection());
|
||||
if (loginResult == null) return null;
|
||||
Object[] properties = (Object[]) LoginResult_getProperties.invoke(loginResult);
|
||||
if (properties == null || properties.length == 0) return null;
|
||||
return new Skin((String) LoginResult_Property_getValue.invoke(properties[0]),
|
||||
(String) LoginResult_Property_getSignature.invoke(properties[0]));
|
||||
} catch (ReflectiveOperationException e) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to get skin of " + getName(), e);
|
||||
return null;
|
||||
}
|
||||
LoginResult loginResult = ((InitialHandler)getPlayer().getPendingConnection()).getLoginProfile();
|
||||
if (loginResult == null) return null;
|
||||
Property[] properties = loginResult.getProperties();
|
||||
if (properties == null || properties.length == 0) return null;
|
||||
return new Skin(properties[0].getValue(), properties[0].getSignature());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -163,12 +134,12 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
|
||||
|
||||
@Override
|
||||
public int getGamemode() {
|
||||
try {
|
||||
return (int) UserConnection_getGamemode.invoke(player);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to get gamemode of " + getPlayer().getName(), e);
|
||||
return 0;
|
||||
}
|
||||
return ((UserConnection)player).getGamemode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getProfilePublicKey() {
|
||||
return ((InitialHandler)getPlayer().getPendingConnection()).getLoginRequest().getPublicKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -41,9 +41,9 @@ public class Main extends Plugin {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for compatibility and returns true if version is supported, false if not
|
||||
* Checks for compatibility and returns {@code true} if version is supported, {@code false} if not
|
||||
*
|
||||
* @return true if version is compatible, false if not
|
||||
* @return {@code true} if version is compatible, {@code false} if not
|
||||
*/
|
||||
private boolean isVersionSupported() {
|
||||
try {
|
||||
|
||||
@@ -52,6 +52,7 @@ scoreboard-teams:
|
||||
- "GROUPS:owner,admin,mod,helper,builder,vip,default"
|
||||
- "PLACEHOLDER_A_TO_Z:%player%"
|
||||
case-sensitive-sorting: true
|
||||
can-see-friendly-invisibles: false
|
||||
disable-in-worlds:
|
||||
- disabledworld
|
||||
disable-in-servers:
|
||||
@@ -89,6 +90,7 @@ global-playerlist:
|
||||
display-vanished-players-as-spectators: true
|
||||
isolate-unlisted-servers: false
|
||||
update-latency: false
|
||||
fill-profile-key: false
|
||||
spy-servers:
|
||||
- spyserver1
|
||||
- spyserver2
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
# Project properties - we put these here so they can be modified without causing a recompile of the build scripts
|
||||
projectVersion=3.1.3
|
||||
projectVersion=3.1.5
|
||||
|
||||
# Gradle properties
|
||||
org.gradle.daemon=true
|
||||
|
||||
+19
-11
@@ -3,36 +3,40 @@ metadata.format.version = "1.1"
|
||||
[versions]
|
||||
|
||||
# API
|
||||
netty = "4.1.78.Final"
|
||||
netty = "4.1.80.Final"
|
||||
jsonSimple = "1.1.1"
|
||||
snakeyaml = "1.30"
|
||||
yamlAssist = "1.0.5"
|
||||
gson = "2.9.0"
|
||||
gson = "2.9.1"
|
||||
slf4j = "1.7.36"
|
||||
minimessage = "4.11.0"
|
||||
|
||||
# General
|
||||
luckperms = "5.4"
|
||||
guava = "31.1-jre"
|
||||
floodgate = "2.0-SNAPSHOT"
|
||||
kotlin = "1.6.10"
|
||||
floodgate = "2.2.0-SNAPSHOT"
|
||||
kotlin = "1.7.10"
|
||||
event = "3.0.0"
|
||||
|
||||
# Platforms
|
||||
bukkit = "1.16.5-R0.1-SNAPSHOT"
|
||||
bungeecord = "1.19-R0.1-SNAPSHOT"
|
||||
krypton = "0.59.2"
|
||||
velocity = "3.0.1"
|
||||
velocity = "3.1.2-SNAPSHOT"
|
||||
|
||||
# Platform extras
|
||||
placeholderapi = "2.11.1"
|
||||
placeholderapi = "2.11.2"
|
||||
vault = "1.7"
|
||||
via = "4.3.1"
|
||||
via = "4.4.2"
|
||||
authlib = "1.5.25"
|
||||
bstats = "3.0.0"
|
||||
essentials = "2.19.4"
|
||||
premiumVanish = "2.0.3"
|
||||
redisBungee = "0.7.2"
|
||||
essentials = "2.19.7"
|
||||
premiumVanish = "2.7.11-2"
|
||||
redisBungee = "0.7.3"
|
||||
|
||||
# Plugins
|
||||
indra = "2.1.1"
|
||||
shadow = "7.1.2"
|
||||
|
||||
[libraries]
|
||||
|
||||
@@ -56,7 +60,7 @@ event = { group = "net.kyori", name = "event-method", version.ref = "event" }
|
||||
|
||||
# Platforms
|
||||
bukkit = { group = "org.purpurmc.purpur", name = "purpur-api", version.ref = "bukkit" }
|
||||
bungeecord = { group = "net.md-5", name = "bungeecord-api", version.ref = "bungeecord" }
|
||||
bungeecord = { group = "net.md-5", name = "bungeecord-proxy", version.ref = "bungeecord" }
|
||||
krypton-api = { group = "org.kryptonmc", name = "krypton-api", version.ref = "krypton" }
|
||||
krypton-server = { group = "org.kryptonmc", name = "krypton-server", version.ref = "krypton" }
|
||||
krypton-annotationProcessor = { group = "org.kryptonmc", name = "krypton-annotation-processor", version.ref = "krypton" }
|
||||
@@ -75,3 +79,7 @@ redisBungee = { group = "com.github.limework.redisbungee", name = "RedisBungee-B
|
||||
bstats-bukkit = { group = "org.bstats", name = "bstats-bukkit", version.ref = "bstats" }
|
||||
bstats-bungeecord = { group = "org.bstats", name = "bstats-bungeecord", version.ref = "bstats" }
|
||||
bstats-velocity = { group = "org.bstats", name = "bstats-velocity", version.ref = "bstats" }
|
||||
|
||||
# Plugins
|
||||
plugin-indra = { group = "net.kyori", name = "indra-common", version.ref = "indra" }
|
||||
plugin-shadow = { group = "gradle.plugin.com.github.johnrengelman", name = "shadow", version.ref = "shadow" }
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
+6
-1
@@ -8,8 +8,9 @@ dependencyResolutionManagement {
|
||||
maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") // PlaceholderAPI
|
||||
maven("https://repo.viaversion.com/") // ViaVersion
|
||||
maven("https://repo.essentialsx.net/releases/") // Essentials
|
||||
maven("https://repo.codemc.org/repository/maven-public/") // RedisBungee, BungeeCord
|
||||
maven("https://repo.codemc.org/repository/maven-public/") // RedisBungee
|
||||
maven("https://repo.kryptonmc.org/releases") // YamlAssist, LuckPerms
|
||||
maven("https://repo.kryptonmc.org/snapshots") // BungeeCord
|
||||
maven("https://repo.opencollab.dev/maven-snapshots/") // Floodgate
|
||||
maven("https://repo.purpurmc.org/snapshots") // Purpur, Vault
|
||||
maven("https://repo.papermc.io/repository/maven-public/") // Velocity
|
||||
@@ -19,6 +20,10 @@ dependencyResolutionManagement {
|
||||
|
||||
pluginManagement {
|
||||
includeBuild("build-logic")
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "TAB"
|
||||
|
||||
@@ -159,7 +159,8 @@ public class FeatureManagerImpl implements FeatureManager {
|
||||
String from = changed.getServer();
|
||||
((ITabPlayer)changed).setServer(to);
|
||||
((ProxyTabPlayer)changed).sendJoinPluginMessage();
|
||||
if (!isFeatureEnabled(TabConstants.Feature.PIPELINE_INJECTION)) onLoginPacket(changed);
|
||||
if (!isFeatureEnabled(TabConstants.Feature.PIPELINE_INJECTION) || changed.getVersion().getMinorVersion() < 8)
|
||||
onLoginPacket(changed);
|
||||
for (TabFeature f : values) {
|
||||
if (!f.overridesMethod("onServerChange")) continue;
|
||||
long time = System.nanoTime();
|
||||
@@ -327,4 +328,9 @@ public class FeatureManagerImpl implements FeatureManager {
|
||||
public TabFeature getFeature(String name) {
|
||||
return features.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TabFeature[] getValues() {
|
||||
return values;
|
||||
}
|
||||
}
|
||||
@@ -415,4 +415,8 @@ public abstract class ITabPlayer implements TabPlayer {
|
||||
public void setNickname(String nickname) {
|
||||
this.nickname = nickname;
|
||||
}
|
||||
|
||||
public void setChannel(Channel channel) {
|
||||
this.channel = channel;
|
||||
}
|
||||
}
|
||||
@@ -114,7 +114,6 @@ public class TAB extends TabAPI {
|
||||
this.serverVersionString = serverVersionString;
|
||||
this.dataFolder = dataFolder;
|
||||
this.logger = logger;
|
||||
TabAPI.setInstance(this);
|
||||
try {
|
||||
Class.forName("org.geysermc.floodgate.api.FloodgateApi");
|
||||
floodgate = true;
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.util.Map;
|
||||
import me.neznamy.tab.api.TabFeature;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.api.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.api.config.ConfigurationFile;
|
||||
import me.neznamy.tab.shared.DynamicText;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.api.TabConstants;
|
||||
@@ -59,6 +60,7 @@ public class DebugCommand extends SubCommand {
|
||||
sendMessage(sender, "&6Permission plugin: &b" + TAB.getInstance().getGroupManager().getPlugin().getName());
|
||||
sendMessage(sender, "&6Permission group choice logic: &b" + getGroupChoiceLogic());
|
||||
sendMessage(sender, "&6Sorting system: &b" + getSortingType());
|
||||
sendMessage(sender, "&6Storage type: &b" + (tab.getConfiguration().getGroups() instanceof ConfigurationFile ? "File" : "MySQL"));
|
||||
sendMessage(sender, separator);
|
||||
if (analyzed == null) return;
|
||||
sendMessage(sender, "&ePlayer: &a" + analyzed.getName());
|
||||
|
||||
@@ -6,9 +6,7 @@ import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.api.TabConstants;
|
||||
import me.neznamy.tab.shared.features.nametags.unlimited.NameTagX;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Handler for "/tab nametag" subcommand
|
||||
@@ -24,16 +22,18 @@ public class NameTagCommand extends SubCommand {
|
||||
|
||||
@Override
|
||||
public void execute(TabPlayer sender, String[] args) {
|
||||
if (args.length != 1) {
|
||||
if (args.length == 0 || args.length > 3) {
|
||||
sendMessages(sender, getMessages().getNameTagHelpMenu());
|
||||
return;
|
||||
}
|
||||
boolean silent = args.length >= 3 && args[2].equals("-s");
|
||||
|
||||
switch (args[0].toLowerCase(Locale.US)) {
|
||||
case "preview":
|
||||
preview(sender);
|
||||
preview(sender,getTarget(sender, args, TabConstants.Permission.COMMAND_NAMETAG_PREVIEW_OTHER, TabConstants.Permission.COMMAND_NAMETAG_PREVIEW),silent);
|
||||
break;
|
||||
case "toggle":
|
||||
toggle(sender);
|
||||
toggle(sender,getTarget(sender, args, TabConstants.Permission.COMMAND_NAMETAG_TOGGLE_OTHER, TabConstants.Permission.COMMAND_NAMETAG_TOGGLE),silent);
|
||||
break;
|
||||
default:
|
||||
sendMessages(sender, getMessages().getNameTagHelpMenu());
|
||||
@@ -41,46 +41,54 @@ public class NameTagCommand extends SubCommand {
|
||||
}
|
||||
}
|
||||
|
||||
private void preview(TabPlayer sender) {
|
||||
if (sender == null) {
|
||||
sendMessage(null, getMessages().getCommandOnlyFromGame());
|
||||
return;
|
||||
}
|
||||
if (!hasPermission(sender, TabConstants.Permission.COMMAND_NAMETAG_PREVIEW)) {
|
||||
sendMessage(sender, getMessages().getNoPermission());
|
||||
return;
|
||||
}
|
||||
private void preview(TabPlayer sender, TabPlayer target, boolean silent) {
|
||||
if (target == null) return;
|
||||
|
||||
NameTagX nameTagX = (NameTagX) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.UNLIMITED_NAME_TAGS);
|
||||
if (nameTagX == null) {
|
||||
sendMessage(sender, getMessages().getUnlimitedNametagModeNotEnabled());
|
||||
return;
|
||||
}
|
||||
if (nameTagX.hasDisabledArmorStands(sender)) {
|
||||
if (nameTagX.hasDisabledArmorStands(target)) {
|
||||
sendMessage(sender, getMessages().getArmorStandsDisabledCannotPreview());
|
||||
return;
|
||||
}
|
||||
nameTagX.toggleNametagPreview(sender);
|
||||
nameTagX.toggleNametagPreview(target, !silent);
|
||||
}
|
||||
|
||||
private void toggle(TabPlayer sender) {
|
||||
if (sender == null) {
|
||||
sendMessage(null, getMessages().getCommandOnlyFromGame());
|
||||
return;
|
||||
}
|
||||
if (!hasPermission(sender, TabConstants.Permission.COMMAND_NAMETAG_TOGGLE)) {
|
||||
sendMessage(sender, getMessages().getNoPermission());
|
||||
return;
|
||||
}
|
||||
private void toggle(TabPlayer sender, TabPlayer target, boolean silent) {
|
||||
if (target == null) return;
|
||||
|
||||
TeamManager teams = TAB.getInstance().getTeamManager();
|
||||
if (teams == null) {
|
||||
sendMessage(sender, getMessages().getNameTagFeatureNotEnabled());
|
||||
return;
|
||||
}
|
||||
teams.toggleNameTagVisibilityView(sender, true);
|
||||
teams.toggleNameTagVisibilityView(target, !silent);
|
||||
}
|
||||
|
||||
private TabPlayer getTarget(TabPlayer sender, String[] args, String permissionOther, String permission) {
|
||||
if (args.length >= 2 && TAB.getInstance().getPlayer(args[1]) != null) {
|
||||
if (hasPermission(sender, permissionOther)) {
|
||||
return TAB.getInstance().getPlayer(args[1]);
|
||||
} else {
|
||||
sendMessage(sender, getMessages().getNoPermission());
|
||||
}
|
||||
} else {
|
||||
if (hasPermission(sender, permission)) {
|
||||
return sender;
|
||||
} else {
|
||||
sendMessage(sender, getMessages().getNoPermission());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> complete(TabPlayer sender, String[] arguments) {
|
||||
return getStartingArgument(Arrays.asList("preview", "toggle"), arguments[0]);
|
||||
if (arguments.length == 1) return getStartingArgument(Arrays.asList("toggle", "preview"), arguments[0]);
|
||||
if (arguments.length == 2) return getOnlinePlayers(arguments[1]);
|
||||
if (arguments.length == 3) return getStartingArgument(Collections.singletonList("-s"), arguments[2]);
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.api.TabConstants;
|
||||
|
||||
/**
|
||||
* Handler for "/tab parse <player> <placeholder>" subcommand
|
||||
* Handler for "/tab parse <player> <placeholder>" subcommand
|
||||
*/
|
||||
public class ParseCommand extends SubCommand {
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import me.neznamy.tab.shared.features.scoreboard.ScoreboardManagerImpl;
|
||||
|
||||
/**
|
||||
* Handler for "/tab scoreboard [on/off/toggle] [player] [options]" subcommand
|
||||
* and "/tab scoreboard show <name> [player]"
|
||||
* and "/tab scoreboard show <name> [player]"
|
||||
*/
|
||||
public class ScoreboardCommand extends SubCommand {
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ public class Converter {
|
||||
* The method contains a check if the conversion is necessary, so it is always safe
|
||||
* to call this method, even if conversion is not needed.
|
||||
* <p>
|
||||
* This change was made from 2.8.10 -> 2.9.0
|
||||
* This change was made from 2.8.10 -> 2.9.0
|
||||
*
|
||||
* @param animations
|
||||
* animation file to convert
|
||||
|
||||
@@ -214,8 +214,8 @@ public class MessageFile extends YamlConfigurationFile {
|
||||
|
||||
public List<String> getNameTagHelpMenu() {
|
||||
return getStringList("nametag-help-menu", Arrays.asList(
|
||||
"/tab nametag preview - toggles armor stand preview mode",
|
||||
"/tab nametag toggle - toggles nametags on all players for command sender"
|
||||
"/tab nametag preview [player] - toggles armor stand preview mode",
|
||||
"/tab nametag toggle [player] - toggles nametags on all players for command sender"
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ public final class EventBusImpl implements EventBus {
|
||||
this.bus = new SimpleEventBus<TabEvent>(TabEvent.class) {
|
||||
|
||||
@Override
|
||||
protected boolean shouldPost(@NonNull TabEvent event, @NonNull EventSubscriber subscriber) {
|
||||
protected boolean shouldPost(@NonNull TabEvent event, @NonNull EventSubscriber<?> subscriber) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -67,7 +67,7 @@ public class NickCompatibility extends TabFeature {
|
||||
String replacedPrefix = player.getProperty(TabConstants.Property.TAGPREFIX).getFormat(viewer);
|
||||
String replacedSuffix = player.getProperty(TabConstants.Property.TAGSUFFIX).getFormat(viewer);
|
||||
viewer.sendCustomPacket(new PacketPlayOutScoreboardTeam(player.getTeamName(), replacedPrefix, replacedSuffix, nameTags.translate(nameTags.getTeamVisibility(player, viewer)),
|
||||
nameTags.translate(nameTags.getCollisionManager().getCollision(player)), Collections.singletonList(player.getNickname()), 2), this);
|
||||
nameTags.translate(nameTags.getCollisionManager().getCollision(player)), Collections.singletonList(player.getNickname()), nameTags.getTeamOptions()), this);
|
||||
}
|
||||
}
|
||||
if (belowname != null) {
|
||||
|
||||
@@ -5,16 +5,12 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import me.neznamy.tab.api.Property;
|
||||
import me.neznamy.tab.api.TabFeature;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.api.TablistFormatManager;
|
||||
import me.neznamy.tab.api.*;
|
||||
import me.neznamy.tab.api.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo;
|
||||
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
|
||||
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.PlayerInfoData;
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
import me.neznamy.tab.api.TabConstants;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.features.layout.Layout;
|
||||
import me.neznamy.tab.shared.features.layout.LayoutManager;
|
||||
@@ -221,7 +217,7 @@ public class PlayerList extends TabFeature implements TablistFormatManager {
|
||||
|
||||
@Override
|
||||
public void onPlayerInfo(TabPlayer receiver, PacketPlayOutPlayerInfo info) {
|
||||
if (disabling || !antiOverrideTabList) return;
|
||||
if (disabling || !antiOverrideTabList || TabAPI.getInstance().getFeatureManager().isFeatureEnabled(TabConstants.Feature.LAYOUT)) return;
|
||||
if (info.getAction() != EnumPlayerInfoAction.UPDATE_DISPLAY_NAME && info.getAction() != EnumPlayerInfoAction.ADD_PLAYER) return;
|
||||
for (PlayerInfoData playerInfoData : info.getEntries()) {
|
||||
TabPlayer packetPlayer = TAB.getInstance().getPlayerByTabListUUID(playerInfoData.getUniqueId());
|
||||
|
||||
+3
-3
@@ -93,13 +93,13 @@ public class PlayerView {
|
||||
/**
|
||||
* Returns a combination of normal and bold spaces to build exactly the requested amount of pixels.
|
||||
* Must be at least 12 as lower numbers cannot always be built using numbers 4 (normal space + 1 pixel) and 5 (bold space + 1 pixel)
|
||||
* Returns the result string with normal then bold spaces, such as " &l &r"
|
||||
* Returns the result string with normal then bold spaces, such as " &l &r"
|
||||
*
|
||||
* @param pixelWidth
|
||||
* amount of pixels to be built
|
||||
* @return string consisting of spaces and &l &r
|
||||
* @return string consisting of spaces and &l &r
|
||||
* @throws IllegalArgumentException
|
||||
* if pixelWidth is < 12
|
||||
* if pixelWidth is < 12
|
||||
*/
|
||||
private String buildSpaces(int pixelWidth) {
|
||||
if (pixelWidth < 12) throw new IllegalArgumentException("Cannot build space lower than 12 pixels wide");
|
||||
|
||||
+31
-14
@@ -24,6 +24,7 @@ public class GlobalPlayerList extends TabFeature {
|
||||
private final boolean displayAsSpectators = TAB.getInstance().getConfiguration().getConfig().getBoolean("global-playerlist.display-others-as-spectators", false);
|
||||
private final boolean vanishedAsSpectators = TAB.getInstance().getConfiguration().getConfig().getBoolean("global-playerlist.display-vanished-players-as-spectators", true);
|
||||
private final boolean isolateUnlistedServers = TAB.getInstance().getConfiguration().getConfig().getBoolean("global-playerlist.isolate-unlisted-servers", false);
|
||||
private final boolean fillProfileKey = TAB.getInstance().getConfiguration().getConfig().getBoolean("global-playerlist.fill-profile-key", false);
|
||||
|
||||
public GlobalPlayerList() {
|
||||
super("Global PlayerList", null);
|
||||
@@ -97,19 +98,26 @@ public class GlobalPlayerList extends TabFeature {
|
||||
|
||||
@Override
|
||||
public void onServerChange(TabPlayer p, String from, String to) {
|
||||
PacketPlayOutPlayerInfo removeChanged = getRemovePacket(p);
|
||||
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
|
||||
if (all == p) continue;
|
||||
if (shouldSee(all, p)) {
|
||||
all.sendCustomPacket(getAddPacket(p, all), this);
|
||||
} else {
|
||||
all.sendCustomPacket(removeChanged, this);
|
||||
}
|
||||
if (shouldSee(p, all)) {
|
||||
p.sendCustomPacket(getAddPacket(all, p), this);
|
||||
} else {
|
||||
p.sendCustomPacket(getRemovePacket(all), this);
|
||||
Runnable r = () -> {
|
||||
PacketPlayOutPlayerInfo removeChanged = getRemovePacket(p);
|
||||
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
|
||||
if (all == p) continue;
|
||||
if (shouldSee(all, p)) {
|
||||
all.sendCustomPacket(getAddPacket(p, all), this);
|
||||
} else {
|
||||
all.sendCustomPacket(removeChanged, this);
|
||||
}
|
||||
if (shouldSee(p, all)) {
|
||||
p.sendCustomPacket(getAddPacket(all, p), this);
|
||||
} else {
|
||||
p.sendCustomPacket(getRemovePacket(all), this);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (!TAB.getInstance().getFeatureManager().isFeatureEnabled(TabConstants.Feature.PIPELINE_INJECTION)) {
|
||||
TAB.getInstance().getCPUManager().runTaskLater(200, this, TabConstants.CpuUsageCategory.SERVER_SWITCH, r);
|
||||
} else {
|
||||
r.run();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,8 +133,17 @@ public class GlobalPlayerList extends TabFeature {
|
||||
if (playerlist != null) {
|
||||
format = playerlist.getTabFormat(p, viewer);
|
||||
}
|
||||
return new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.ADD_PLAYER, new PlayerInfoData(p.getName(), p.getTablistUUID(), p.getSkin(),
|
||||
p.getPing(), vanishedAsSpectators && p.isVanished() ? EnumGamemode.SPECTATOR : EnumGamemode.CREATIVE, format));
|
||||
return new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.ADD_PLAYER,
|
||||
new PlayerInfoData(
|
||||
p.getName(),
|
||||
p.getTablistUUID(),
|
||||
p.getSkin(),
|
||||
p.getPing(),
|
||||
vanishedAsSpectators && p.isVanished() ? EnumGamemode.SPECTATOR : EnumGamemode.CREATIVE,
|
||||
viewer.getVersion().getMinorVersion() >= 8 ? format : null,
|
||||
fillProfileKey ? p.getProfilePublicKey() : null
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -2,6 +2,7 @@ package me.neznamy.tab.shared.features.layout;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import me.neznamy.tab.api.TabFeature;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
@@ -41,10 +42,10 @@ public class Layout extends TabFeature {
|
||||
groups.forEach(g -> list.addAll(g.getSlots(p)));
|
||||
for (FixedSlot slot : fixedSlots.values()) {
|
||||
p.setProperty(slot, slot.getPropertyName(), slot.getText());
|
||||
list.add(new PlayerInfoData("", slot.getId(), slot.getSkin(), slot.getPing(), EnumGamemode.CREATIVE, IChatBaseComponent.optimizedComponent(p.getProperty(slot.getPropertyName()).updateAndGet())));
|
||||
list.add(new PlayerInfoData("", slot.getId(), slot.getSkin(), slot.getPing(), EnumGamemode.CREATIVE, IChatBaseComponent.optimizedComponent(p.getProperty(slot.getPropertyName()).updateAndGet()), null));
|
||||
}
|
||||
for (int slot : emptySlots) {
|
||||
list.add(new PlayerInfoData("", manager.getUUID(slot), manager.getSkinManager().getDefaultSkin(), manager.getEmptySlotPing(), EnumGamemode.CREATIVE, new IChatBaseComponent("")));
|
||||
list.add(new PlayerInfoData("", manager.getUUID(slot), manager.getSkinManager().getDefaultSkin(), manager.getEmptySlotPing(), EnumGamemode.CREATIVE, new IChatBaseComponent(""), null));
|
||||
}
|
||||
if (p.getVersion().getMinorVersion() < 8 || p.isBedrockPlayer()) return;
|
||||
p.sendCustomPacket(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.ADD_PLAYER, list), this);
|
||||
@@ -70,7 +71,11 @@ public class Layout extends TabFeature {
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
List<TabPlayer> players = manager.getSortedPlayers().keySet().stream().filter(player -> !player.isVanished()).collect(Collectors.toList());
|
||||
Stream<TabPlayer> str = manager.getSortedPlayers().keySet().stream();
|
||||
if (manager.isHideVanishedPlayers()) {
|
||||
str = str.filter(player -> !player.isVanished());
|
||||
}
|
||||
List<TabPlayer> players = str.collect(Collectors.toList());
|
||||
for (ParentGroup group : groups) {
|
||||
group.tick(players);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ public class LayoutManager extends TabFeature {
|
||||
private final boolean enableRemainingPlayersText = TAB.getInstance().getConfiguration().getLayout().getBoolean("enable-remaining-players-text", true);
|
||||
private final String remainingPlayersText = EnumChatFormat.color(TAB.getInstance().getConfiguration().getLayout().getString("remaining-players-text", "... and %s more"));
|
||||
private final int emptySlotPing = TAB.getInstance().getConfiguration().getLayout().getInt("empty-slot-ping-value", 1000);
|
||||
private final boolean hideVanishedPlayers = TAB.getInstance().getConfiguration().getLayout().getBoolean("hide-vanished-players", true);
|
||||
private final SkinManager skinManager = new SkinManager(defaultSkin);
|
||||
|
||||
private final Map<Integer, UUID> uuids = new HashMap<Integer, UUID>(){{
|
||||
@@ -104,6 +105,12 @@ public class LayoutManager extends TabFeature {
|
||||
if (highest != null) highest.sendTo(p);
|
||||
playerViews.put(p, highest);
|
||||
layouts.values().forEach(Layout::tick);
|
||||
|
||||
List<PlayerInfoData> data = new ArrayList<>();
|
||||
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
|
||||
data.add(new PlayerInfoData(all.getTablistUUID()));
|
||||
}
|
||||
p.sendCustomPacket(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.UPDATE_DISPLAY_NAME, data), this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -205,6 +212,10 @@ public class LayoutManager extends TabFeature {
|
||||
return defaultSkin;
|
||||
}
|
||||
|
||||
public boolean isHideVanishedPlayers() {
|
||||
return hideVanishedPlayers;
|
||||
}
|
||||
|
||||
public enum Direction {
|
||||
|
||||
COLUMNS, ROWS
|
||||
|
||||
@@ -45,9 +45,9 @@ public class PlayerSlot {
|
||||
PlayerInfoData data;
|
||||
TabPlayer player = this.player; //avoiding NPE from concurrent access
|
||||
if (player != null) {
|
||||
data = new PlayerInfoData("", id, player.getSkin(), player.getPing(), EnumGamemode.SURVIVAL, playerlist == null ? new IChatBaseComponent(player.getName()) : playerlist.getTabFormat(player, p));
|
||||
data = new PlayerInfoData("", id, player.getSkin(), player.getPing(), EnumGamemode.SURVIVAL, playerlist == null ? new IChatBaseComponent(player.getName()) : playerlist.getTabFormat(player, p), null);
|
||||
} else {
|
||||
data = new PlayerInfoData("", id, layout.getManager().getSkinManager().getDefaultSkin(), layout.getManager().getEmptySlotPing(), EnumGamemode.SURVIVAL, new IChatBaseComponent(text));
|
||||
data = new PlayerInfoData("", id, layout.getManager().getSkinManager().getDefaultSkin(), layout.getManager().getEmptySlotPing(), EnumGamemode.SURVIVAL, new IChatBaseComponent(text), null);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ public class Texture extends SkinSource {
|
||||
con.setRequestProperty("Content-Type", "application/json");
|
||||
con.setRequestMethod("POST");
|
||||
con.setDoOutput(true);
|
||||
String jsonInputString = "{\"variant\":\"classic\",\"name\":\"string\",\"visibility\":0,\"url\":\"http://textures.minecraft.net/texture/" + texture + "\"}";
|
||||
String jsonInputString = "{\"variant\":\"classic\",\"name\":\"string\",\"visibility\":0,\"url\":\"https://textures.minecraft.net/texture/" + texture + "\"}";
|
||||
try (OutputStream os = con.getOutputStream()) {
|
||||
byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
|
||||
os.write(input, 0, input.length);
|
||||
|
||||
@@ -21,6 +21,7 @@ public class NameTag extends TabFeature implements TeamManager {
|
||||
|
||||
private final boolean invisibleNameTags = TAB.getInstance().getConfiguration().getConfig().getBoolean("scoreboard-teams.invisible-nametags", false);
|
||||
private final boolean collisionRule = TAB.getInstance().getConfiguration().getConfig().getBoolean("scoreboard-teams.enable-collision", true);
|
||||
private final boolean canSeeFriendlyInvisibles = TAB.getInstance().getConfig().getBoolean("scoreboard-teams.can-see-friendly-invisibles", false);
|
||||
private final Sorting sorting = new Sorting(this);
|
||||
private final CollisionManager collisionManager = new CollisionManager(this, collisionRule);
|
||||
|
||||
@@ -245,7 +246,7 @@ public class NameTag extends TabFeature implements TeamManager {
|
||||
String currentPrefix = tagPrefix.getFormat(viewer);
|
||||
String currentSuffix = tagSuffix.getFormat(viewer);
|
||||
boolean visible = getTeamVisibility(p, viewer);
|
||||
viewer.sendCustomPacket(new PacketPlayOutScoreboardTeam(p.getTeamName(), currentPrefix, currentSuffix, translate(visible), translate(collisionManager.getCollision(p)), 2), TabConstants.PacketCategory.NAMETAGS_TEAM_UPDATE);
|
||||
viewer.sendCustomPacket(new PacketPlayOutScoreboardTeam(p.getTeamName(), currentPrefix, currentSuffix, translate(visible), translate(collisionManager.getCollision(p)), getTeamOptions()), TabConstants.PacketCategory.NAMETAGS_TEAM_UPDATE);
|
||||
}
|
||||
RedisSupport redis = (RedisSupport) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.REDIS_BUNGEE);
|
||||
if (redis != null) redis.updateNameTag(p, p.getProperty(TabConstants.Property.TAGPREFIX).get(), p.getProperty(TabConstants.Property.TAGSUFFIX).get());
|
||||
@@ -255,7 +256,7 @@ public class NameTag extends TabFeature implements TeamManager {
|
||||
boolean visible = getTeamVisibility(p, viewer);
|
||||
String currentPrefix = p.getProperty(TabConstants.Property.TAGPREFIX).getFormat(viewer);
|
||||
String currentSuffix = p.getProperty(TabConstants.Property.TAGSUFFIX).getFormat(viewer);
|
||||
viewer.sendCustomPacket(new PacketPlayOutScoreboardTeam(p.getTeamName(), currentPrefix, currentSuffix, translate(visible), translate(collisionManager.getCollision(p)), 2), TabConstants.PacketCategory.NAMETAGS_TEAM_UPDATE);
|
||||
viewer.sendCustomPacket(new PacketPlayOutScoreboardTeam(p.getTeamName(), currentPrefix, currentSuffix, translate(visible), translate(collisionManager.getCollision(p)), getTeamOptions()), TabConstants.PacketCategory.NAMETAGS_TEAM_UPDATE);
|
||||
}
|
||||
|
||||
public void unregisterTeam(TabPlayer p) {
|
||||
@@ -276,7 +277,7 @@ public class NameTag extends TabFeature implements TeamManager {
|
||||
String replacedPrefix = p.getProperty(TabConstants.Property.TAGPREFIX).getFormat(viewer);
|
||||
String replacedSuffix = p.getProperty(TabConstants.Property.TAGSUFFIX).getFormat(viewer);
|
||||
viewer.sendCustomPacket(new PacketPlayOutScoreboardTeam(p.getTeamName(), replacedPrefix, replacedSuffix, translate(getTeamVisibility(p, viewer)),
|
||||
translate(collisionManager.getCollision(p)), Collections.singletonList(p.getNickname()), 2), TabConstants.PacketCategory.NAMETAGS_TEAM_REGISTER);
|
||||
translate(collisionManager.getCollision(p)), Collections.singletonList(p.getNickname()), getTeamOptions()), TabConstants.PacketCategory.NAMETAGS_TEAM_REGISTER);
|
||||
}
|
||||
|
||||
private void updateTeam(TabPlayer p) {
|
||||
@@ -318,6 +319,10 @@ public class NameTag extends TabFeature implements TeamManager {
|
||||
return collisionManager;
|
||||
}
|
||||
|
||||
public int getTeamOptions() {
|
||||
return canSeeFriendlyInvisibles ? 2 : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPrefix(TabPlayer player, String prefix) {
|
||||
Preconditions.checkLoaded(player);
|
||||
|
||||
+3
-3
@@ -120,14 +120,14 @@ public abstract class NameTagX extends NameTag implements UnlimitedNametagManage
|
||||
return disabledUnlimitedWorlds;
|
||||
}
|
||||
|
||||
public void toggleNametagPreview(TabPlayer player) {
|
||||
public void toggleNametagPreview(TabPlayer player, boolean sendToggleMessage) {
|
||||
if (playersPreviewingNametag.contains(player)) {
|
||||
setNameTagPreview(player, false);
|
||||
player.sendMessage(TAB.getInstance().getConfiguration().getMessages().getNametagPreviewOff(), true);
|
||||
if (sendToggleMessage) player.sendMessage(TAB.getInstance().getConfiguration().getMessages().getNametagPreviewOff(), true);
|
||||
playersPreviewingNametag.remove(player);
|
||||
} else {
|
||||
setNameTagPreview(player, true);
|
||||
player.sendMessage(TAB.getInstance().getConfiguration().getMessages().getNametagPreviewOn(), true);
|
||||
if (sendToggleMessage) player.sendMessage(TAB.getInstance().getConfiguration().getMessages().getNametagPreviewOn(), true);
|
||||
playersPreviewingNametag.add(player);
|
||||
}
|
||||
TabExpansion expansion = TAB.getInstance().getPlaceholderManager().getTabExpansion();
|
||||
|
||||
@@ -122,7 +122,7 @@ public class RedisPlayer {
|
||||
|
||||
public PacketPlayOutPlayerInfo getAddPacket() {
|
||||
return new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.ADD_PLAYER, new PlayerInfoData(nickname, uniqueId, skin, 0, EnumGamemode.SURVIVAL,
|
||||
disabledPlayerList ? null : IChatBaseComponent.optimizedComponent(tabFormat)));
|
||||
disabledPlayerList ? null : IChatBaseComponent.optimizedComponent(tabFormat), null));
|
||||
}
|
||||
|
||||
public PacketPlayOutPlayerInfo getUpdatePacket() {
|
||||
|
||||
+1
-1
@@ -266,7 +266,7 @@ public class ScoreboardManagerImpl extends TabFeature implements ScoreboardManag
|
||||
activeScoreboard.remove(player);
|
||||
}
|
||||
forcedScoreboard.put(player, (ScoreboardImpl) scoreboard);
|
||||
((ScoreboardImpl) scoreboard).addPlayer(player);
|
||||
if (hasScoreboardVisible(player)) ((ScoreboardImpl) scoreboard).addPlayer(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -10,7 +10,7 @@ import me.neznamy.tab.api.TabConstants;
|
||||
import me.neznamy.tab.shared.features.sorting.Sorting;
|
||||
|
||||
/**
|
||||
* Sorting by permission nodes tab.sort.<name>, where names are defined in sorting list
|
||||
* Sorting by permission nodes tab.sort.<name>, where names are defined in sorting list
|
||||
*/
|
||||
public class Permissions extends SortingType {
|
||||
|
||||
|
||||
@@ -53,7 +53,6 @@ public class PlayerPlaceholderImpl extends TabPlaceholder implements PlayerPlace
|
||||
if (output == null) return false; //bridge placeholders, they are updated using updateValue method
|
||||
String obj = getReplacements().findReplacement(String.valueOf(output));
|
||||
String newValue = obj == null ? identifier : setPlaceholders(obj, p);
|
||||
newValue = getReplacements().findReplacement(newValue);
|
||||
|
||||
//make invalid placeholders return identifier instead of nothing
|
||||
if (identifier.equals(newValue) && !lastValues.containsKey(p)) {
|
||||
@@ -82,12 +81,18 @@ public class PlayerPlaceholderImpl extends TabPlaceholder implements PlayerPlace
|
||||
* whether refreshing should be forced or not
|
||||
*/
|
||||
private void updateValue(TabPlayer player, Object value, boolean force) {
|
||||
String s = getReplacements().findReplacement(value == null ? lastValues.getOrDefault(player, identifier) : value.toString());
|
||||
String s = getReplacements().findReplacement(value == null ? lastValues.getOrDefault(player, identifier) :
|
||||
setPlaceholders(value.toString(), player));
|
||||
if (s.equals(lastValues.getOrDefault(player, identifier)) && !force) return;
|
||||
lastValues.put(player, s);
|
||||
if (TAB.getInstance().getPlaceholderManager().getTabExpansion() != null)
|
||||
TAB.getInstance().getPlaceholderManager().getTabExpansion().setPlaceholderValue(player, identifier, s);
|
||||
if (!player.isLoaded()) return;
|
||||
if (!player.isLoaded()) {
|
||||
if (player.isOnline()) {
|
||||
TAB.getInstance().getCPUManager().runTask(() -> updateValue(player, value, force));
|
||||
}
|
||||
return;
|
||||
}
|
||||
Set<TabFeature> usage = TAB.getInstance().getPlaceholderManager().getPlaceholderUsage().get(identifier);
|
||||
if (usage == null) return;
|
||||
for (TabFeature f : usage) {
|
||||
|
||||
@@ -44,7 +44,6 @@ public class ServerPlaceholderImpl extends TabPlaceholder implements ServerPlace
|
||||
public boolean update() {
|
||||
String obj = getReplacements().findReplacement(String.valueOf(request()));
|
||||
String newValue = obj == null ? identifier : setPlaceholders(obj, null);
|
||||
newValue = getReplacements().findReplacement(newValue);
|
||||
|
||||
//make invalid placeholders return identifier instead of nothing
|
||||
if (identifier.equals(newValue) && lastValue == null) {
|
||||
|
||||
@@ -27,6 +27,8 @@ public class Condition {
|
||||
new LinkedHashMap<String, Function<String, SimpleCondition>>(){{
|
||||
put("permission:", PermissionCondition::new);
|
||||
put("<-", ContainsCondition::new);
|
||||
put("|-", StartsWithCondition::new);
|
||||
put("-|", EndsWithCondition::new);
|
||||
put(">=", MoreThanOrEqualsCondition::new);
|
||||
put(">", MoreThanCondition::new);
|
||||
put("<=", LessThanOrEqualsCondition::new);
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package me.neznamy.tab.shared.placeholders.conditions.simple;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
/**
|
||||
* Condition for "endswith" type using "-|"
|
||||
*/
|
||||
public class EndsWithCondition extends SimpleCondition {
|
||||
|
||||
/**
|
||||
* Constructs new instance with given condition line
|
||||
*
|
||||
* @param line
|
||||
* configured condition line
|
||||
*/
|
||||
public EndsWithCondition(String line) {
|
||||
String[] arr = line.split("-\\|");
|
||||
setSides(arr.length < 1 ? "" : arr[0], arr.length < 2 ? "" : arr[1]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMet(TabPlayer p) {
|
||||
return parseLeftSide(p).endsWith(parseRightSide(p));
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -3,7 +3,7 @@ package me.neznamy.tab.shared.placeholders.conditions.simple;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
/**
|
||||
* "leftSide>rightSide" condition
|
||||
* "leftSide>rightSide" condition
|
||||
*/
|
||||
public class MoreThanCondition extends NumericCondition {
|
||||
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ package me.neznamy.tab.shared.placeholders.conditions.simple;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
/**
|
||||
* "leftSide>=rightSide" condition
|
||||
* "leftSide>=rightSide" condition
|
||||
*/
|
||||
public class MoreThanOrEqualsCondition extends NumericCondition {
|
||||
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package me.neznamy.tab.shared.placeholders.conditions.simple;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
/**
|
||||
* Condition for "startswith" type using "|-"
|
||||
*/
|
||||
public class StartsWithCondition extends SimpleCondition {
|
||||
|
||||
/**
|
||||
* Constructs new instance with given condition line
|
||||
*
|
||||
* @param line
|
||||
* configured condition line
|
||||
*/
|
||||
public StartsWithCondition(String line) {
|
||||
String[] arr = line.split("\\|-");
|
||||
setSides(arr.length < 1 ? "" : arr[0], arr.length < 2 ? "" : arr[1]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMet(TabPlayer p) {
|
||||
return parseLeftSide(p).startsWith(parseRightSide(p));
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ default-skin: "mineskin:1753261242"
|
||||
enable-remaining-players-text: true
|
||||
remaining-players-text: "... and %s more"
|
||||
empty-slot-ping-value: 1000
|
||||
hide-vanished-players: true
|
||||
|
||||
layouts:
|
||||
staff:
|
||||
|
||||
@@ -62,8 +62,8 @@ mysql-fail-error: "MySQL download failed due to an error. Check console for more
|
||||
mysql-download-success: "&aMySQL data downloaded successfully."
|
||||
mysql-upload-success: "&aMySQL data uploaded successfully."
|
||||
nametag-help-menu:
|
||||
- "/tab nametag preview - toggles armor stand preview mode"
|
||||
- "/tab nametag toggle - toggles nametags on all players for command sender"
|
||||
- "/tab nametag preview [player] - toggles armor stand preview mode"
|
||||
- "/tab nametag toggle [player] - toggles nametags on all players for command sender"
|
||||
nametag-feature-not-enabled: "&cThis command requires nametag feature to be enabled."
|
||||
nametags-hidden: "&aNametags of all players were hidden to you"
|
||||
nametags-shown: "&aNametags of all players were shown to you"
|
||||
@@ -30,7 +30,10 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Main class for Velocity platform
|
||||
* Main class for Velocity platform.
|
||||
* The velocity-plugin.json file creation is disabled by default and
|
||||
* requires manual compilation. This avoids unnecessary complications
|
||||
* and bug reports from an unsupported platform.
|
||||
*/
|
||||
//@com.velocitypowered.api.plugin.Plugin(id = "tab", name = "TAB", version = TabConstants.PLUGIN_VERSION, description = "An all-in-one solution that works", authors = {"NEZNAMY"})
|
||||
public class Main {
|
||||
@@ -96,6 +99,11 @@ public class Main {
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns platform implementation for Velocity
|
||||
*
|
||||
* @return platform implementation for Velocity
|
||||
*/
|
||||
public VelocityPlatform getPlatform() {
|
||||
return platform;
|
||||
}
|
||||
@@ -119,7 +127,8 @@ public class Main {
|
||||
}
|
||||
|
||||
/**
|
||||
* Unloads the plugin
|
||||
* Shutdown event listener that properly disables all features
|
||||
* and makes them send unload packets to players.
|
||||
*
|
||||
* @param event
|
||||
* proxy disable event
|
||||
@@ -132,12 +141,14 @@ public class Main {
|
||||
/**
|
||||
* Converts TAB's component class into adventure component.
|
||||
* Currently, the only way of conversion is string serialization / deserialization.
|
||||
* Manual conversion for better performance might be added in the future.
|
||||
* If the entered component is {@code null}, returns {@code null}
|
||||
*
|
||||
* @param component
|
||||
* Component to convert
|
||||
* @param clientVersion
|
||||
* Version of player to convert for
|
||||
* @return Converted component
|
||||
* @return Converted component or {@code null} if {@code component} is {@code null}
|
||||
*/
|
||||
public Component convertComponent(IChatBaseComponent component, ProtocolVersion clientVersion) {
|
||||
if (component == null) return null;
|
||||
@@ -149,7 +160,7 @@ public class Main {
|
||||
* inserted into cache and returned.
|
||||
*
|
||||
* @param map
|
||||
* Cache to load / save component
|
||||
* Cache to load component from / save component into
|
||||
* @param component
|
||||
* Component to convert
|
||||
* @param clientVersion
|
||||
@@ -165,7 +176,7 @@ public class Main {
|
||||
}
|
||||
|
||||
/**
|
||||
* TAB's command
|
||||
* TAB's main command for operating with the plugin
|
||||
*/
|
||||
private static class VelocityTABCommand implements SimpleCommand {
|
||||
|
||||
|
||||
@@ -38,11 +38,12 @@ public class VelocityEventListener {
|
||||
public void onConnect(ServerPostConnectEvent e){
|
||||
if (TabAPI.getInstance().isPluginDisabled()) return;
|
||||
Player p = e.getPlayer();
|
||||
TabAPI.getInstance().getThreadManager().runTaskLater(200, () -> {
|
||||
TabAPI.getInstance().getThreadManager().runTask(() -> {
|
||||
if (TabAPI.getInstance().getPlayer(p.getUniqueId()) == null) {
|
||||
TabAPI.getInstance().getThreadManager().runTask(() -> TabAPI.getInstance().getFeatureManager().onJoin(new VelocityTabPlayer(p)));
|
||||
TabAPI.getInstance().getFeatureManager().onJoin(new VelocityTabPlayer(p));
|
||||
} else {
|
||||
TabAPI.getInstance().getFeatureManager().onServerChange(p.getUniqueId(), p.getCurrentServer().isPresent() ? p.getCurrentServer().get().getServerInfo().getName() : "null");
|
||||
String server = p.getCurrentServer().isPresent() ? p.getCurrentServer().get().getServerInfo().getName() : "null";
|
||||
TabAPI.getInstance().getFeatureManager().onServerChange(p.getUniqueId(), server);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -62,7 +63,7 @@ public class VelocityEventListener {
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener to plugin message event
|
||||
* Listener to plugin message event to process messages coming from bridge
|
||||
*
|
||||
* @param event
|
||||
* plugin message event
|
||||
|
||||
Loaded 100 of 101 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user