diff --git a/api/src/main/java/com/velocitypowered/api/event/connection/package-info.java b/api/src/main/java/com/velocitypowered/api/event/connection/package-info.java index 44e41f8d5..8f02c2b15 100644 --- a/api/src/main/java/com/velocitypowered/api/event/connection/package-info.java +++ b/api/src/main/java/com/velocitypowered/api/event/connection/package-info.java @@ -1,4 +1,4 @@ /** - * Provides events for handling incoming connections to the proxy and loigns. + * Provides events for handling incoming connections to the proxy and logins. */ package com.velocitypowered.api.event.connection; \ No newline at end of file diff --git a/api/src/main/java/com/velocitypowered/api/event/player/PlayerSettingsChangedEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/PlayerSettingsChangedEvent.java new file mode 100644 index 000000000..4bb433ec2 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/player/PlayerSettingsChangedEvent.java @@ -0,0 +1,24 @@ +package com.velocitypowered.api.event.player; + +import com.google.common.base.Preconditions; +import com.velocitypowered.api.playersettings.PlayerSettings; +import com.velocitypowered.api.proxy.Player; + +public class PlayerSettingsChangedEvent { + private final Player player; + private final PlayerSettings playerSettings; + + public PlayerSettingsChangedEvent(Player player, PlayerSettings playerSettings) { + this.player = Preconditions.checkNotNull(player, "player"); + this.playerSettings = Preconditions.checkNotNull(playerSettings, "playerSettings"); + } + + public Player getPlayer() { + return player; + } + + //New settings + public PlayerSettings getPlayerSettings() { + return playerSettings; + } +} diff --git a/api/src/main/java/com/velocitypowered/api/playersettings/PlayerSettings.java b/api/src/main/java/com/velocitypowered/api/playersettings/PlayerSettings.java new file mode 100644 index 000000000..0c04ac44d --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/playersettings/PlayerSettings.java @@ -0,0 +1,29 @@ +package com.velocitypowered.api.playersettings; + +import java.util.Locale; + +public interface PlayerSettings { + + Locale getLocale(); + + byte getViewDistance(); + + ChatMode getChatMode(); + + boolean hasChatColors(); + + SkinParts getSkinParts(); + + MainHand getMainHand(); + + public enum ChatMode { + SHOWN, + COMMANDS_ONLY, + HIDDEN + } + + public enum MainHand { + LEFT, + RIGHT + } +} diff --git a/api/src/main/java/com/velocitypowered/api/playersettings/SkinParts.java b/api/src/main/java/com/velocitypowered/api/playersettings/SkinParts.java new file mode 100644 index 000000000..4ec3d305b --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/playersettings/SkinParts.java @@ -0,0 +1,39 @@ +package com.velocitypowered.api.playersettings; + +public class SkinParts { + + static final SkinParts SKIN_SHOW_ALL = new SkinParts((byte) 127); + private final byte bitmask; + + public SkinParts(byte skinBitmask) { + this.bitmask = skinBitmask; + } + + public boolean hasCape() { + return ((bitmask >> 0) & 1) == 1; + } + + public boolean hasJacket() { + return ((bitmask >> 1) & 1) == 1; + } + + public boolean hasLeftSleeve() { + return ((bitmask >> 2) & 1) == 1; + } + + public boolean hasRightSleeve() { + return ((bitmask >> 3) & 1) == 1; + } + + public boolean hasLeftPants() { + return ((bitmask >> 4) & 1) == 1; + } + + public boolean hasRightPants() { + return ((bitmask >> 5) & 1) == 1; + } + + public boolean hasHat() { + return ((bitmask >> 6) & 1) == 1; + } +} diff --git a/api/src/main/java/com/velocitypowered/api/proxy/Player.java b/api/src/main/java/com/velocitypowered/api/proxy/Player.java index ecd40dc2d..7e72b5d9b 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/Player.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/Player.java @@ -1,6 +1,7 @@ package com.velocitypowered.api.proxy; import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.playersettings.PlayerSettings; import com.velocitypowered.api.proxy.messages.ChannelMessageSink; import com.velocitypowered.api.proxy.messages.ChannelMessageSource; import com.velocitypowered.api.proxy.server.ServerInfo; @@ -32,7 +33,9 @@ public interface Player extends CommandSource, InboundConnection, ChannelMessage * @return an {@link Optional} the server that the player is connected to, which may be empty */ Optional getCurrentServer(); - + + PlayerSettings getPlayerSettings(); + /** * Returns the current player's ping * @return the player's ping or -1 if ping information is currently unknown diff --git a/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java b/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java index f48fbc3a8..525cd2bc9 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java @@ -18,9 +18,9 @@ public class ServerPing { private final Component description; private final @Nullable Favicon favicon; - public ServerPing(@NonNull Version version, @NonNull Players players, @NonNull Component description, @Nullable Favicon favicon) { + public ServerPing(Version version, @Nullable Players players, Component description, @Nullable Favicon favicon) { this.version = Preconditions.checkNotNull(version, "version"); - this.players = Preconditions.checkNotNull(players, "players"); + this.players = players; this.description = Preconditions.checkNotNull(description, "description"); this.favicon = favicon; } @@ -29,8 +29,8 @@ public class ServerPing { return version; } - public Players getPlayers() { - return players; + public Optional getPlayers() { + return Optional.ofNullable(players); } public Component getDescription() { @@ -76,6 +76,7 @@ public class ServerPing { private final List samplePlayers = new ArrayList<>(); private Component description; private Favicon favicon; + private boolean nullOutPlayers; private Builder() { @@ -106,6 +107,11 @@ public class ServerPing { return this; } + public Builder nullPlayers() { + this.nullOutPlayers = true; + return this; + } + public Builder description(Component description) { this.description = Preconditions.checkNotNull(description, "description"); return this; @@ -117,7 +123,7 @@ public class ServerPing { } public ServerPing build() { - return new ServerPing(version, new Players(onlinePlayers, maximumPlayers, samplePlayers), description, favicon); + return new ServerPing(version, nullOutPlayers ? null : new Players(onlinePlayers, maximumPlayers, samplePlayers), description, favicon); } public Version getVersion() { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java index f1acfc652..95dc18b00 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java @@ -12,16 +12,12 @@ import com.velocitypowered.proxy.protocol.remap.EntityIdRemapper; import com.velocitypowered.proxy.protocol.util.PluginMessageUtil; import com.velocitypowered.proxy.util.ThrowableUtils; import io.netty.buffer.ByteBuf; -import io.netty.channel.EventLoop; import net.kyori.text.TextComponent; import net.kyori.text.format.TextColor; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.util.*; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.TimeUnit; /** * Handles communication with the connected Minecraft client. This is effectively the primary nerve center that @@ -68,7 +64,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { } if (packet instanceof ClientSettings) { - player.setClientSettings((ClientSettings) packet); + player.setPlayerSettings((ClientSettings) packet); // forward it on } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientSettingsWrapper.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientSettingsWrapper.java new file mode 100644 index 000000000..854e6c7d8 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientSettingsWrapper.java @@ -0,0 +1,56 @@ +package com.velocitypowered.proxy.connection.client; + +import com.velocitypowered.api.playersettings.PlayerSettings; +import com.velocitypowered.api.playersettings.SkinParts; +import com.velocitypowered.proxy.protocol.packet.ClientSettings; +import java.util.Locale; + +public class ClientSettingsWrapper implements PlayerSettings { + + public static PlayerSettings DEFAULT = new ClientSettingsWrapper(new ClientSettings("en_US", (byte) 10, 0, true, (short)127, 1)); + + private final ClientSettings settings; + private final SkinParts parts; + private Locale locale = null; + + public ClientSettingsWrapper(ClientSettings settings) { + this.settings = settings; + this.parts = new SkinParts((byte) settings.getSkinParts()); + } + + @Override + public Locale getLocale() { + return locale == null ? locale = Locale.forLanguageTag(settings.getLocale().replaceAll("_", "-")) : locale; //Will throw error if locale not found + } + + @Override + public byte getViewDistance() { + return settings.getViewDistance(); + } + + @Override + public ChatMode getChatMode() { + int chat = settings.getChatVisibility(); + if (chat < 0 || chat > 2) { + return ChatMode.SHOWN; + } + return ChatMode.values()[chat]; + } + + @Override + public boolean hasChatColors() { + return settings.isChatColors(); + } + + @Override + public SkinParts getSkinParts() { + return parts; + } + + @Override + public MainHand getMainHand() { + return settings.getMainHand() == 1 ? MainHand.RIGHT : MainHand.LEFT; + } + + +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index b477a07eb..7c5d19db6 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -2,9 +2,11 @@ package com.velocitypowered.proxy.connection.client; import com.google.common.base.Preconditions; import com.google.gson.JsonObject; +import com.velocitypowered.api.event.player.PlayerSettingsChangedEvent; import com.velocitypowered.api.event.player.ServerPreConnectEvent; import com.velocitypowered.api.permission.PermissionFunction; import com.velocitypowered.api.permission.PermissionProvider; +import com.velocitypowered.api.playersettings.PlayerSettings; import com.velocitypowered.api.proxy.ConnectionRequestBuilder; import com.velocitypowered.api.proxy.ServerConnection; import com.velocitypowered.api.proxy.messages.ChannelIdentifier; @@ -56,7 +58,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { private VelocityServerConnection connectedServer; private ClientSettings clientSettings; private VelocityServerConnection connectionInFlight; - + private PlayerSettings settings; + public ConnectedPlayer(GameProfile profile, MinecraftConnection connection, InetSocketAddress virtualHost) { this.profile = profile; this.connection = connection; @@ -95,6 +98,15 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { this.ping = ping; } + public PlayerSettings getPlayerSettings() { + return settings == null ? ClientSettingsWrapper.DEFAULT : this.settings; + } + + public void setPlayerSettings(ClientSettings settings) { + this.settings = new ClientSettingsWrapper(settings); + VelocityServer.getServer().getEventManager().fireAndForget(new PlayerSettingsChangedEvent(this, this.settings)); + } + @Override public InetSocketAddress getRemoteAddress() { return (InetSocketAddress) connection.getChannel().remoteAddress(); @@ -172,10 +184,6 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { return clientSettings; } - public void setClientSettings(ClientSettings clientSettings) { - this.clientSettings = clientSettings; - } - public void handleConnectionException(ServerInfo info, Throwable throwable) { String error = ThrowableUtils.briefDescription(throwable); String userMessage; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientSettings.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientSettings.java index cd5a92bab..0637cf3fe 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientSettings.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientSettings.java @@ -6,6 +6,7 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; public class ClientSettings implements MinecraftPacket { + private String locale; private byte viewDistance; private int chatVisibility; @@ -13,6 +14,18 @@ public class ClientSettings implements MinecraftPacket { private short skinParts; private int mainHand; + public ClientSettings() { + } + + public ClientSettings(String locale, byte viewDistance, int chatVisibility, boolean chatColors, short skinParts, int mainHand) { + this.locale = locale; + this.viewDistance = viewDistance; + this.chatVisibility = chatVisibility; + this.chatColors = chatColors; + this.skinParts = skinParts; + this.mainHand = mainHand; + } + public String getLocale() { return locale; } @@ -63,14 +76,14 @@ public class ClientSettings implements MinecraftPacket { @Override public String toString() { - return "ClientSettings{" + - "locale='" + locale + '\'' + - ", viewDistance=" + viewDistance + - ", chatVisibility=" + chatVisibility + - ", chatColors=" + chatColors + - ", skinParts=" + skinParts + - ", mainHand=" + mainHand + - '}'; + return "ClientSettings{" + + "locale='" + locale + '\'' + + ", viewDistance=" + viewDistance + + ", chatVisibility=" + chatVisibility + + ", chatColors=" + chatColors + + ", skinParts=" + skinParts + + ", mainHand=" + mainHand + + '}'; } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyPingResponse.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyPingResponse.java index 2b733f5ef..36cbe7845 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyPingResponse.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyPingResponse.java @@ -1,9 +1,11 @@ package com.velocitypowered.proxy.protocol.packet; +import com.google.common.collect.ImmutableList; import com.velocitypowered.api.proxy.server.ServerPing; import net.kyori.text.serializer.ComponentSerializers; public class LegacyPingResponse { + private static final ServerPing.Players FAKE_PLAYERS = new ServerPing.Players(0, 0, ImmutableList.of()); private int protocolVersion; private String serverVersion; private String motd; @@ -76,7 +78,7 @@ public class LegacyPingResponse { return new LegacyPingResponse(ping.getVersion().getProtocol(), ping.getVersion().getName(), ComponentSerializers.LEGACY.serialize(ping.getDescription()), - ping.getPlayers().getOnline(), - ping.getPlayers().getMax()); + ping.getPlayers().orElse(FAKE_PLAYERS).getOnline(), + ping.getPlayers().orElse(FAKE_PLAYERS).getMax()); } }