2013-12-09 23:09:08 +01:00
|
|
|
package com.comphenix.protocol.wrappers;
|
|
|
|
|
2014-02-02 16:50:56 +01:00
|
|
|
import com.comphenix.protocol.PacketType;
|
2013-12-09 23:09:08 +01:00
|
|
|
import com.comphenix.protocol.injector.BukkitUnwrapper;
|
|
|
|
import com.comphenix.protocol.reflect.EquivalentConverter;
|
|
|
|
import com.comphenix.protocol.reflect.accessors.Accessors;
|
|
|
|
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
|
|
|
|
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
|
2014-02-02 16:50:56 +01:00
|
|
|
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
|
2016-06-09 04:28:53 +02:00
|
|
|
import com.comphenix.protocol.utility.MinecraftProtocolVersion;
|
2013-12-09 23:09:08 +01:00
|
|
|
import com.comphenix.protocol.utility.MinecraftReflection;
|
|
|
|
import com.comphenix.protocol.utility.MinecraftVersion;
|
2015-03-08 00:27:48 +01:00
|
|
|
import com.comphenix.protocol.utility.Util;
|
2013-12-09 23:09:08 +01:00
|
|
|
import com.google.common.base.Preconditions;
|
|
|
|
import com.google.common.base.Splitter;
|
|
|
|
import com.google.common.collect.ImmutableList;
|
|
|
|
import com.google.common.io.ByteStreams;
|
2016-06-09 04:28:53 +02:00
|
|
|
import io.netty.buffer.ByteBuf;
|
|
|
|
import io.netty.buffer.Unpooled;
|
|
|
|
import io.netty.handler.codec.base64.Base64;
|
2022-06-25 17:32:42 +02:00
|
|
|
import org.bukkit.Bukkit;
|
|
|
|
import org.bukkit.Server;
|
|
|
|
import org.bukkit.entity.Player;
|
|
|
|
|
|
|
|
import javax.imageio.ImageIO;
|
|
|
|
import java.awt.image.BufferedImage;
|
|
|
|
import java.awt.image.RenderedImage;
|
|
|
|
import java.io.ByteArrayInputStream;
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.io.InputStream;
|
|
|
|
import java.nio.charset.StandardCharsets;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.List;
|
2016-06-09 04:28:53 +02:00
|
|
|
|
2013-12-09 23:09:08 +01:00
|
|
|
/**
|
|
|
|
* Represents a server ping packet data.
|
|
|
|
* @author Kristian
|
|
|
|
*/
|
2018-09-15 20:32:18 +02:00
|
|
|
public class WrappedServerPing extends AbstractWrapper implements ClonableWrapper {
|
2015-06-19 03:07:32 +02:00
|
|
|
private static Class<?> GAME_PROFILE = MinecraftReflection.getGameProfileClass();
|
|
|
|
private static Class<?> GAME_PROFILE_ARRAY = MinecraftReflection.getArrayClass(GAME_PROFILE);
|
|
|
|
|
2013-12-09 23:09:08 +01:00
|
|
|
// Server ping fields
|
|
|
|
private static Class<?> SERVER_PING = MinecraftReflection.getServerPingClass();
|
|
|
|
private static ConstructorAccessor SERVER_PING_CONSTRUCTOR = Accessors.getConstructorAccessor(SERVER_PING);
|
|
|
|
private static FieldAccessor DESCRIPTION = Accessors.getFieldAccessor(SERVER_PING, MinecraftReflection.getIChatBaseComponentClass(), true);
|
|
|
|
private static FieldAccessor PLAYERS = Accessors.getFieldAccessor(SERVER_PING, MinecraftReflection.getServerPingPlayerSampleClass(), true);
|
|
|
|
private static FieldAccessor VERSION = Accessors.getFieldAccessor(SERVER_PING, MinecraftReflection.getServerPingServerDataClass(), true);
|
|
|
|
private static FieldAccessor FAVICON = Accessors.getFieldAccessor(SERVER_PING, String.class, true);
|
2022-07-27 23:09:25 +02:00
|
|
|
private static FieldAccessor[] BOOLEAN_ACCESSORS = Accessors.getFieldAccessorArray(SERVER_PING, boolean.class, true);
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-09 23:09:08 +01:00
|
|
|
// For converting to the underlying array
|
2014-11-15 19:02:03 +01:00
|
|
|
private static EquivalentConverter<Iterable<? extends WrappedGameProfile>> PROFILE_CONVERT =
|
2015-06-19 03:07:32 +02:00
|
|
|
BukkitConverters.getArrayConverter(GAME_PROFILE, BukkitConverters.getWrappedGameProfileConverter());
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-09 23:09:08 +01:00
|
|
|
// Server ping player sample fields
|
|
|
|
private static Class<?> PLAYERS_CLASS = MinecraftReflection.getServerPingPlayerSampleClass();
|
|
|
|
private static ConstructorAccessor PLAYERS_CONSTRUCTOR = Accessors.getConstructorAccessor(PLAYERS_CLASS, int.class, int.class);
|
|
|
|
private static FieldAccessor[] PLAYERS_INTS = Accessors.getFieldAccessorArray(PLAYERS_CLASS, int.class, true);
|
2015-06-19 03:07:32 +02:00
|
|
|
private static FieldAccessor PLAYERS_PROFILES = Accessors.getFieldAccessor(PLAYERS_CLASS, GAME_PROFILE_ARRAY, true);
|
2013-12-09 23:09:08 +01:00
|
|
|
private static FieldAccessor PLAYERS_MAXIMUM = PLAYERS_INTS[0];
|
|
|
|
private static FieldAccessor PLAYERS_ONLINE = PLAYERS_INTS[1];
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2014-02-02 16:50:56 +01:00
|
|
|
// Server ping serialization
|
|
|
|
private static Class<?> GSON_CLASS = MinecraftReflection.getMinecraftGsonClass();
|
|
|
|
private static MethodAccessor GSON_TO_JSON = Accessors.getMethodAccessor(GSON_CLASS, "toJson", Object.class);
|
|
|
|
private static MethodAccessor GSON_FROM_JSON = Accessors.getMethodAccessor(GSON_CLASS, "fromJson", String.class, Class.class);
|
2022-07-24 16:16:05 +02:00
|
|
|
private static FieldAccessor PING_GSON = Accessors.getMemorizing(Accessors.getFieldAccessor(
|
2016-03-20 22:46:23 +01:00
|
|
|
PacketType.Status.Server.SERVER_INFO.getPacketClass(), GSON_CLASS, true
|
2014-02-02 16:50:56 +01:00
|
|
|
));
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-09 23:09:08 +01:00
|
|
|
// Server data fields
|
|
|
|
private static Class<?> VERSION_CLASS = MinecraftReflection.getServerPingServerDataClass();
|
|
|
|
private static ConstructorAccessor VERSION_CONSTRUCTOR = Accessors.getConstructorAccessor(VERSION_CLASS, String.class, int.class);
|
|
|
|
private static FieldAccessor VERSION_NAME = Accessors.getFieldAccessor(VERSION_CLASS, String.class, true);
|
|
|
|
private static FieldAccessor VERSION_PROTOCOL = Accessors.getFieldAccessor(VERSION_CLASS, int.class, true);
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-09 23:09:08 +01:00
|
|
|
// Get profile from player
|
|
|
|
private static FieldAccessor ENTITY_HUMAN_PROFILE = Accessors.getFieldAccessor(
|
2015-06-19 03:07:32 +02:00
|
|
|
MinecraftReflection.getEntityPlayerClass().getSuperclass(), GAME_PROFILE, true);
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-09 23:09:08 +01:00
|
|
|
// Inner class
|
2014-01-01 04:21:31 +01:00
|
|
|
private Object players; // may be NULL
|
2013-12-09 23:09:08 +01:00
|
|
|
private Object version;
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-09 23:09:08 +01:00
|
|
|
/**
|
2014-11-15 19:02:03 +01:00
|
|
|
* Construct a new server ping initialized with a zero player count, and zero maximum.
|
2014-01-01 04:21:31 +01:00
|
|
|
* <p>
|
2016-06-09 04:28:53 +02:00
|
|
|
* Note that the version string is set to 1.9.4.
|
2013-12-09 23:09:08 +01:00
|
|
|
*/
|
|
|
|
public WrappedServerPing() {
|
|
|
|
super(MinecraftReflection.getServerPingClass());
|
|
|
|
setHandle(SERVER_PING_CONSTRUCTOR.invoke());
|
2014-01-01 04:21:31 +01:00
|
|
|
resetPlayers();
|
|
|
|
resetVersion();
|
2013-12-09 23:09:08 +01:00
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-09 23:09:08 +01:00
|
|
|
private WrappedServerPing(Object handle) {
|
|
|
|
super(MinecraftReflection.getServerPingClass());
|
|
|
|
setHandle(handle);
|
|
|
|
this.players = PLAYERS.get(handle);
|
|
|
|
this.version = VERSION.get(handle);
|
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2014-01-01 04:21:31 +01:00
|
|
|
/**
|
|
|
|
* Set the player count and player maximum to the default values.
|
|
|
|
*/
|
|
|
|
protected void resetPlayers() {
|
|
|
|
players = PLAYERS_CONSTRUCTOR.invoke(0, 0);
|
|
|
|
PLAYERS.set(handle, players);
|
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2014-01-01 04:21:31 +01:00
|
|
|
/**
|
|
|
|
* Reset the version string to the default state.
|
|
|
|
*/
|
|
|
|
protected void resetVersion() {
|
2016-06-09 04:28:53 +02:00
|
|
|
MinecraftVersion minecraftVersion = MinecraftVersion.getCurrentVersion();
|
|
|
|
version = VERSION_CONSTRUCTOR.invoke(minecraftVersion.toString(), MinecraftProtocolVersion.getCurrentVersion());
|
2014-01-01 04:21:31 +01:00
|
|
|
VERSION.set(handle, version);
|
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-09 23:09:08 +01:00
|
|
|
/**
|
|
|
|
* Construct a wrapped server ping from a native NMS object.
|
|
|
|
* @param handle - the native object.
|
|
|
|
* @return The wrapped server ping object.
|
|
|
|
*/
|
|
|
|
public static WrappedServerPing fromHandle(Object handle) {
|
|
|
|
return new WrappedServerPing(handle);
|
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2014-02-02 16:50:56 +01:00
|
|
|
/**
|
|
|
|
* Construct a wrapper server ping from an encoded JSON string.
|
|
|
|
* @param json - the JSON string.
|
|
|
|
* @return The wrapped server ping.
|
|
|
|
*/
|
|
|
|
public static WrappedServerPing fromJson(String json) {
|
|
|
|
return fromHandle(GSON_FROM_JSON.invoke(PING_GSON.get(null), json, SERVER_PING));
|
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-09 23:09:08 +01:00
|
|
|
/**
|
|
|
|
* Retrieve the message of the day.
|
|
|
|
* @return The messge of the day.
|
|
|
|
*/
|
|
|
|
public WrappedChatComponent getMotD() {
|
|
|
|
return WrappedChatComponent.fromHandle(DESCRIPTION.get(handle));
|
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-09 23:09:08 +01:00
|
|
|
/**
|
|
|
|
* Set the message of the day.
|
|
|
|
* @param description - message of the day.
|
|
|
|
*/
|
|
|
|
public void setMotD(WrappedChatComponent description) {
|
|
|
|
DESCRIPTION.set(handle, description.getHandle());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2014-01-09 20:01:37 +01:00
|
|
|
* Set the message of the day.
|
2015-06-17 20:25:39 +02:00
|
|
|
* @param message - the message.
|
2013-12-09 23:09:08 +01:00
|
|
|
*/
|
|
|
|
public void setMotD(String message) {
|
2021-05-02 23:43:32 +02:00
|
|
|
setMotD(WrappedChatComponent.fromLegacyText(message));
|
2013-12-09 23:09:08 +01:00
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-09 23:09:08 +01:00
|
|
|
/**
|
|
|
|
* Retrieve the compressed PNG file that is being displayed as a favicon.
|
2014-01-01 16:29:21 +01:00
|
|
|
* @return The favicon, or NULL if no favicon will be displayed.
|
2013-12-09 23:09:08 +01:00
|
|
|
*/
|
|
|
|
public CompressedImage getFavicon() {
|
2014-01-01 16:29:21 +01:00
|
|
|
String favicon = (String) FAVICON.get(handle);
|
|
|
|
return (favicon != null) ? CompressedImage.fromEncodedText(favicon) : null;
|
2013-12-09 23:09:08 +01:00
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-09 23:09:08 +01:00
|
|
|
/**
|
|
|
|
* Set the compressed PNG file that is being displayed.
|
2014-01-01 16:29:21 +01:00
|
|
|
* @param image - the new compressed image or NULL if no favicon should be displayed.
|
2013-12-09 23:09:08 +01:00
|
|
|
*/
|
|
|
|
public void setFavicon(CompressedImage image) {
|
2014-01-01 16:29:21 +01:00
|
|
|
FAVICON.set(handle, (image != null) ? image.toEncodedText() : null);
|
2013-12-09 23:09:08 +01:00
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2022-06-11 18:35:12 +02:00
|
|
|
/**
|
|
|
|
* Retrieve whether chat preview is enabled on the server.
|
|
|
|
* @return whether chat preview is enabled on the server.
|
|
|
|
* @since 1.19
|
2022-12-14 02:38:37 +01:00
|
|
|
* @deprecated Removed in 1.19.3
|
2022-06-11 18:35:12 +02:00
|
|
|
*/
|
2022-12-14 02:38:37 +01:00
|
|
|
@Deprecated
|
2022-06-11 18:35:12 +02:00
|
|
|
public boolean isChatPreviewEnabled() {
|
2022-07-27 23:09:25 +02:00
|
|
|
return (Boolean) BOOLEAN_ACCESSORS[0].get(handle);
|
2022-06-11 18:35:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets whether chat preview is enabled on the server.
|
|
|
|
* @param chatPreviewEnabled true if enabled, false otherwise.
|
|
|
|
* @since 1.19
|
2022-12-14 02:38:37 +01:00
|
|
|
* @deprecated Removed in 1.19.3
|
2022-06-11 18:35:12 +02:00
|
|
|
*/
|
2022-12-14 02:38:37 +01:00
|
|
|
@Deprecated
|
2022-06-11 18:35:12 +02:00
|
|
|
public void setChatPreviewEnabled(boolean chatPreviewEnabled) {
|
2022-07-27 23:09:25 +02:00
|
|
|
BOOLEAN_ACCESSORS[0].set(handle, chatPreviewEnabled);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets whether the server enforces secure chat.
|
|
|
|
* @return whether the server enforces secure chat.
|
|
|
|
* @since 1.19.1
|
|
|
|
*/
|
|
|
|
public boolean isEnforceSecureChat() {
|
2022-12-14 02:38:37 +01:00
|
|
|
int index = MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove() ? 0 : 1;
|
|
|
|
return (Boolean) BOOLEAN_ACCESSORS[index].get(handle);
|
2022-07-27 23:09:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets whether the server enforces secure chat.
|
|
|
|
* @param enforceSecureChat true if enabled, false otherwise.
|
|
|
|
* @since 1.19.1
|
|
|
|
*/
|
|
|
|
public void setEnforceSecureChat(boolean enforceSecureChat) {
|
2022-12-14 02:38:37 +01:00
|
|
|
int index = MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove() ? 0 : 1;
|
|
|
|
BOOLEAN_ACCESSORS[index].set(handle, enforceSecureChat);
|
2022-06-11 18:35:12 +02:00
|
|
|
}
|
|
|
|
|
2013-12-09 23:09:08 +01:00
|
|
|
/**
|
|
|
|
* Retrieve the displayed number of online players.
|
|
|
|
* @return The displayed number.
|
2014-01-01 04:21:31 +01:00
|
|
|
* @throws IllegalStateException If the player count has been hidden via {@link #setPlayersVisible(boolean)}.
|
2015-06-17 20:25:39 +02:00
|
|
|
* @see #setPlayersOnline(int)
|
2013-12-09 23:09:08 +01:00
|
|
|
*/
|
|
|
|
public int getPlayersOnline() {
|
2014-01-01 04:21:31 +01:00
|
|
|
if (players == null)
|
|
|
|
throw new IllegalStateException("The player count has been hidden.");
|
2013-12-09 23:09:08 +01:00
|
|
|
return (Integer) PLAYERS_ONLINE.get(players);
|
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-09 23:09:08 +01:00
|
|
|
/**
|
|
|
|
* Set the displayed number of online players.
|
2014-01-01 04:21:31 +01:00
|
|
|
* <p>
|
2014-11-15 19:02:03 +01:00
|
|
|
* As of 1.7.2, this is completely unrestricted, and can be both positive and
|
2014-01-01 04:21:31 +01:00
|
|
|
* negative, as well as higher than the player maximum.
|
2013-12-09 23:09:08 +01:00
|
|
|
* @param online - online players.
|
|
|
|
*/
|
|
|
|
public void setPlayersOnline(int online) {
|
2014-01-01 04:21:31 +01:00
|
|
|
if (players == null)
|
|
|
|
resetPlayers();
|
2013-12-09 23:09:08 +01:00
|
|
|
PLAYERS_ONLINE.set(players, online);
|
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-09 23:09:08 +01:00
|
|
|
/**
|
|
|
|
* Retrieve the displayed maximum number of players.
|
|
|
|
* @return The maximum number.
|
2014-01-01 04:21:31 +01:00
|
|
|
* @throws IllegalStateException If the player maximum has been hidden via {@link #setPlayersVisible(boolean)}.
|
2015-06-17 20:25:39 +02:00
|
|
|
* @see #setPlayersMaximum(int)
|
2013-12-09 23:09:08 +01:00
|
|
|
*/
|
|
|
|
public int getPlayersMaximum() {
|
2014-01-01 04:21:31 +01:00
|
|
|
if (players == null)
|
|
|
|
throw new IllegalStateException("The player maximum has been hidden.");
|
2013-12-09 23:09:08 +01:00
|
|
|
return (Integer) PLAYERS_MAXIMUM.get(players);
|
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-09 23:09:08 +01:00
|
|
|
/**
|
|
|
|
* Set the displayed maximum number of players.
|
2014-01-01 04:21:31 +01:00
|
|
|
* <p>
|
|
|
|
* The 1.7.2 accepts any value as a player maximum, positive or negative. It even permits a player maximum that
|
|
|
|
* is less than the player count.
|
2013-12-09 23:09:08 +01:00
|
|
|
* @param maximum - maximum player count.
|
|
|
|
*/
|
|
|
|
public void setPlayersMaximum(int maximum) {
|
2014-01-01 04:21:31 +01:00
|
|
|
if (players == null)
|
|
|
|
resetPlayers();
|
2013-12-09 23:09:08 +01:00
|
|
|
PLAYERS_MAXIMUM.set(players, maximum);
|
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2014-01-01 04:21:31 +01:00
|
|
|
/**
|
|
|
|
* Set whether or not the player count and player maximum is visible.
|
|
|
|
* <p>
|
|
|
|
* Note that this may set the current player count and maximum to their respective real values.
|
|
|
|
* @param visible - TRUE if it should be visible, FALSE otherwise.
|
|
|
|
*/
|
|
|
|
public void setPlayersVisible(boolean visible) {
|
|
|
|
if (isPlayersVisible() != visible) {
|
|
|
|
if (visible) {
|
|
|
|
// Recreate the count and maximum
|
|
|
|
Server server = Bukkit.getServer();
|
|
|
|
setPlayersMaximum(server.getMaxPlayers());
|
2022-06-25 17:32:42 +02:00
|
|
|
setPlayersOnline(Bukkit.getOnlinePlayers().size());
|
2014-01-01 04:21:31 +01:00
|
|
|
} else {
|
|
|
|
PLAYERS.set(handle, players = null);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2014-01-01 04:21:31 +01:00
|
|
|
/**
|
|
|
|
* Determine if the player count and maximum is visible.
|
|
|
|
* <p>
|
|
|
|
* If not, the client will display ??? in the same location.
|
|
|
|
* @return TRUE if the player statistics is visible, FALSE otherwise.
|
|
|
|
*/
|
|
|
|
public boolean isPlayersVisible() {
|
|
|
|
return players != null;
|
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-09 23:09:08 +01:00
|
|
|
/**
|
|
|
|
* Retrieve a copy of all the logged in players.
|
2014-01-09 17:18:34 +01:00
|
|
|
* @return Logged in players or an empty list if no player names will be displayed.
|
2013-12-09 23:09:08 +01:00
|
|
|
*/
|
|
|
|
public ImmutableList<WrappedGameProfile> getPlayers() {
|
2014-01-09 16:45:05 +01:00
|
|
|
if (players == null)
|
2014-01-09 17:18:34 +01:00
|
|
|
return ImmutableList.of();
|
2014-01-09 17:25:53 +01:00
|
|
|
Object playerProfiles = PLAYERS_PROFILES.get(players);
|
|
|
|
if (playerProfiles == null)
|
|
|
|
return ImmutableList.of();
|
|
|
|
return ImmutableList.copyOf(PROFILE_CONVERT.getSpecific(playerProfiles));
|
2013-12-09 23:09:08 +01:00
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-09 23:09:08 +01:00
|
|
|
/**
|
|
|
|
* Set the displayed list of logged in players.
|
|
|
|
* @param profile - every logged in player.
|
|
|
|
*/
|
|
|
|
public void setPlayers(Iterable<? extends WrappedGameProfile> profile) {
|
2014-01-09 16:45:05 +01:00
|
|
|
if (players == null)
|
|
|
|
resetPlayers();
|
2017-07-24 20:15:56 +02:00
|
|
|
PLAYERS_PROFILES.set(players, (profile != null) ? PROFILE_CONVERT.getGeneric(profile) : null);
|
2013-12-09 23:09:08 +01:00
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-09 23:09:08 +01:00
|
|
|
/**
|
|
|
|
* Set the displayed lst of logged in players.
|
|
|
|
* @param players - the players to display.
|
|
|
|
*/
|
|
|
|
public void setBukkitPlayers(Iterable<? extends Player> players) {
|
2022-05-26 05:30:08 +02:00
|
|
|
final List<WrappedGameProfile> profiles = new ArrayList<>();
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-09 23:09:08 +01:00
|
|
|
for (Player player : players) {
|
2015-06-19 03:07:32 +02:00
|
|
|
Object profile = ENTITY_HUMAN_PROFILE.get(BukkitUnwrapper.getInstance().unwrapItem(player));
|
2014-04-12 23:56:11 +02:00
|
|
|
profiles.add(WrappedGameProfile.fromHandle(profile));
|
2013-12-09 23:09:08 +01:00
|
|
|
}
|
2015-06-19 03:07:32 +02:00
|
|
|
|
2013-12-09 23:09:08 +01:00
|
|
|
setPlayers(profiles);
|
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-09 23:09:08 +01:00
|
|
|
/**
|
|
|
|
* Retrieve the version name of the current server.
|
|
|
|
* @return The version name.
|
|
|
|
*/
|
|
|
|
public String getVersionName() {
|
|
|
|
return (String) VERSION_NAME.get(version);
|
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-09 23:09:08 +01:00
|
|
|
/**
|
|
|
|
* Set the version name of the current server.
|
|
|
|
* @param name - the new version name.
|
|
|
|
*/
|
|
|
|
public void setVersionName(String name) {
|
|
|
|
VERSION_NAME.set(version, name);
|
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-09 23:09:08 +01:00
|
|
|
/**
|
|
|
|
* Retrieve the protocol number.
|
|
|
|
* @return The protocol.
|
|
|
|
*/
|
|
|
|
public int getVersionProtocol() {
|
|
|
|
return (Integer) VERSION_PROTOCOL.get(version);
|
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-09 23:09:08 +01:00
|
|
|
/**
|
|
|
|
* Set the version protocol
|
|
|
|
* @param protocol - the protocol number.
|
|
|
|
*/
|
|
|
|
public void setVersionProtocol(int protocol) {
|
|
|
|
VERSION_PROTOCOL.set(version, protocol);
|
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-11 03:55:25 +01:00
|
|
|
/**
|
|
|
|
* Retrieve a deep copy of the current wrapper object.
|
|
|
|
* @return The current object.
|
|
|
|
*/
|
|
|
|
public WrappedServerPing deepClone() {
|
|
|
|
WrappedServerPing copy = new WrappedServerPing();
|
|
|
|
WrappedChatComponent motd = getMotD();
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-11 03:55:25 +01:00
|
|
|
copy.setPlayers(getPlayers());
|
|
|
|
copy.setFavicon(getFavicon());
|
|
|
|
copy.setMotD(motd != null ? motd.deepClone() : null);
|
|
|
|
copy.setVersionName(getVersionName());
|
|
|
|
copy.setVersionProtocol(getVersionProtocol());
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2014-01-01 17:16:14 +01:00
|
|
|
if (isPlayersVisible()) {
|
|
|
|
copy.setPlayersMaximum(getPlayersMaximum());
|
|
|
|
copy.setPlayersOnline(getPlayersOnline());
|
|
|
|
} else {
|
|
|
|
copy.setPlayersVisible(false);
|
|
|
|
}
|
2013-12-11 03:55:25 +01:00
|
|
|
return copy;
|
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2014-02-02 16:50:56 +01:00
|
|
|
/**
|
|
|
|
* Retrieve the underlying JSON representation of this server ping.
|
|
|
|
* @return The JSON representation.
|
|
|
|
*/
|
|
|
|
public String toJson() {
|
|
|
|
return (String) GSON_TO_JSON.invoke(PING_GSON.get(null), handle);
|
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2014-04-14 13:56:04 +02:00
|
|
|
@Override
|
|
|
|
public String toString() {
|
|
|
|
return "WrappedServerPing< " + toJson() + ">";
|
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-09 23:09:08 +01:00
|
|
|
/**
|
|
|
|
* Represents a compressed favicon.
|
|
|
|
* @author Kristian
|
|
|
|
*/
|
2013-12-31 23:01:17 +01:00
|
|
|
// Should not have been an inner class ... oh well.
|
2013-12-09 23:09:08 +01:00
|
|
|
public static class CompressedImage {
|
2013-12-31 23:01:17 +01:00
|
|
|
protected volatile String mime;
|
|
|
|
protected volatile byte[] data;
|
|
|
|
protected volatile String encoded;
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-31 23:01:17 +01:00
|
|
|
/**
|
|
|
|
* Represents a compressed image with no content.
|
|
|
|
*/
|
|
|
|
protected CompressedImage() {
|
|
|
|
// Derived class should initialize some of the fields
|
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-09 23:09:08 +01:00
|
|
|
/**
|
|
|
|
* Construct a new compressed image.
|
|
|
|
* @param mime - the mime type.
|
|
|
|
* @param data - the raw compressed image data.
|
|
|
|
*/
|
|
|
|
public CompressedImage(String mime, byte[] data) {
|
|
|
|
this.mime = Preconditions.checkNotNull(mime, "mime cannot be NULL");
|
|
|
|
this.data = Preconditions.checkNotNull(data, "data cannot be NULL");
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieve a compressed image from an input stream.
|
|
|
|
* @param input - the PNG as an input stream.
|
|
|
|
* @return The compressed image.
|
|
|
|
* @throws IOException If we cannot read the input stream.
|
|
|
|
*/
|
|
|
|
public static CompressedImage fromPng(InputStream input) throws IOException {
|
|
|
|
return new CompressedImage("image/png", ByteStreams.toByteArray(input));
|
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-09 23:09:08 +01:00
|
|
|
/**
|
|
|
|
* Retrieve a compressed image from a byte array of a PNG file.
|
|
|
|
* @param data - the file as a byte array.
|
|
|
|
* @return The compressed image.
|
|
|
|
*/
|
|
|
|
public static CompressedImage fromPng(byte[] data) {
|
|
|
|
return new CompressedImage("image/png", data);
|
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-31 23:01:17 +01:00
|
|
|
/**
|
|
|
|
* Retrieve a compressed image from a base-64 encoded PNG file.
|
|
|
|
* @param base64 - the base 64-encoded PNG.
|
|
|
|
* @return The compressed image.
|
|
|
|
*/
|
|
|
|
public static CompressedImage fromBase64Png(String base64) {
|
|
|
|
try {
|
|
|
|
return new EncodedCompressedImage("data:image/png;base64," + base64);
|
|
|
|
} catch (IllegalArgumentException e) {
|
|
|
|
// Remind the caller
|
2015-06-19 03:07:32 +02:00
|
|
|
throw new IllegalArgumentException("Must be a pure base64 encoded string. Cannot be an encoded text.", e);
|
2013-12-31 23:01:17 +01:00
|
|
|
}
|
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-09 23:09:08 +01:00
|
|
|
/**
|
|
|
|
* Retrieve a compressed image from an image.
|
|
|
|
* @param image - the image.
|
2015-06-17 21:08:58 +02:00
|
|
|
* @return A compressed image from an image.
|
2013-12-09 23:09:08 +01:00
|
|
|
* @throws IOException If we were unable to compress the image.
|
|
|
|
*/
|
|
|
|
public static CompressedImage fromPng(RenderedImage image) throws IOException {
|
|
|
|
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
|
|
|
ImageIO.write(image, "png", output);
|
|
|
|
return new CompressedImage("image/png", output.toByteArray());
|
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-09 23:09:08 +01:00
|
|
|
/**
|
|
|
|
* Retrieve a compressed image from an encoded text.
|
|
|
|
* @param text - the encoded text.
|
|
|
|
* @return The corresponding compressed image.
|
|
|
|
*/
|
|
|
|
public static CompressedImage fromEncodedText(String text) {
|
2013-12-31 23:01:17 +01:00
|
|
|
return new EncodedCompressedImage(text);
|
2013-12-09 23:09:08 +01:00
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-09 23:09:08 +01:00
|
|
|
/**
|
|
|
|
* Retrieve the MIME type of the image.
|
2014-11-15 19:02:03 +01:00
|
|
|
* <p>
|
2013-12-09 23:09:08 +01:00
|
|
|
* This is image/png in vanilla Minecraft.
|
|
|
|
* @return The MIME type.
|
|
|
|
*/
|
|
|
|
public String getMime() {
|
|
|
|
return mime;
|
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-09 23:09:08 +01:00
|
|
|
/**
|
|
|
|
* Retrieve a copy of the underlying data array.
|
|
|
|
* @return The underlying compressed image.
|
|
|
|
*/
|
|
|
|
public byte[] getDataCopy() {
|
2013-12-31 23:01:17 +01:00
|
|
|
return getData().clone();
|
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-31 23:01:17 +01:00
|
|
|
/**
|
|
|
|
* Retrieve the underlying data, with no copying.
|
|
|
|
* @return The underlying data.
|
|
|
|
*/
|
|
|
|
protected byte[] getData() {
|
|
|
|
return data;
|
2013-12-09 23:09:08 +01:00
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-09 23:09:08 +01:00
|
|
|
/**
|
|
|
|
* Uncompress and return the stored image.
|
|
|
|
* @return The image.
|
|
|
|
* @throws IOException If the image data could not be decoded.
|
|
|
|
*/
|
|
|
|
public BufferedImage getImage() throws IOException {
|
2013-12-31 23:01:17 +01:00
|
|
|
return ImageIO.read(new ByteArrayInputStream(getData()));
|
2013-12-09 23:09:08 +01:00
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-09 23:09:08 +01:00
|
|
|
/**
|
|
|
|
* Convert the compressed image to encoded text.
|
|
|
|
* @return The encoded text.
|
|
|
|
*/
|
|
|
|
public String toEncodedText() {
|
2013-12-31 23:01:17 +01:00
|
|
|
if (encoded == null) {
|
2016-03-19 21:01:38 +01:00
|
|
|
final ByteBuf buffer = Unpooled.wrappedBuffer(getDataCopy());
|
|
|
|
encoded = "data:" + getMime() + ";base64," +
|
2022-06-25 17:32:42 +02:00
|
|
|
Base64.encode(buffer).toString(StandardCharsets.UTF_8);
|
2013-12-31 23:01:17 +01:00
|
|
|
}
|
2015-06-19 03:07:32 +02:00
|
|
|
|
2013-12-31 23:01:17 +01:00
|
|
|
return encoded;
|
|
|
|
}
|
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-31 23:01:17 +01:00
|
|
|
/**
|
|
|
|
* Represents a compressed image that starts out as an encoded base 64 string.
|
|
|
|
* @author Kristian
|
|
|
|
*/
|
|
|
|
private static class EncodedCompressedImage extends CompressedImage {
|
|
|
|
public EncodedCompressedImage(String encoded) {
|
2014-01-01 16:29:21 +01:00
|
|
|
this.encoded = Preconditions.checkNotNull(encoded, "encoded favicon cannot be NULL");
|
2013-12-31 23:01:17 +01:00
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-31 23:01:17 +01:00
|
|
|
/**
|
|
|
|
* Ensure that we have decoded the content of the encoded text.
|
|
|
|
*/
|
|
|
|
protected void initialize() {
|
|
|
|
if (mime == null || data == null) {
|
|
|
|
decode();
|
|
|
|
}
|
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-31 23:01:17 +01:00
|
|
|
/**
|
|
|
|
* Decode the encoded text.
|
|
|
|
*/
|
|
|
|
protected void decode() {
|
|
|
|
for (String segment : Splitter.on(";").split(encoded)) {
|
|
|
|
if (segment.startsWith("data:")) {
|
|
|
|
this.mime = segment.substring(5);
|
|
|
|
} else if (segment.startsWith("base64,")) {
|
2022-06-25 17:32:42 +02:00
|
|
|
byte[] encoded = segment.substring(7).getBytes(StandardCharsets.UTF_8);
|
2016-03-19 21:01:38 +01:00
|
|
|
ByteBuf decoded = Base64.decode(Unpooled.wrappedBuffer(encoded));
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-31 23:01:17 +01:00
|
|
|
// Read into a byte array
|
|
|
|
byte[] data = new byte[decoded.readableBytes()];
|
|
|
|
decoded.readBytes(data);
|
|
|
|
this.data = data;
|
|
|
|
} else {
|
|
|
|
// We will ignore these segments
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-31 23:01:17 +01:00
|
|
|
@Override
|
|
|
|
protected byte[] getData() {
|
|
|
|
initialize();
|
|
|
|
return super.getData();
|
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-31 23:01:17 +01:00
|
|
|
@Override
|
|
|
|
public String getMime() {
|
|
|
|
initialize();
|
|
|
|
return super.getMime();
|
|
|
|
}
|
2014-11-15 19:02:03 +01:00
|
|
|
|
2013-12-31 23:01:17 +01:00
|
|
|
@Override
|
|
|
|
public String toEncodedText() {
|
|
|
|
return encoded;
|
2013-12-09 23:09:08 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|