ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/ping/ServerPingRecord.java

301 lines
8.0 KiB
Java

package com.comphenix.protocol.wrappers.ping;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Semaphore;
import com.comphenix.protocol.events.InternalStructure;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
import com.comphenix.protocol.utility.MinecraftProtocolVersion;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.wrappers.*;
import com.google.common.collect.ImmutableList;
import org.bukkit.Bukkit;
public final class ServerPingRecord implements ServerPingImpl {
private static Class<?> SERVER_PING;
private static Class<?> PLAYER_SAMPLE_CLASS;
private static Class<?> SERVER_DATA_CLASS;
private static WrappedChatComponent DEFAULT_DESCRIPTION;
private static ConstructorAccessor PING_CTOR;
private static EquivalentConverter<List<WrappedGameProfile>> PROFILE_LIST_CONVERTER;
private static boolean initialized = false;
private static final Object lock = new Object();
private static void initialize() {
if (initialized) {
return;
}
synchronized (lock) {
// may have been initialized while waiting for the lock
if (initialized) {
return;
}
try {
SERVER_PING = MinecraftReflection.getServerPingClass();
PLAYER_SAMPLE_CLASS = MinecraftReflection.getServerPingPlayerSampleClass();
SERVER_DATA_CLASS = MinecraftReflection.getServerPingServerDataClass();
PING_CTOR = Accessors.getConstructorAccessor(SERVER_PING.getConstructors()[0]);
DATA_WRAPPER = AutoWrapper.wrap(ServerData.class, SERVER_DATA_CLASS);
SAMPLE_WRAPPER = AutoWrapper.wrap(PlayerSample.class, PLAYER_SAMPLE_CLASS);
FAVICON_WRAPPER = AutoWrapper.wrap(Favicon.class, MinecraftReflection.getMinecraftClass("network.protocol.status.ServerPing$a"));
PROFILE_LIST_CONVERTER = BukkitConverters.getListConverter(BukkitConverters.getWrappedGameProfileConverter());
DEFAULT_DESCRIPTION = WrappedChatComponent.fromLegacyText("A Minecraft Server");
} catch (Exception ex) {
throw new RuntimeException("Failed to initialize Server Ping", ex);
} finally {
initialized = true;
}
}
}
public static final class PlayerSample {
public int max;
public int online;
public Object sample;
public PlayerSample(int max, int online, Object sample) {
this.max = max;
this.online = online;
this.sample = sample;
}
public PlayerSample() {
this(0, 0, null);
}
}
public static final class ServerData {
public String name;
public int protocol;
public ServerData(String name, int protocol) {
this.name = name;
this.protocol = protocol;
}
public ServerData() {
this("", 0);
}
}
static final byte[] EMPTY_FAVICON = new byte[0];
public static final class Favicon {
public byte[] iconBytes;
public Favicon(byte[] iconBytes) {
this.iconBytes = iconBytes;
}
public Favicon() {
this(EMPTY_FAVICON);
}
}
private static AutoWrapper<PlayerSample> SAMPLE_WRAPPER;
private static AutoWrapper<ServerData> DATA_WRAPPER;
private static AutoWrapper<Favicon> FAVICON_WRAPPER;
private WrappedChatComponent description;
private PlayerSample playerSample;
private ServerData serverData;
private Favicon favicon;
private boolean enforceSafeChat;
private boolean playersVisible = true;
private static ServerData defaultData() {
String name = MinecraftVersion.getCurrentVersion().toString();
int protocol = MinecraftProtocolVersion.getCurrentVersion();
return new ServerData(name, protocol);
}
private static PlayerSample defaultSample() {
int max = Bukkit.getMaxPlayers();
int online = Bukkit.getOnlinePlayers().size();
return new PlayerSample(max, online, null);
}
private static Favicon defaultFavicon() {
return new Favicon();
}
public ServerPingRecord(Object handle) {
initialize();
StructureModifier<Object> modifier = new StructureModifier<>(handle.getClass()).withTarget(handle);
InternalStructure structure = new InternalStructure(handle, modifier);
this.description = structure.getChatComponents().readSafely(0);
StructureModifier<Optional<Object>> optionals = structure.getOptionals(Converters.passthrough(Object.class));
Optional<Object> sampleHandle = optionals.readSafely(0);
this.playerSample = sampleHandle.isPresent() ? SAMPLE_WRAPPER.wrap(sampleHandle.get()) : defaultSample();
Optional<Object> dataHandle = optionals.readSafely(1);
this.serverData = dataHandle.isPresent() ? DATA_WRAPPER.wrap(dataHandle.get()) : defaultData();
Optional<Object> faviconHandle = optionals.readSafely(2);
this.favicon = faviconHandle.isPresent() ? FAVICON_WRAPPER.wrap(faviconHandle.get()) : defaultFavicon();
this.enforceSafeChat = structure.getBooleans().readSafely(0);
}
public ServerPingRecord() {
initialize();
this.description = DEFAULT_DESCRIPTION;
this.playerSample = defaultSample();
this.serverData = defaultData();
this.favicon = defaultFavicon();
}
@Override
public WrappedChatComponent getMotD() {
return description;
}
@Override
public void setMotD(WrappedChatComponent description) {
this.description = description;
}
@Override
public int getPlayersMaximum() {
return playerSample.max;
}
@Override
public void setPlayersMaximum(int maxPlayers) {
playerSample.max = maxPlayers;
}
@Override
public int getPlayersOnline() {
return playerSample.online;
}
@Override
public void setPlayersOnline(int onlineCount) {
playerSample.online = onlineCount;
}
@Override
public ImmutableList<WrappedGameProfile> getPlayers() {
if (playerSample.sample == null) {
return ImmutableList.of();
}
List<WrappedGameProfile> list = PROFILE_LIST_CONVERTER.getSpecific(playerSample.sample);
if (list == null) {
return ImmutableList.of();
}
return ImmutableList.copyOf(list);
}
@Override
public void setPlayers(Iterable<? extends WrappedGameProfile> playerSample) {
if (playerSample == null) {
this.playerSample.sample = null;
return;
}
List<WrappedGameProfile> list = Converters.toList(playerSample);
this.playerSample.sample = PROFILE_LIST_CONVERTER.getGeneric(list);
}
@Override
public String getVersionName() {
return serverData.name;
}
@Override
public void setVersionName(String versionName) {
serverData.name = versionName;
}
@Override
public int getVersionProtocol() {
return serverData.protocol;
}
@Override
public void setVersionProtocol(int protocolVersion) {
serverData.protocol = protocolVersion;
}
@Override
public String getFavicon() {
return new String(favicon.iconBytes, StandardCharsets.UTF_8);
}
@Override
public void setFavicon(String favicon) {
this.favicon.iconBytes = favicon.getBytes(StandardCharsets.UTF_8);
}
@Override
public boolean isEnforceSecureChat() {
return enforceSafeChat;
}
@Override
public void setEnforceSecureChat(boolean safeChat) {
this.enforceSafeChat = safeChat;
}
@Override
public void resetPlayers() {
this.playerSample = defaultSample();
}
@Override
public void resetVersion() {
this.serverData = defaultData();
}
@Override
public boolean arePlayersVisible() {
return playersVisible;
}
@Override
public void setPlayersVisible(boolean visible) {
this.playersVisible = visible;
}
@Override
public Object getHandle() {
WrappedChatComponent wrappedDescription = description != null ? description : DEFAULT_DESCRIPTION;
Object descHandle = wrappedDescription.getHandle();
Optional<Object> playersHandle = Optional.ofNullable(playerSample != null ? SAMPLE_WRAPPER.unwrap(playerSample) : null);
Optional<Object> versionHandle = Optional.ofNullable(serverData != null ? DATA_WRAPPER.unwrap(serverData) : null);
Optional<Object> favHandle = Optional.ofNullable(favicon != null ? FAVICON_WRAPPER.unwrap(favicon) : null);
return PING_CTOR.invoke(descHandle, playersHandle, versionHandle, favHandle, enforceSafeChat);
}
}