@Override public void setTabHeader(BaseComponent[] header, BaseComponent[] footer) { if (pendingConnection.getVersion() >= ProtocolConstants.MINECRAFT_1_8) { unsafe() .sendPacket( new PlayerListHeaderFooter( (header != null) ? ComponentSerializer.toString(header) : EMPTY_TEXT, (footer != null) ? ComponentSerializer.toString(footer) : EMPTY_TEXT)); } }
@Override public void sendMessage(ChatMessageType position, BaseComponent message) { // Action bar doesn't display the new JSON formattings, legacy works - send it using this for // now if (position == ChatMessageType.ACTION_BAR && pendingConnection.getVersion() >= ProtocolConstants.MINECRAFT_1_8) { sendMessage( position, ComponentSerializer.toString(new TextComponent(TextComponent.toLegacyText(message)))); } else { sendMessage(position, ComponentSerializer.toString(message)); } }
public synchronized void disconnect0(BaseComponent... reason) { if (ch.getHandle().isActive()) { bungee .getLogger() .log( Level.INFO, "[" + getName() + "] disconnected with: " + BaseComponent.toLegacyText(reason)); unsafe().sendPacket(new Kick(ComponentSerializer.toString(reason))); ch.close(); if (server != null) { server.disconnect("Quitting"); } } }
@Override public void execute(CommandSender sender, String[] args) { if (args.length == 0) { sender.sendMessage(ChatColor.RED + "You must supply a message."); } else { String message = Joiner.on(' ').join(args); try { ProxyServer.getInstance().broadcast(ComponentSerializer.parse(message)); } catch (Exception e) { sender.sendMessage( new ComponentBuilder( "An error occurred while parsing your message. (Hover for details)") .color(ChatColor.RED) .underlined(true) .event( new HoverEvent( HoverEvent.Action.SHOW_TEXT, new ComponentBuilder(e.getMessage()).color(ChatColor.RED).create())) .create()); } } }
@Override public void sendMessage(BaseComponent message) { unsafe().sendPacket(new Chat(ComponentSerializer.toString(message))); }
@RequiredArgsConstructor public final class UserConnection implements ProxiedPlayer { /*========================================================================*/ @NonNull private final ProxyServer bungee; @NonNull private final ChannelWrapper ch; @Getter @NonNull private final String name; @Getter private final InitialHandler pendingConnection; /*========================================================================*/ @Getter @Setter private ServerConnection server; @Getter @Setter private boolean dimensionChange = true; @Getter private final Collection<ServerInfo> pendingConnects = new HashSet<>(); /*========================================================================*/ @Getter @Setter private int sentPingId; @Getter @Setter private long sentPingTime; @Getter @Setter private int ping = 100; @Getter @Setter private ServerInfo reconnectServer; @Getter private TabList tabListHandler; @Getter @Setter private int gamemode; @Getter private int compressionThreshold = -1; /*========================================================================*/ private final Collection<String> groups = new CaseInsensitiveSet(); private final Collection<String> permissions = new CaseInsensitiveSet(); /*========================================================================*/ @Getter @Setter private int clientEntityId; @Getter @Setter private int serverEntityId; @Getter private ClientSettings settings; @Getter private final Scoreboard serverSentScoreboard = new Scoreboard(); /*========================================================================*/ @Getter private String displayName; @Getter private EntityMap entityRewrite; private Locale locale; /*========================================================================*/ @Getter @Setter private ForgeClientHandler forgeClientHandler; @Getter @Setter private ForgeServerHandler forgeServerHandler; /*========================================================================*/ private final Unsafe unsafe = new Unsafe() { @Override public void sendPacket(DefinedPacket packet) { ch.write(packet); } }; public void init() { this.entityRewrite = EntityMap.getEntityMap(getPendingConnection().getVersion()); this.displayName = name; // Blame Mojang for this one /*switch ( getPendingConnection().getListener().getTabListType() ) { case "GLOBAL": tabListHandler = new Global( this ); break; case "SERVER": tabListHandler = new ServerUnique( this ); break; default: tabListHandler = new GlobalPing( this ); break; }*/ tabListHandler = new ServerUnique(this); Collection<String> g = bungee.getConfigurationAdapter().getGroups(name); g.addAll(bungee.getConfigurationAdapter().getGroups(getUniqueId().toString())); for (String s : g) { addGroups(s); } forgeClientHandler = new ForgeClientHandler(this); // Set whether the connection has a 1.8 FML marker in the handshake. forgeClientHandler.setFmlTokenInHandshake( this.getPendingConnection() .getExtraDataInHandshake() .contains(ForgeConstants.FML_HANDSHAKE_TOKEN)); } public void sendPacket(PacketWrapper packet) { ch.write(packet); } @Deprecated public boolean isActive() { return !ch.isClosed(); } @Override public void setDisplayName(String name) { Preconditions.checkNotNull(name, "displayName"); Preconditions.checkArgument( name.length() <= 16, "Display name cannot be longer than 16 characters"); displayName = name; } @Override public void connect(ServerInfo target) { connect(target, null); } @Override public void connect(ServerInfo target, Callback<Boolean> callback) { connect(target, callback, false); } void sendDimensionSwitch() { dimensionChange = true; unsafe().sendPacket(PacketConstants.DIM1_SWITCH); unsafe().sendPacket(PacketConstants.DIM2_SWITCH); } public void connectNow(ServerInfo target) { sendDimensionSwitch(); connect(target); } public void connect(ServerInfo info, final Callback<Boolean> callback, final boolean retry) { Preconditions.checkNotNull(info, "info"); ServerConnectEvent event = new ServerConnectEvent(this, info); if (bungee.getPluginManager().callEvent(event).isCancelled()) { return; } final BungeeServerInfo target = (BungeeServerInfo) event.getTarget(); // Update in case the event changed target if (getServer() != null && Objects.equal(getServer().getInfo(), target)) { sendMessage(bungee.getTranslation("already_connected")); return; } if (pendingConnects.contains(target)) { sendMessage(bungee.getTranslation("already_connecting")); return; } pendingConnects.add(target); ChannelInitializer initializer = new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { PipelineUtils.BASE.initChannel(ch); ch.pipeline() .addAfter( PipelineUtils.FRAME_DECODER, PipelineUtils.PACKET_DECODER, new MinecraftDecoder( Protocol.HANDSHAKE, false, getPendingConnection().getVersion())); ch.pipeline() .addAfter( PipelineUtils.FRAME_PREPENDER, PipelineUtils.PACKET_ENCODER, new MinecraftEncoder( Protocol.HANDSHAKE, false, getPendingConnection().getVersion())); ch.pipeline() .get(HandlerBoss.class) .setHandler(new ServerConnector(bungee, UserConnection.this, target)); } }; ChannelFutureListener listener = new ChannelFutureListener() { @Override @SuppressWarnings("ThrowableResultIgnored") public void operationComplete(ChannelFuture future) throws Exception { if (callback != null) { callback.done(future.isSuccess(), future.cause()); } if (!future.isSuccess()) { future.channel().close(); pendingConnects.remove(target); ServerInfo def = ProxyServer.getInstance() .getServers() .get(getPendingConnection().getListener().getFallbackServer()); if (retry && target != def && (getServer() == null || def != getServer().getInfo())) { sendMessage(bungee.getTranslation("fallback_lobby")); connect(def, null, false); } else { if (dimensionChange) { disconnect( bungee.getTranslation("fallback_kick", future.cause().getClass().getName())); } else { sendMessage( bungee.getTranslation("fallback_kick", future.cause().getClass().getName())); } } } } }; Bootstrap b = new Bootstrap() .channel(PipelineUtils.getChannel()) .group(ch.getHandle().eventLoop()) .handler(initializer) .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) // TODO: Configurable .remoteAddress(target.getAddress()); // Windows is bugged, multi homed users will just have to live with random connecting IPs if (getPendingConnection().getListener().isSetLocalAddress() && !PlatformDependent.isWindows()) { b.localAddress(getPendingConnection().getListener().getHost().getHostString(), 0); } b.connect().addListener(listener); } @Override public void disconnect(String reason) { disconnect0(TextComponent.fromLegacyText(reason)); } @Override public void disconnect(BaseComponent... reason) { disconnect0(reason); } @Override public void disconnect(BaseComponent reason) { disconnect0(reason); } public void disconnect0(final BaseComponent... reason) { if (!ch.isClosed()) { bungee .getLogger() .log( Level.INFO, "[{0}] disconnected with: {1}", new Object[] {getName(), BaseComponent.toLegacyText(reason)}); // Why do we have to delay this you might ask? Well the simple reason is MOJANG. // Despite many a bug report posted, ever since the 1.7 protocol rewrite, the client STILL has // a race condition upon switching protocols. // As such, despite the protocol switch packets already having been sent, there is the // possibility of a client side exception // To help combat this we will wait half a second before actually sending the disconnected // packet so that whoever is on the other // end has a somewhat better chance of receiving the proper packet. ch.getHandle() .eventLoop() .schedule( new Runnable() { @Override public void run() { unsafe().sendPacket(new Kick(ComponentSerializer.toString(reason))); ch.close(); } }, 500, TimeUnit.MILLISECONDS); if (server != null) { server.disconnect("Quitting"); } } } @Override public void chat(String message) { Preconditions.checkState(server != null, "Not connected to server"); server.getCh().write(new Chat(message)); } @Override public void sendMessage(String message) { sendMessage(TextComponent.fromLegacyText(message)); } @Override public void sendMessages(String... messages) { for (String message : messages) { sendMessage(message); } } @Override public void sendMessage(BaseComponent... message) { sendMessage(ChatMessageType.CHAT, message); } @Override public void sendMessage(BaseComponent message) { sendMessage(ChatMessageType.CHAT, message); } private void sendMessage(ChatMessageType position, String message) { unsafe().sendPacket(new Chat(message, (byte) position.ordinal())); } @Override public void sendMessage(ChatMessageType position, BaseComponent... message) { // Action bar doesn't display the new JSON formattings, legacy works - send it using this for // now if (position == ChatMessageType.ACTION_BAR && pendingConnection.getVersion() >= ProtocolConstants.MINECRAFT_1_8) { sendMessage( position, ComponentSerializer.toString(new TextComponent(TextComponent.toLegacyText(message)))); } else { sendMessage(position, ComponentSerializer.toString(message)); } } @Override public void sendMessage(ChatMessageType position, BaseComponent message) { // Action bar doesn't display the new JSON formattings, legacy works - send it using this for // now if (position == ChatMessageType.ACTION_BAR && pendingConnection.getVersion() >= ProtocolConstants.MINECRAFT_1_8) { sendMessage( position, ComponentSerializer.toString(new TextComponent(TextComponent.toLegacyText(message)))); } else { sendMessage(position, ComponentSerializer.toString(message)); } } @Override public void sendData(String channel, byte[] data) { unsafe().sendPacket(new PluginMessage(channel, data, forgeClientHandler.isForgeUser())); } @Override public InetSocketAddress getAddress() { return (InetSocketAddress) ch.getHandle().remoteAddress(); } @Override public Collection<String> getGroups() { return Collections.unmodifiableCollection(groups); } @Override public void addGroups(String... groups) { for (String group : groups) { this.groups.add(group); for (String permission : bungee.getConfigurationAdapter().getPermissions(group)) { setPermission(permission, true); } } } @Override public void removeGroups(String... groups) { for (String group : groups) { this.groups.remove(group); for (String permission : bungee.getConfigurationAdapter().getPermissions(group)) { setPermission(permission, false); } } } @Override public boolean hasPermission(String permission) { return bungee .getPluginManager() .callEvent(new PermissionCheckEvent(this, permission, permissions.contains(permission))) .hasPermission(); } @Override public void setPermission(String permission, boolean value) { if (value) { permissions.add(permission); } else { permissions.remove(permission); } } @Override public Collection<String> getPermissions() { return Collections.unmodifiableCollection(permissions); } @Override public String toString() { return name; } @Override public Unsafe unsafe() { return unsafe; } @Override public String getUUID() { return getPendingConnection().getUUID(); } @Override public UUID getUniqueId() { return getPendingConnection().getUniqueId(); } public void setSettings(ClientSettings settings) { this.settings = settings; this.locale = null; } @Override public Locale getLocale() { return (locale == null && settings != null) ? locale = Locale.forLanguageTag(settings.getLocale().replaceAll("_", "-")) : locale; } @Override public boolean isForgeUser() { return forgeClientHandler.isForgeUser(); } @Override public Map<String, String> getModList() { if (forgeClientHandler.getClientModList() == null) { // Return an empty map, rather than a null, if the client hasn't got any mods, // or is yet to complete a handshake. return ImmutableMap.of(); } return ImmutableMap.copyOf(forgeClientHandler.getClientModList()); } private static final String EMPTY_TEXT = ComponentSerializer.toString(new TextComponent("")); @Override public void setTabHeader(BaseComponent header, BaseComponent footer) { if (pendingConnection.getVersion() >= ProtocolConstants.MINECRAFT_1_8) { unsafe() .sendPacket( new PlayerListHeaderFooter( (header != null) ? ComponentSerializer.toString(header) : EMPTY_TEXT, (footer != null) ? ComponentSerializer.toString(footer) : EMPTY_TEXT)); } } @Override public void setTabHeader(BaseComponent[] header, BaseComponent[] footer) { if (pendingConnection.getVersion() >= ProtocolConstants.MINECRAFT_1_8) { unsafe() .sendPacket( new PlayerListHeaderFooter( (header != null) ? ComponentSerializer.toString(header) : EMPTY_TEXT, (footer != null) ? ComponentSerializer.toString(footer) : EMPTY_TEXT)); } } @Override public void resetTabHeader() { // Mojang did not add a way to remove the header / footer completely, we can only set it to // empty setTabHeader((BaseComponent) null, null); } @Override public void sendTitle(Title title) { title.send(this); } public String getExtraDataInHandshake() { return this.getPendingConnection().getExtraDataInHandshake(); } public void setCompressionThreshold(int compressionThreshold) { if (ch.getHandle().isActive() && this.compressionThreshold == -1 && getPendingConnection().getVersion() >= ProtocolConstants.MINECRAFT_1_8) { this.compressionThreshold = compressionThreshold; unsafe.sendPacket(new SetCompression(compressionThreshold)); ch.setCompressionThreshold(compressionThreshold); } } }