mirror of
https://github.com/SpigotMC/BungeeCord.git
synced 2024-12-24 17:47:41 +01:00
Merge API into master. This marks the dawn of a new Bungee era, but must be regarded as UNSTABLE.
This commit is contained in:
commit
def2269b6e
30
.gitignore
vendored
30
.gitignore
vendored
@ -1,31 +1,31 @@
|
||||
# Eclipse stuff
|
||||
/.classpath
|
||||
/.project
|
||||
/.settings
|
||||
.classpath/
|
||||
.project/
|
||||
.settings/
|
||||
|
||||
# netbeans
|
||||
/nbproject
|
||||
/nbactions.xml
|
||||
/nb-configuration.xml
|
||||
nbproject/
|
||||
nbactions.xml
|
||||
nb-configuration.xml
|
||||
|
||||
# we use maven!
|
||||
/build.xml
|
||||
build.xml
|
||||
|
||||
# maven
|
||||
/target
|
||||
/dependency-reduced-pom.xml
|
||||
target/
|
||||
dependency-reduced-pom.xml
|
||||
|
||||
# vim
|
||||
.*.sw[a-p]
|
||||
|
||||
# various other potential build files
|
||||
/build
|
||||
/bin
|
||||
/dist
|
||||
/manifest.mf
|
||||
build/
|
||||
bin/
|
||||
dist/
|
||||
manifest.mf
|
||||
|
||||
# Mac filesystem dust
|
||||
/.DS_Store
|
||||
.DS_Store/
|
||||
|
||||
# intellij
|
||||
*.iml
|
||||
@ -34,5 +34,5 @@
|
||||
.idea/
|
||||
|
||||
# other files
|
||||
/*log*
|
||||
*.log*
|
||||
*.yml
|
||||
|
5
.travis.yml
Normal file
5
.travis.yml
Normal file
@ -0,0 +1,5 @@
|
||||
language: java
|
||||
jdk:
|
||||
- openjdk7
|
||||
notifications:
|
||||
email: false
|
35
api/pom.xml
Normal file
35
api/pom.xml
Normal file
@ -0,0 +1,35 @@
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-parent</artifactId>
|
||||
<version>1.4.7-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-api</artifactId>
|
||||
<version>1.4.7-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>BungeeCord-API</name>
|
||||
<description>API implemented by the Elastic Portal Suite</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>13.0.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.yaml</groupId>
|
||||
<artifactId>snakeyaml</artifactId>
|
||||
<version>1.11</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
19
api/src/main/java/net/md_5/bungee/api/Callback.java
Normal file
19
api/src/main/java/net/md_5/bungee/api/Callback.java
Normal file
@ -0,0 +1,19 @@
|
||||
package net.md_5.bungee.api;
|
||||
|
||||
/**
|
||||
* Represents a method which may be called once a result has been computed
|
||||
* asynchronously.
|
||||
*
|
||||
* @param <V> the type of result
|
||||
*/
|
||||
public interface Callback<V>
|
||||
{
|
||||
|
||||
/**
|
||||
* Called when the result is done.
|
||||
*
|
||||
* @param result the result of the computation
|
||||
* @param error the error(s) that occurred, if any
|
||||
*/
|
||||
public void done(V result, Throwable error);
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package net.md_5.bungee;
|
||||
package net.md_5.bungee.api;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
59
api/src/main/java/net/md_5/bungee/api/CommandSender.java
Normal file
59
api/src/main/java/net/md_5/bungee/api/CommandSender.java
Normal file
@ -0,0 +1,59 @@
|
||||
package net.md_5.bungee.api;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public interface CommandSender
|
||||
{
|
||||
|
||||
/**
|
||||
* Get the unique name of this command sender.
|
||||
*
|
||||
* @return the senders username
|
||||
*/
|
||||
public String getName();
|
||||
|
||||
/**
|
||||
* Send a message to this sender.
|
||||
*
|
||||
* @param message the message to send
|
||||
*/
|
||||
public void sendMessage(String message);
|
||||
|
||||
/**
|
||||
* Get all groups this user is part of. This returns an unmodifiable
|
||||
* collection.
|
||||
*
|
||||
* @return the users groups
|
||||
*/
|
||||
public Collection<String> getGroups();
|
||||
|
||||
/**
|
||||
* Adds groups to a this user for the current session only.
|
||||
*
|
||||
* @param groups the groups to add
|
||||
*/
|
||||
public void addGroups(String... groups);
|
||||
|
||||
/**
|
||||
* Remove groups from this user for the current session only.
|
||||
*
|
||||
* @param groups the groups to remove
|
||||
*/
|
||||
public void removeGroups(String... groups);
|
||||
|
||||
/**
|
||||
* Checks if this user has the specified permission node.
|
||||
*
|
||||
* @param permission the node to check
|
||||
* @return whether they have this node
|
||||
*/
|
||||
public boolean hasPermission(String permission);
|
||||
|
||||
/**
|
||||
* Set a permission node for this user.
|
||||
*
|
||||
* @param permission the node to set
|
||||
* @param value the value of the node
|
||||
*/
|
||||
public void setPermission(String permission, boolean value);
|
||||
}
|
177
api/src/main/java/net/md_5/bungee/api/ProxyServer.java
Normal file
177
api/src/main/java/net/md_5/bungee/api/ProxyServer.java
Normal file
@ -0,0 +1,177 @@
|
||||
package net.md_5.bungee.api;
|
||||
|
||||
import net.md_5.bungee.api.plugin.PluginManager;
|
||||
import com.google.common.base.Preconditions;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Logger;
|
||||
import lombok.Getter;
|
||||
import net.md_5.bungee.api.config.ConfigurationAdapter;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.connection.Server;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
|
||||
public abstract class ProxyServer
|
||||
{
|
||||
|
||||
@Getter
|
||||
private static ProxyServer instance;
|
||||
|
||||
/**
|
||||
* Sets the proxy instance. This method may only be called once per an
|
||||
* application.
|
||||
*
|
||||
* @param instance the new instance to set
|
||||
*/
|
||||
public static void setInstance(ProxyServer instance)
|
||||
{
|
||||
Preconditions.checkNotNull(instance, "instance");
|
||||
Preconditions.checkArgument(ProxyServer.instance == null, "Instance already set");
|
||||
ProxyServer.instance = instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the currently running proxy software.
|
||||
*
|
||||
* @return the name of this instance
|
||||
*/
|
||||
public abstract String getName();
|
||||
|
||||
/**
|
||||
* Gets the version of the currently running proxy software.
|
||||
*
|
||||
* @return the version of this instance
|
||||
*/
|
||||
public abstract String getVersion();
|
||||
|
||||
/**
|
||||
* Gets the main logger which can be used as a suitable replacement for
|
||||
* {@link System#out} and {@link System#err}.
|
||||
*
|
||||
* @return the {@link Logger} instance
|
||||
*/
|
||||
public abstract Logger getLogger();
|
||||
|
||||
/**
|
||||
* Return all players currently connected.
|
||||
*
|
||||
* @return all connected players
|
||||
*/
|
||||
public abstract Collection<ProxiedPlayer> getPlayers();
|
||||
|
||||
/**
|
||||
* Gets a connected player via their unique username.
|
||||
*
|
||||
* @param name of the player
|
||||
* @return their player instance
|
||||
*/
|
||||
public abstract ProxiedPlayer getPlayer(String name);
|
||||
|
||||
/**
|
||||
* Get a server by its name. The instance returned will be taken from a
|
||||
* player currently on that server to facilitate abstract proxy -> server
|
||||
* actions.
|
||||
*
|
||||
* @param name the name to lookup
|
||||
* @return the associated server
|
||||
*/
|
||||
public abstract Server getServer(String name);
|
||||
|
||||
/**
|
||||
* Return all servers registered to this proxy, keyed by name. Unlike the
|
||||
* methods in {@link ConfigurationAdapter#getServers()}, this will not
|
||||
* return a fresh map each time.
|
||||
*
|
||||
* @return all registered remote server destinations
|
||||
*/
|
||||
public abstract Map<String, ServerInfo> getServers();
|
||||
|
||||
/**
|
||||
* Get the {@link PluginManager} associated with loading plugins and
|
||||
* dispatching events. It is recommended that implementations use the
|
||||
* provided PluginManager class.
|
||||
*
|
||||
* @return the plugin manager
|
||||
*/
|
||||
public abstract PluginManager getPluginManager();
|
||||
|
||||
/**
|
||||
* Returns the currently in use configuration adapter.
|
||||
*
|
||||
* @return the used configuration adapter
|
||||
*/
|
||||
public abstract ConfigurationAdapter getConfigurationAdapter();
|
||||
|
||||
/**
|
||||
* Set the configuration adapter to be used. Must be called from
|
||||
* {@link Plugin#onLoad()}.
|
||||
*
|
||||
* @param adapter the adapter to use
|
||||
*/
|
||||
public abstract void setConfigurationAdapter(ConfigurationAdapter adapter);
|
||||
|
||||
/**
|
||||
* Get the currently in use tab list handle.
|
||||
*
|
||||
* @return the tab list handler
|
||||
*/
|
||||
public abstract TabListHandler getTabListHandler();
|
||||
|
||||
/**
|
||||
* Set the used tab list handler, should not be changed once players have
|
||||
* connected
|
||||
*
|
||||
* @param handler the tab list handler to set
|
||||
*/
|
||||
public abstract void setTabListHandler(TabListHandler handler);
|
||||
|
||||
/**
|
||||
* Get the currently in use reconnect handler.
|
||||
*
|
||||
* @return the in use reconnect handler
|
||||
*/
|
||||
public abstract ReconnectHandler getReconnectHandler();
|
||||
|
||||
/**
|
||||
* Sets the reconnect handler to be used for subsequent connections.
|
||||
*
|
||||
* @param handler the new handler
|
||||
*/
|
||||
public abstract void setReconnectHandler(ReconnectHandler handler);
|
||||
|
||||
/**
|
||||
* Gracefully mark this instance for shutdown.
|
||||
*/
|
||||
public abstract void stop();
|
||||
|
||||
/**
|
||||
* Start this instance so that it may accept connections.
|
||||
*
|
||||
* @throws Exception any exception thrown during startup causing the
|
||||
* instance to fail to boot
|
||||
*/
|
||||
public abstract void start() throws Exception;
|
||||
|
||||
/**
|
||||
* Register a channel for use with plugin messages. This is required by some
|
||||
* server / client implementations.
|
||||
*
|
||||
* @param channel the channel to register
|
||||
*/
|
||||
public abstract void registerChannel(String channel);
|
||||
|
||||
/**
|
||||
* Unregister a previously registered channel.
|
||||
*
|
||||
* @param channel the channel to unregister
|
||||
*/
|
||||
public abstract void unregisterChannel(String channel);
|
||||
|
||||
/**
|
||||
* Get an immutable set of all registered plugin channels.
|
||||
*
|
||||
* @return registered plugin channels
|
||||
*/
|
||||
public abstract Collection<String> getChannels();
|
||||
}
|
30
api/src/main/java/net/md_5/bungee/api/ReconnectHandler.java
Normal file
30
api/src/main/java/net/md_5/bungee/api/ReconnectHandler.java
Normal file
@ -0,0 +1,30 @@
|
||||
package net.md_5.bungee.api;
|
||||
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
|
||||
public interface ReconnectHandler
|
||||
{
|
||||
|
||||
/**
|
||||
* Gets the initial server name for a connecting player.
|
||||
*
|
||||
* @param player the connecting player
|
||||
* @return the server name
|
||||
*/
|
||||
public String getServer(ProxiedPlayer player);
|
||||
|
||||
/**
|
||||
* Save the server of this player before they disconnect so it can be
|
||||
* retrieved later.
|
||||
*
|
||||
* @param player the player to save
|
||||
*/
|
||||
public void setServer(ProxiedPlayer player);
|
||||
|
||||
/**
|
||||
* Save all pending reconnect locations. Whilst not used for database
|
||||
* connections, this method will be called at a predefined interval to allow
|
||||
* the saving of reconnect files.
|
||||
*/
|
||||
public void save();
|
||||
}
|
33
api/src/main/java/net/md_5/bungee/api/ServerPing.java
Normal file
33
api/src/main/java/net/md_5/bungee/api/ServerPing.java
Normal file
@ -0,0 +1,33 @@
|
||||
package net.md_5.bungee.api;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Represents the standard list data returned by opening a server in the
|
||||
* Minecraft client server list, or hitting it with a packet 0xFE.
|
||||
*/
|
||||
@Data
|
||||
public class ServerPing
|
||||
{
|
||||
|
||||
/**
|
||||
* Numeric protocol version supported by the server.
|
||||
*/
|
||||
private final byte protocolVersion;
|
||||
/**
|
||||
* Human readable game version.
|
||||
*/
|
||||
private final String gameVersion;
|
||||
/**
|
||||
* Server MOTD.
|
||||
*/
|
||||
private final String motd;
|
||||
/**
|
||||
* Current amount of players on the server.
|
||||
*/
|
||||
private final String currentPlayers;
|
||||
/**
|
||||
* Max amount of players the server will allow.
|
||||
*/
|
||||
private final String maxPlayers;
|
||||
}
|
48
api/src/main/java/net/md_5/bungee/api/TabListHandler.java
Normal file
48
api/src/main/java/net/md_5/bungee/api/TabListHandler.java
Normal file
@ -0,0 +1,48 @@
|
||||
package net.md_5.bungee.api;
|
||||
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
|
||||
public interface TabListHandler
|
||||
{
|
||||
|
||||
/**
|
||||
* Called when a player first connects to the proxy.
|
||||
*
|
||||
* @param player the connecting player
|
||||
*/
|
||||
public void onConnect(ProxiedPlayer player);
|
||||
|
||||
/**
|
||||
* Called when a player changes their connected server.
|
||||
*
|
||||
* @param player the player who changed servers
|
||||
*/
|
||||
public void onServerChange(ProxiedPlayer player);
|
||||
|
||||
/**
|
||||
* Called when a players ping changes. The new ping will have not updated in
|
||||
* the player instance until this method returns.
|
||||
*
|
||||
* @param player the player who's ping changed
|
||||
* @param ping the player's new ping.
|
||||
*/
|
||||
public void onPingChange(ProxiedPlayer player, int ping);
|
||||
|
||||
/**
|
||||
* Called when a player disconnects.
|
||||
*
|
||||
* @param player the disconnected player
|
||||
*/
|
||||
public void onDisconnect(ProxiedPlayer player);
|
||||
|
||||
/**
|
||||
* Called when a list update packet is sent from server to client.
|
||||
*
|
||||
* @param player receiving this packet
|
||||
* @param name the player which this packet is relevant to
|
||||
* @param online whether the subject player is online
|
||||
* @param ping ping of the subject player
|
||||
* @return whether to send the packet to the client
|
||||
*/
|
||||
public boolean onListUpdate(ProxiedPlayer player, String name, boolean online, int ping);
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
package net.md_5.bungee.api.config;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This class allows plugins to set their own configuration adapter to load
|
||||
* settings from a different place.
|
||||
*/
|
||||
public interface ConfigurationAdapter
|
||||
{
|
||||
|
||||
/**
|
||||
* Gets an integer from the specified path.
|
||||
*
|
||||
* @param path the path to retrieve the integer from
|
||||
* @param def the default value
|
||||
* @return the retrieved integer
|
||||
*/
|
||||
public int getInt(String path, int def);
|
||||
|
||||
/**
|
||||
* Gets a string from the specified path.
|
||||
*
|
||||
* @param path the path to retrieve the string from.
|
||||
* @param def the default value
|
||||
* @return the retrieved string
|
||||
*/
|
||||
public String getString(String path, String def);
|
||||
|
||||
/**
|
||||
* Get the configuration all servers which may be accessible via the proxy.
|
||||
*
|
||||
* @return all accessible servers, keyed by name
|
||||
*/
|
||||
public Map<String, ServerInfo> getServers();
|
||||
|
||||
/**
|
||||
* Get information about all hosts to bind the proxy to.
|
||||
*
|
||||
* @return a list of all hosts to bind to
|
||||
*/
|
||||
public Collection<ListenerInfo> getListeners();
|
||||
|
||||
/**
|
||||
* Get all groups this player is in.
|
||||
*
|
||||
* @param player the player to check
|
||||
* @return all the player's groups.
|
||||
*/
|
||||
public Collection<String> getGroups(String player);
|
||||
|
||||
/**
|
||||
* Get all permission corresponding to the specified group. The result of
|
||||
* this method may or may not be cached, depending on the implementation.
|
||||
*
|
||||
* @param group the group to check
|
||||
* @return all true permissions for this group
|
||||
*/
|
||||
public Collection<String> getPermissions(String group);
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package net.md_5.bungee.api.config;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Map;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Class representing the configuration of a server listener. Used for allowing
|
||||
* multiple listeners on different ports.
|
||||
*/
|
||||
@Data
|
||||
public class ListenerInfo
|
||||
{
|
||||
|
||||
/**
|
||||
* Host to bind to.
|
||||
*/
|
||||
private final InetSocketAddress host;
|
||||
/**
|
||||
* Displayed MOTD.
|
||||
*/
|
||||
private final String motd;
|
||||
/**
|
||||
* Max amount of slots displayed on the ping page.
|
||||
*/
|
||||
private final int maxPlayers;
|
||||
/**
|
||||
* Name of the server which users will be taken to by default.
|
||||
*/
|
||||
private final String defaultServer;
|
||||
/**
|
||||
* Whether reconnect locations will be used, or else the user is simply
|
||||
* transferred to the default server on connect.
|
||||
*/
|
||||
private final boolean forceDefault;
|
||||
/**
|
||||
* A list of host to server name mappings which will force a user to be
|
||||
* transferred depending on the host they connect to.
|
||||
*/
|
||||
private final Map<String, String> forcedHosts;
|
||||
}
|
65
api/src/main/java/net/md_5/bungee/api/config/ServerInfo.java
Normal file
65
api/src/main/java/net/md_5/bungee/api/config/ServerInfo.java
Normal file
@ -0,0 +1,65 @@
|
||||
package net.md_5.bungee.api.config;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.Synchronized;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
|
||||
/**
|
||||
* Class used to represent a server to connect to.
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class ServerInfo
|
||||
{
|
||||
|
||||
/**
|
||||
* Name this server displays as.
|
||||
*/
|
||||
private final String name;
|
||||
/**
|
||||
* Connectable address of this server.
|
||||
*/
|
||||
private final InetSocketAddress address;
|
||||
/**
|
||||
* Players connected to a server defined by these properties.
|
||||
*/
|
||||
private final Collection<ProxiedPlayer> players = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Add a player to the internal set of this server.
|
||||
*
|
||||
* @param player the player to add
|
||||
*/
|
||||
@Synchronized("players")
|
||||
public void addPlayer(ProxiedPlayer player)
|
||||
{
|
||||
players.add(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a player form the internal set of this server.
|
||||
*
|
||||
* @param player the player to remove
|
||||
*/
|
||||
@Synchronized("players")
|
||||
public void removePlayer(ProxiedPlayer player)
|
||||
{
|
||||
players.remove(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the set of all players on this server.
|
||||
*
|
||||
* @return an unmodifiable collection of all players on this server
|
||||
*/
|
||||
@Synchronized("players")
|
||||
public Collection<ProxiedPlayer> getPlayers()
|
||||
{
|
||||
return Collections.unmodifiableCollection(players);
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package net.md_5.bungee.api.connection;
|
||||
|
||||
/**
|
||||
* Represents a player physically connected to the world hosted on this server.
|
||||
*/
|
||||
public interface ConnectedPlayer extends ProxiedPlayer
|
||||
{
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package net.md_5.bungee.api.connection;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
/**
|
||||
* A proxy connection is defined as a connection directly connected to a socket.
|
||||
* It should expose information about the remote peer, however not be specific
|
||||
* to a type of connection, whether server or player.
|
||||
*/
|
||||
public interface Connection
|
||||
{
|
||||
|
||||
/**
|
||||
* Gets the remote address of this connection.
|
||||
*
|
||||
* @return the remote address
|
||||
*/
|
||||
public InetSocketAddress getAddress();
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package net.md_5.bungee.api.connection;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import net.md_5.bungee.api.config.ListenerInfo;
|
||||
|
||||
/**
|
||||
* Represents a user attempting to log into the proxy.
|
||||
*/
|
||||
public interface PendingConnection extends Connection
|
||||
{
|
||||
|
||||
/**
|
||||
* Get the requested username.
|
||||
*
|
||||
* @return the requested username, or null if not set
|
||||
*/
|
||||
public String getName();
|
||||
|
||||
/**
|
||||
* Get the numerical client version of the player attempting to log in.
|
||||
*
|
||||
* @return the protocol version of the remote client
|
||||
*/
|
||||
public byte getVersion();
|
||||
|
||||
/**
|
||||
* Get the requested virtual host that the client tried to connect to.
|
||||
*
|
||||
* @return request virtual host or null if invalid / not specified.
|
||||
*/
|
||||
public InetSocketAddress getVirtualHost();
|
||||
|
||||
/**
|
||||
* Completely kick this user from the proxy and all of its child
|
||||
* connections.
|
||||
*
|
||||
* @param reason the disconnect reason displayed to the player
|
||||
*/
|
||||
public void disconnect(String reason);
|
||||
|
||||
/**
|
||||
* Get the listener that accepted this connection.
|
||||
*
|
||||
* @return the accepting listener
|
||||
*/
|
||||
public ListenerInfo getListener();
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
package net.md_5.bungee.api.connection;
|
||||
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
|
||||
/**
|
||||
* Represents a player who's connection is being connected to somewhere else,
|
||||
* whether it be a remote or embedded server.
|
||||
*/
|
||||
public interface ProxiedPlayer extends Connection, CommandSender
|
||||
{
|
||||
|
||||
/**
|
||||
* Gets this player's display name.
|
||||
*
|
||||
* @return the players current display name
|
||||
*/
|
||||
public String getDisplayName();
|
||||
|
||||
/**
|
||||
* Sets this players display name to be used as their nametag and tab list
|
||||
* name.
|
||||
*
|
||||
* @param name the name to set
|
||||
*/
|
||||
public void setDisplayName(String name);
|
||||
|
||||
/**
|
||||
* Connects / transfers this user to the specified connection, gracefully
|
||||
* closing the current one. Depending on the implementation, this method
|
||||
* might return before the user has been connected.
|
||||
*
|
||||
* @param target the new server to connect to
|
||||
*/
|
||||
public void connect(ServerInfo target);
|
||||
|
||||
/**
|
||||
* Gets the server this player is connected to.
|
||||
*
|
||||
* @return the server this player is connected to
|
||||
*/
|
||||
public Server getServer();
|
||||
|
||||
/**
|
||||
* Gets the ping time between the proxy and this connection.
|
||||
*
|
||||
* @return the current ping time
|
||||
*/
|
||||
public int getPing();
|
||||
|
||||
/**
|
||||
* Disconnect (remove) this player from the proxy with the specified reason.
|
||||
*
|
||||
* @param reason the reason displayed to the player
|
||||
*/
|
||||
public void disconnect(String reason);
|
||||
|
||||
/**
|
||||
* Send a plugin message to this player.
|
||||
*
|
||||
* @param channel the channel to send this data via
|
||||
* @param data the data to send
|
||||
*/
|
||||
public void sendData(String channel, byte[] data);
|
||||
|
||||
/**
|
||||
* Get the pending connection that belongs to this player.
|
||||
*
|
||||
* @return the pending connection that this player used
|
||||
*/
|
||||
public PendingConnection getPendingConnection();
|
||||
}
|
34
api/src/main/java/net/md_5/bungee/api/connection/Server.java
Normal file
34
api/src/main/java/net/md_5/bungee/api/connection/Server.java
Normal file
@ -0,0 +1,34 @@
|
||||
package net.md_5.bungee.api.connection;
|
||||
|
||||
import net.md_5.bungee.api.Callback;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
import net.md_5.bungee.api.ServerPing;
|
||||
|
||||
/**
|
||||
* Represents a destination which this proxy might connect to.
|
||||
*/
|
||||
public interface Server extends Connection
|
||||
{
|
||||
|
||||
/**
|
||||
* Returns the basic information about this server.
|
||||
*
|
||||
* @return the {@link ServerInfo} for this server
|
||||
*/
|
||||
public ServerInfo getInfo();
|
||||
|
||||
/**
|
||||
* Send data by any available means to this server.
|
||||
*
|
||||
* @param channel the channel to send this data via
|
||||
* @param data the data to send
|
||||
*/
|
||||
public abstract void sendData(String channel, byte[] data);
|
||||
|
||||
/**
|
||||
* Asynchronously gets the current player count on this server.
|
||||
*
|
||||
* @param callback the callback to call when the count has been retrieved.
|
||||
*/
|
||||
public abstract void ping(Callback<ServerPing> callback);
|
||||
}
|
33
api/src/main/java/net/md_5/bungee/api/event/ChatEvent.java
Normal file
33
api/src/main/java/net/md_5/bungee/api/event/ChatEvent.java
Normal file
@ -0,0 +1,33 @@
|
||||
package net.md_5.bungee.api.event;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import net.md_5.bungee.api.connection.Connection;
|
||||
import net.md_5.bungee.api.plugin.Cancellable;
|
||||
|
||||
/**
|
||||
* Event called when a player sends a message to a server, or a server sends a
|
||||
* message to a player.
|
||||
*/
|
||||
@Data
|
||||
@ToString(callSuper = true)
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ChatEvent extends TargetedEvent implements Cancellable
|
||||
{
|
||||
|
||||
/**
|
||||
* Cancelled state.
|
||||
*/
|
||||
private boolean cancelled;
|
||||
/**
|
||||
* Text contained in this chat.
|
||||
*/
|
||||
private String message;
|
||||
|
||||
public ChatEvent(Connection sender, Connection receiver, String message)
|
||||
{
|
||||
super(sender, receiver);
|
||||
this.message = message;
|
||||
}
|
||||
}
|
31
api/src/main/java/net/md_5/bungee/api/event/LoginEvent.java
Normal file
31
api/src/main/java/net/md_5/bungee/api/event/LoginEvent.java
Normal file
@ -0,0 +1,31 @@
|
||||
package net.md_5.bungee.api.event;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import net.md_5.bungee.api.connection.PendingConnection;
|
||||
import net.md_5.bungee.api.plugin.Cancellable;
|
||||
import net.md_5.bungee.api.plugin.Event;
|
||||
|
||||
/**
|
||||
* Event called to represent a player logging in.
|
||||
*/
|
||||
@Data
|
||||
@ToString(callSuper = true)
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class LoginEvent extends Event implements Cancellable
|
||||
{
|
||||
|
||||
/**
|
||||
* Cancelled state.
|
||||
*/
|
||||
private boolean cancelled;
|
||||
/**
|
||||
* Message to use when kicking if this event is canceled.
|
||||
*/
|
||||
private String cancelReason;
|
||||
/**
|
||||
* Connection attempting to login.
|
||||
*/
|
||||
private final PendingConnection connection;
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package net.md_5.bungee.api.event;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import net.md_5.bungee.api.connection.Connection;
|
||||
import net.md_5.bungee.api.plugin.Cancellable;
|
||||
|
||||
/**
|
||||
* Event called when a plugin message is sent to the client or server.
|
||||
*/
|
||||
@Data
|
||||
@ToString(callSuper = true)
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class PluginMessageEvent extends TargetedEvent implements Cancellable
|
||||
{
|
||||
|
||||
/**
|
||||
* Cancelled state.
|
||||
*/
|
||||
private boolean cancelled;
|
||||
/**
|
||||
* Tag specified for this plugin message.
|
||||
*/
|
||||
private final String tag;
|
||||
/**
|
||||
* Data contained in this plugin message.
|
||||
*/
|
||||
private final byte[] data;
|
||||
|
||||
public PluginMessageEvent(Connection sender, Connection receiver, String tag, byte[] data)
|
||||
{
|
||||
super(sender, receiver);
|
||||
this.tag = tag;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package net.md_5.bungee.api.event;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import net.md_5.bungee.api.ServerPing;
|
||||
import net.md_5.bungee.api.config.ListenerInfo;
|
||||
import net.md_5.bungee.api.plugin.Event;
|
||||
|
||||
/**
|
||||
* Called when the proxy is pinged with packet 0xFE from the server list.
|
||||
*/
|
||||
@Data
|
||||
@ToString(callSuper = true)
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ProxyPingEvent extends Event
|
||||
{
|
||||
|
||||
/**
|
||||
* The address of the user pinging.
|
||||
*/
|
||||
private final InetSocketAddress remoteAddress;
|
||||
/**
|
||||
* The data corresponding to the server which received this ping.
|
||||
*/
|
||||
private final ListenerInfo server;
|
||||
/**
|
||||
* The data to respond with.
|
||||
*/
|
||||
private ServerPing response;
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package net.md_5.bungee.api.event;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.plugin.Event;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@ToString(callSuper = true)
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ServerConnectEvent extends Event
|
||||
{
|
||||
|
||||
/**
|
||||
* Player connecting to a new server.
|
||||
*/
|
||||
private final ProxiedPlayer player;
|
||||
/**
|
||||
* Server the player will be connected to.
|
||||
*/
|
||||
private ServerInfo target;
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package net.md_5.bungee.api.event;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.connection.Server;
|
||||
import net.md_5.bungee.api.plugin.Event;
|
||||
|
||||
/**
|
||||
* Not to be confused with {@link ServerConnectEvent}, this event is called once
|
||||
* a connection to a server is fully operational, and is about to hand over
|
||||
* control of the session to the player. It is useful if you wish to send
|
||||
* information to the server before the player logs in.
|
||||
*/
|
||||
@Data
|
||||
@ToString(callSuper = true)
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ServerConnectedEvent extends Event
|
||||
{
|
||||
|
||||
/**
|
||||
* Player whom the server is for.
|
||||
*/
|
||||
private final ProxiedPlayer player;
|
||||
/**
|
||||
* The server itself.
|
||||
*/
|
||||
private final Server server;
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package net.md_5.bungee.api.event;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import net.md_5.bungee.api.connection.Connection;
|
||||
import net.md_5.bungee.api.plugin.Event;
|
||||
|
||||
/**
|
||||
* An event which occurs in the communication between two nodes. It is not
|
||||
* recommended to call {@link #setSender(net.md_5.bungee.api.Connection)} or
|
||||
* {@link #setReceiver(net.md_5.bungee.api.Connection)} and the results of doing
|
||||
* so are undefined.
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public abstract class TargetedEvent extends Event
|
||||
{
|
||||
|
||||
/**
|
||||
* Creator of the action.
|
||||
*/
|
||||
private Connection sender;
|
||||
/**
|
||||
* Receiver of the action.
|
||||
*/
|
||||
private Connection receiver;
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package net.md_5.bungee.api.plugin;
|
||||
|
||||
/**
|
||||
* Events that implement this indicate that they may be cancelled and thus
|
||||
* prevented from happening.
|
||||
*/
|
||||
public interface Cancellable
|
||||
{
|
||||
|
||||
/**
|
||||
* Get whether or not this event is cancelled.
|
||||
*
|
||||
* @return the cancelled state of this event
|
||||
*/
|
||||
public boolean isCancelled();
|
||||
|
||||
/**
|
||||
* Sets the cancelled state of this event.
|
||||
*
|
||||
* @param cancel the state to set
|
||||
*/
|
||||
public void setCancelled(boolean cancel);
|
||||
}
|
52
api/src/main/java/net/md_5/bungee/api/plugin/Command.java
Normal file
52
api/src/main/java/net/md_5/bungee/api/plugin/Command.java
Normal file
@ -0,0 +1,52 @@
|
||||
package net.md_5.bungee.api.plugin;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
|
||||
/**
|
||||
* A command that can be executed by a {@link CommandSender}.
|
||||
*/
|
||||
@Data
|
||||
@RequiredArgsConstructor(access = AccessLevel.NONE)
|
||||
public abstract class Command
|
||||
{
|
||||
|
||||
private final String name;
|
||||
private final String permission;
|
||||
private final String[] aliases;
|
||||
|
||||
/**
|
||||
* Construct a new command with no permissions or aliases.
|
||||
*
|
||||
* @param name the name of this command
|
||||
*/
|
||||
public Command(String name)
|
||||
{
|
||||
this(name, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new command.
|
||||
*
|
||||
* @param name primary name of this command
|
||||
* @param permission the permission node required to execute this command,
|
||||
* null or empty string allows it to be executed by everyone
|
||||
* @param aliases aliases which map back to this command
|
||||
*/
|
||||
public Command(String name, String permission, String... aliases)
|
||||
{
|
||||
this.name = name;
|
||||
this.permission = permission;
|
||||
this.aliases = aliases;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute this command with the specified sender and arguments.
|
||||
*
|
||||
* @param sender the executor of this command
|
||||
* @param args arguments used to invoke this command
|
||||
*/
|
||||
public abstract void execute(CommandSender sender, String[] args);
|
||||
}
|
8
api/src/main/java/net/md_5/bungee/api/plugin/Event.java
Normal file
8
api/src/main/java/net/md_5/bungee/api/plugin/Event.java
Normal file
@ -0,0 +1,8 @@
|
||||
package net.md_5.bungee.api.plugin;
|
||||
|
||||
/**
|
||||
* Dummy class which all callable events must extend.
|
||||
*/
|
||||
public abstract class Event
|
||||
{
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package net.md_5.bungee.api.plugin;
|
||||
|
||||
/**
|
||||
* Dummy interface which all event subscribers and listeners must implement.
|
||||
*/
|
||||
public interface Listener
|
||||
{
|
||||
}
|
48
api/src/main/java/net/md_5/bungee/api/plugin/Plugin.java
Normal file
48
api/src/main/java/net/md_5/bungee/api/plugin/Plugin.java
Normal file
@ -0,0 +1,48 @@
|
||||
package net.md_5.bungee.api.plugin;
|
||||
|
||||
import lombok.Getter;
|
||||
import net.md_5.bungee.api.config.ConfigurationAdapter;
|
||||
|
||||
/**
|
||||
* Represents any Plugin that may be loaded at runtime to enhance existing
|
||||
* functionality.
|
||||
*/
|
||||
public class Plugin
|
||||
{
|
||||
|
||||
@Getter
|
||||
private PluginDescription description;
|
||||
|
||||
/**
|
||||
* Called when the plugin has just been loaded. Most of the proxy will not
|
||||
* be initialized, so only use it for registering
|
||||
* {@link ConfigurationAdapter}'s and other predefined behavior.
|
||||
*/
|
||||
public void onLoad()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this plugin is enabled.
|
||||
*/
|
||||
public void onEnable()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this plugin is disabled.
|
||||
*/
|
||||
public void onDisable()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the loader to initialize the fields in this plugin.
|
||||
*
|
||||
* @param description the description that describes this plugin
|
||||
*/
|
||||
final void init(PluginDescription description)
|
||||
{
|
||||
this.description = description;
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package net.md_5.bungee.api.plugin;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* POJO representing the plugin.yml file.
|
||||
*/
|
||||
@Data
|
||||
public class PluginDescription
|
||||
{
|
||||
|
||||
/**
|
||||
* Friendly name of the plugin.
|
||||
*/
|
||||
private final String name;
|
||||
/**
|
||||
* Plugin main class. Needs to extend {@link Plugin}.
|
||||
*/
|
||||
private final String main;
|
||||
/**
|
||||
* Plugin version.
|
||||
*/
|
||||
private final String version;
|
||||
/**
|
||||
* Plugin author.
|
||||
*/
|
||||
private final String author;
|
||||
}
|
218
api/src/main/java/net/md_5/bungee/api/plugin/PluginManager.java
Normal file
218
api/src/main/java/net/md_5/bungee/api/plugin/PluginManager.java
Normal file
@ -0,0 +1,218 @@
|
||||
package net.md_5.bungee.api.plugin;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.eventbus.EventBus;
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.logging.Level;
|
||||
import java.util.regex.Pattern;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
/**
|
||||
* Class to manage bridging between plugin duties and implementation duties, for
|
||||
* example event handling and plugin management.
|
||||
*/
|
||||
public class PluginManager
|
||||
{
|
||||
|
||||
private static final Pattern argsSplit = Pattern.compile(" ");
|
||||
/*========================================================================*/
|
||||
private final Yaml yaml = new Yaml();
|
||||
private final EventBus eventBus = new EventBus();
|
||||
private final Map<String, Plugin> plugins = new HashMap<>();
|
||||
private final Map<String, Command> commandMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Register a command so that it may be executed.
|
||||
*
|
||||
* @param command the command to register
|
||||
*/
|
||||
public void registerCommand(Command command)
|
||||
{
|
||||
commandMap.put(command.getName(), command);
|
||||
for (String alias : command.getAliases())
|
||||
{
|
||||
commandMap.put(alias, command);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a command so it will no longer be executed.
|
||||
*
|
||||
* @param command the command to unregister
|
||||
*/
|
||||
public void unregisterCommand(Command command)
|
||||
{
|
||||
commandMap.values().remove(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a command if it is registered, else return false.
|
||||
*
|
||||
* @param sender the sender executing the command
|
||||
* @param commandLine the complete command line including command name and
|
||||
* arguments
|
||||
* @return whether the command was handled
|
||||
*/
|
||||
public boolean dispatchCommand(CommandSender sender, String commandLine)
|
||||
{
|
||||
String[] split = argsSplit.split(commandLine);
|
||||
Command command = commandMap.get(split[0]);
|
||||
if (command == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
String permission = command.getPermission();
|
||||
if (permission != null && !permission.isEmpty() && !sender.hasPermission(permission))
|
||||
{
|
||||
sender.sendMessage(ChatColor.RED + "You do not have permission to execute this command!");
|
||||
return true;
|
||||
}
|
||||
|
||||
String[] args = Arrays.copyOfRange(split, 1, split.length);
|
||||
try
|
||||
{
|
||||
command.execute(sender, args);
|
||||
} catch (Exception ex)
|
||||
{
|
||||
sender.sendMessage(ChatColor.RED + "An internal error occurred whilst executing this command, please check the console log for details.");
|
||||
ProxyServer.getInstance().getLogger().log(Level.WARNING, "Error in dispatching command", ex);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Plugin} objects corresponding to all loaded plugins.
|
||||
*
|
||||
* @return the set of loaded plugins
|
||||
*/
|
||||
public Collection<Plugin> getPlugins()
|
||||
{
|
||||
return plugins.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a loaded plugin identified by the specified name.
|
||||
*
|
||||
* @param name of the plugin to retrieve
|
||||
* @return the retrieved plugin or null if not loaded
|
||||
*/
|
||||
public Plugin getPlugin(String name)
|
||||
{
|
||||
return plugins.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable all plugins by calling the {@link Plugin#onEnable()} method.
|
||||
*/
|
||||
public void enablePlugins()
|
||||
{
|
||||
for (Map.Entry<String, Plugin> plugin : plugins.entrySet())
|
||||
{
|
||||
try
|
||||
{
|
||||
plugin.getValue().onEnable();
|
||||
} catch (Exception ex)
|
||||
{
|
||||
ProxyServer.getInstance().getLogger().log(Level.WARNING, "Exception encountered when loading plugin: " + plugin.getKey(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a plugin from the specified file. This file must be in jar format.
|
||||
* This will not enable plugins, {@link #enablePlugins()} must be called.
|
||||
*
|
||||
* @param file the file to load from
|
||||
* @throws Exception Any exceptions encountered when loading a plugin from
|
||||
* this file.
|
||||
*/
|
||||
public void loadPlugin(File file) throws Exception
|
||||
{
|
||||
Preconditions.checkNotNull(file, "file");
|
||||
Preconditions.checkArgument(file.isFile(), "Must load from file");
|
||||
|
||||
try (JarFile jar = new JarFile(file))
|
||||
{
|
||||
JarEntry pdf = jar.getJarEntry("plugin.yml");
|
||||
try (InputStream in = jar.getInputStream(pdf))
|
||||
{
|
||||
PluginDescription desc = yaml.loadAs(in, PluginDescription.class);
|
||||
URLClassLoader loader = new URLClassLoader(new URL[]
|
||||
{
|
||||
file.toURI().toURL()
|
||||
});
|
||||
Class<?> main = loader.loadClass(desc.getMain());
|
||||
Plugin plugin = (Plugin) main.getDeclaredConstructor().newInstance();
|
||||
|
||||
plugin.init(desc);
|
||||
plugins.put(pdf.getName(), plugin);
|
||||
plugin.onLoad();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all plugins from the specified folder.
|
||||
*
|
||||
* @param folder the folder to search for plugins in
|
||||
*/
|
||||
public void loadPlugins(File folder)
|
||||
{
|
||||
Preconditions.checkNotNull(folder, "folder");
|
||||
Preconditions.checkArgument(folder.isDirectory(), "Must load from a directory");
|
||||
|
||||
for (File file : folder.listFiles())
|
||||
{
|
||||
if (file.isFile() && file.getName().endsWith(".jar"))
|
||||
{
|
||||
try
|
||||
{
|
||||
loadPlugin(file);
|
||||
} catch (Exception ex)
|
||||
{
|
||||
ProxyServer.getInstance().getLogger().log(Level.WARNING, "Could not load plugin from file " + file, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch an event to all subscribed listeners and return the event once
|
||||
* it has been handled by these listeners.
|
||||
*
|
||||
* @param <T> the type bounds, must be a class which extends event
|
||||
* @param event the event to call
|
||||
* @return the called event
|
||||
*/
|
||||
public <T extends Event> T callEvent(T event)
|
||||
{
|
||||
eventBus.post(event);
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a {@link Listener} for receiving called events. Methods in this
|
||||
* Object which wish to receive events must be annotated with the
|
||||
* {@link Subscribe} annotation.
|
||||
*
|
||||
* @param listener the listener to register events for
|
||||
*/
|
||||
public void registerListener(Listener listener)
|
||||
{
|
||||
eventBus.register(listener);
|
||||
}
|
||||
}
|
112
pom.xml
112
pom.xml
@ -10,12 +10,12 @@
|
||||
</parent>
|
||||
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
<artifactId>bungeecord-parent</artifactId>
|
||||
<version>1.4.7-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>BungeeCord</name>
|
||||
<description>Proxy component of the Elastic Portal Suite</description>
|
||||
<description>Parent project for all BungeeCord modules.</description>
|
||||
<url>https://github.com/ElasticPortalSuite/BungeeCord</url>
|
||||
<inceptionYear>2012</inceptionYear>
|
||||
<organization>
|
||||
@ -36,6 +36,11 @@
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
<modules>
|
||||
<module>api</module>
|
||||
<module>proxy</module>
|
||||
</modules>
|
||||
|
||||
<scm>
|
||||
<connection>scm:git:git@github.com:ElasticPortalSuite/BungeeCord.git</connection>
|
||||
<developerConnection>scm:git:git@github.com:ElasticPortalSuite/BungeeCord.git</developerConnection>
|
||||
@ -53,35 +58,9 @@
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<build.number>unknown</build.number>
|
||||
<main.class>net.md_5.bungee.BungeeCord</main.class>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.google.code.findbugs</groupId>
|
||||
<artifactId>jsr305</artifactId>
|
||||
<version>2.0.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>13.0.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>mendax</artifactId>
|
||||
<version>1.4.6-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-ext-jdk15on</artifactId>
|
||||
<version>1.47</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.yaml</groupId>
|
||||
<artifactId>snakeyaml</artifactId>
|
||||
<version>1.11</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
@ -91,7 +70,6 @@
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>${project.name}</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.lukegb.mojo</groupId>
|
||||
@ -119,78 +97,6 @@
|
||||
<target>1.7</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>2.4</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestEntries>
|
||||
<Main-Class>${main.class}</Main-Class>
|
||||
<Implementation-Version>${describe}</Implementation-Version>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>2.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>**/*.java</exclude>
|
||||
<exclude>**/*.properties</exclude>
|
||||
<exclude>**/*.SF</exclude>
|
||||
<exclude>**/*.DSA</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.github.wvengen</groupId>
|
||||
<artifactId>proguard-maven-plugin</artifactId>
|
||||
<version>2.0.6</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>proguard</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<addMavenDescriptor>true</addMavenDescriptor>
|
||||
<includeDependency>false</includeDependency>
|
||||
<libs>
|
||||
<lib>${java.home}/lib/rt.jar</lib>
|
||||
<lib>${java.home}/lib/jce.jar</lib>
|
||||
</libs>
|
||||
<obfuscate>false</obfuscate>
|
||||
<options>
|
||||
<option>-dontoptimize</option>
|
||||
<option>-keep class net.md_5.bungee.** { *; }</option>
|
||||
</options>
|
||||
</configuration>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>net.sf.proguard</groupId>
|
||||
<artifactId>proguard-base</artifactId>
|
||||
<version>4.8</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
84
proxy/pom.xml
Normal file
84
proxy/pom.xml
Normal file
@ -0,0 +1,84 @@
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-parent</artifactId>
|
||||
<version>1.4.7-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-proxy</artifactId>
|
||||
<version>1.4.7-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>BungeeCord-Proxy</name>
|
||||
<description>Proxy component of the Elastic Portal Suite</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>mendax</artifactId>
|
||||
<version>1.4.6-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
<version>1.47</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>BungeeCord</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>2.4</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestEntries>
|
||||
<Main-Class>net.md_5.bungee.BungeeCord</Main-Class>
|
||||
<Implementation-Version>${describe}</Implementation-Version>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>2.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>**/*.java</exclude>
|
||||
<exclude>**/*.properties</exclude>
|
||||
<exclude>**/*.SF</exclude>
|
||||
<exclude>**/*.DSA</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
<minimizeJar>true</minimizeJar>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
325
proxy/src/main/java/net/md_5/bungee/BungeeCord.java
Normal file
325
proxy/src/main/java/net/md_5/bungee/BungeeCord.java
Normal file
@ -0,0 +1,325 @@
|
||||
package net.md_5.bungee;
|
||||
|
||||
import net.md_5.bungee.config.Configuration;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.Socket;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.Synchronized;
|
||||
import static net.md_5.bungee.Logger.$;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.ReconnectHandler;
|
||||
import net.md_5.bungee.api.TabListHandler;
|
||||
import net.md_5.bungee.api.config.ConfigurationAdapter;
|
||||
import net.md_5.bungee.api.config.ListenerInfo;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.connection.Server;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.api.plugin.PluginManager;
|
||||
import net.md_5.bungee.command.*;
|
||||
import net.md_5.bungee.config.YamlConfig;
|
||||
import net.md_5.bungee.packet.DefinedPacket;
|
||||
import net.md_5.bungee.packet.PacketFAPluginMessage;
|
||||
|
||||
/**
|
||||
* Main BungeeCord proxy class.
|
||||
*/
|
||||
public class BungeeCord extends ProxyServer
|
||||
{
|
||||
|
||||
/**
|
||||
* Server protocol version.
|
||||
*/
|
||||
public static final int PROTOCOL_VERSION = 51;
|
||||
/**
|
||||
* Server game version.
|
||||
*/
|
||||
public static final String GAME_VERSION = "1.4.6";
|
||||
/**
|
||||
* Current operation state.
|
||||
*/
|
||||
public volatile boolean isRunning;
|
||||
/**
|
||||
* Configuration.
|
||||
*/
|
||||
public final Configuration config = new Configuration();
|
||||
/**
|
||||
* Thread pool.
|
||||
*/
|
||||
public final ExecutorService threadPool = Executors.newCachedThreadPool();
|
||||
/**
|
||||
* locations.yml save thread.
|
||||
*/
|
||||
private final Timer saveThread = new Timer("Reconnect Saver");
|
||||
/**
|
||||
* Server socket listener.
|
||||
*/
|
||||
private Collection<ListenThread> listeners = new HashSet<>();
|
||||
/**
|
||||
* Fully qualified connections.
|
||||
*/
|
||||
public Map<String, UserConnection> connections = new ConcurrentHashMap<>();
|
||||
/**
|
||||
* Tab list handler
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public TabListHandler tabListHandler;
|
||||
/**
|
||||
* Plugin manager.
|
||||
*/
|
||||
@Getter
|
||||
public final PluginManager pluginManager = new PluginManager();
|
||||
@Getter
|
||||
@Setter
|
||||
private ReconnectHandler reconnectHandler;
|
||||
@Getter
|
||||
@Setter
|
||||
private ConfigurationAdapter configurationAdapter = new YamlConfig();
|
||||
private final Collection<String> pluginChannels = new HashSet<>();
|
||||
|
||||
|
||||
{
|
||||
getPluginManager().registerCommand(new CommandReload());
|
||||
getPluginManager().registerCommand(new CommandEnd());
|
||||
getPluginManager().registerCommand(new CommandList());
|
||||
getPluginManager().registerCommand(new CommandServer());
|
||||
getPluginManager().registerCommand(new CommandIP());
|
||||
getPluginManager().registerCommand(new CommandAlert());
|
||||
getPluginManager().registerCommand(new CommandBungee());
|
||||
|
||||
registerChannel("BungeeCord::Disconnect");
|
||||
registerChannel("BungeeCord::Connect");
|
||||
registerChannel("BungeeCord::Forward");
|
||||
}
|
||||
|
||||
public static BungeeCord getInstance()
|
||||
{
|
||||
return (BungeeCord) ProxyServer.getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a new instance of BungeeCord.
|
||||
*
|
||||
* @param args command line arguments, currently none are used
|
||||
* @throws IOException when the server cannot be started
|
||||
*/
|
||||
public static void main(String[] args) throws IOException
|
||||
{
|
||||
BungeeCord bungee = new BungeeCord();
|
||||
ProxyServer.setInstance(bungee);
|
||||
$().info("Enabled BungeeCord version " + bungee.getVersion());
|
||||
bungee.start();
|
||||
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
|
||||
while (bungee.isRunning)
|
||||
{
|
||||
String line = br.readLine();
|
||||
if (line != null)
|
||||
{
|
||||
boolean handled = getInstance().getPluginManager().dispatchCommand(ConsoleCommandSender.getInstance(), line);
|
||||
if (!handled)
|
||||
{
|
||||
System.err.println("Command not found");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start this proxy instance by loading the configuration, plugins and
|
||||
* starting the connect thread.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public void start() throws IOException
|
||||
{
|
||||
config.load();
|
||||
reconnectHandler = new YamlReconnectHandler();
|
||||
isRunning = true;
|
||||
|
||||
File plugins = new File("plugins");
|
||||
plugins.mkdir();
|
||||
pluginManager.loadPlugins(plugins);
|
||||
|
||||
for (ListenerInfo info : config.getListeners())
|
||||
{
|
||||
$().info("Listening on " + info.getHost());
|
||||
ListenThread listener = new ListenThread(info);
|
||||
listener.start();
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
saveThread.scheduleAtFixedRate(new TimerTask()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
getReconnectHandler().save();
|
||||
}
|
||||
}, 0, TimeUnit.MINUTES.toMillis(5));
|
||||
|
||||
new Metrics().start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
this.isRunning = false;
|
||||
$().info("Disabling plugins");
|
||||
for (Plugin plugin : pluginManager.getPlugins())
|
||||
{
|
||||
plugin.onDisable();
|
||||
}
|
||||
|
||||
for (ListenThread listener : listeners)
|
||||
{
|
||||
$().log(Level.INFO, "Closing listen thread {0}", listener.socket);
|
||||
try
|
||||
{
|
||||
listener.socket.close();
|
||||
listener.join();
|
||||
} catch (InterruptedException | IOException ex)
|
||||
{
|
||||
$().severe("Could not close listen thread");
|
||||
}
|
||||
}
|
||||
|
||||
$().info("Closing pending connections");
|
||||
threadPool.shutdown();
|
||||
|
||||
$().info("Disconnecting " + connections.size() + " connections");
|
||||
for (UserConnection user : connections.values())
|
||||
{
|
||||
user.disconnect("Proxy restarting, brb.");
|
||||
}
|
||||
|
||||
$().info("Saving reconnect locations");
|
||||
reconnectHandler.save();
|
||||
saveThread.cancel();
|
||||
|
||||
$().info("Thank you and goodbye");
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Miscellaneous method to set options on a socket based on those in the
|
||||
* configuration.
|
||||
*
|
||||
* @param socket to set the options on
|
||||
* @throws IOException when the underlying set methods thrown an exception
|
||||
*/
|
||||
public void setSocketOptions(Socket socket) throws IOException
|
||||
{
|
||||
socket.setSoTimeout(config.getTimeout());
|
||||
socket.setTrafficClass(0x18);
|
||||
socket.setTcpNoDelay(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts a packet to all clients that is connected to this instance.
|
||||
*
|
||||
* @param packet the packet to send
|
||||
*/
|
||||
public void broadcast(DefinedPacket packet)
|
||||
{
|
||||
for (UserConnection con : connections.values())
|
||||
{
|
||||
con.packetQueue.add(packet);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return "BungeeCord";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion()
|
||||
{
|
||||
return (BungeeCord.class.getPackage().getImplementationVersion() == null) ? "unknown" : BungeeCord.class.getPackage().getImplementationVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Logger getLogger()
|
||||
{
|
||||
return $();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked") // TODO: Abstract more
|
||||
public Collection<ProxiedPlayer> getPlayers()
|
||||
{
|
||||
return (Collection) connections.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProxiedPlayer getPlayer(String name)
|
||||
{
|
||||
return connections.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Server getServer(String name)
|
||||
{
|
||||
Collection<ProxiedPlayer> users = getServers().get(name).getPlayers();
|
||||
return (users != null && !users.isEmpty()) ? users.iterator().next().getServer() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ServerInfo> getServers()
|
||||
{
|
||||
return config.getServers();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Synchronized("pluginChannels")
|
||||
public void registerChannel(String channel)
|
||||
{
|
||||
pluginChannels.add(channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Synchronized("pluginChannels")
|
||||
public void unregisterChannel(String channel)
|
||||
{
|
||||
pluginChannels.remove(channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Synchronized("pluginChannels")
|
||||
public Collection<String> getChannels()
|
||||
{
|
||||
return Collections.unmodifiableCollection(pluginChannels);
|
||||
}
|
||||
|
||||
public PacketFAPluginMessage registerChannels()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String s : getChannels())
|
||||
{
|
||||
sb.append(s);
|
||||
sb.append('\00');
|
||||
}
|
||||
byte[] payload = sb.substring(0, sb.length() - 1).getBytes();
|
||||
return new PacketFAPluginMessage("REGISTER", payload);
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import static net.md_5.bungee.Logger.$;
|
||||
import net.md_5.bungee.packet.PacketFFKick;
|
||||
@ -20,8 +21,10 @@ public class GenericConnection
|
||||
protected final Socket socket;
|
||||
protected final PacketInputStream in;
|
||||
protected final OutputStream out;
|
||||
public String username;
|
||||
public String tabListName;
|
||||
@Getter
|
||||
public String name;
|
||||
@Getter
|
||||
public String displayName;
|
||||
|
||||
/**
|
||||
* Close the socket with the specified reason.
|
||||
@ -55,6 +58,6 @@ public class GenericConnection
|
||||
|
||||
public void log(String message)
|
||||
{
|
||||
$().info(socket.getInetAddress() + ((username == null) ? " " : " [" + username + "] ") + message);
|
||||
$().info(socket.getInetAddress() + ((name == null) ? " " : " [" + name + "] ") + message);
|
||||
}
|
||||
}
|
@ -1,31 +1,43 @@
|
||||
package net.md_5.bungee;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import net.md_5.bungee.config.Configuration;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.crypto.SecretKey;
|
||||
import lombok.Getter;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.config.ListenerInfo;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
import net.md_5.bungee.api.connection.PendingConnection;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.event.LoginEvent;
|
||||
import net.md_5.bungee.packet.Packet2Handshake;
|
||||
import net.md_5.bungee.packet.PacketFCEncryptionResponse;
|
||||
import net.md_5.bungee.packet.PacketFDEncryptionRequest;
|
||||
import net.md_5.bungee.packet.PacketFFKick;
|
||||
import net.md_5.bungee.packet.PacketInputStream;
|
||||
import net.md_5.bungee.plugin.LoginEvent;
|
||||
import org.bouncycastle.crypto.io.CipherInputStream;
|
||||
import org.bouncycastle.crypto.io.CipherOutputStream;
|
||||
|
||||
public class InitialHandler implements Runnable
|
||||
public class InitialHandler implements Runnable, PendingConnection
|
||||
{
|
||||
|
||||
private final Socket socket;
|
||||
@Getter
|
||||
private final ListenerInfo listener;
|
||||
private PacketInputStream in;
|
||||
private OutputStream out;
|
||||
private Packet2Handshake handshake;
|
||||
|
||||
public InitialHandler(Socket socket) throws IOException
|
||||
public InitialHandler(Socket socket, ListenerInfo info) throws IOException
|
||||
{
|
||||
this.socket = socket;
|
||||
this.listener = info;
|
||||
in = new PacketInputStream(socket.getInputStream());
|
||||
out = socket.getOutputStream();
|
||||
}
|
||||
@ -40,15 +52,7 @@ public class InitialHandler implements Runnable
|
||||
switch (id)
|
||||
{
|
||||
case 0x02:
|
||||
Packet2Handshake handshake = new Packet2Handshake(packet);
|
||||
// fire connect event
|
||||
LoginEvent event = new LoginEvent(handshake.username, socket.getInetAddress(), handshake.host);
|
||||
BungeeCord.instance.pluginManager.onHandshake(event);
|
||||
if (event.isCancelled())
|
||||
{
|
||||
throw new KickException(event.getCancelReason());
|
||||
}
|
||||
|
||||
handshake = new Packet2Handshake(packet);
|
||||
PacketFDEncryptionRequest request = EncryptionUtil.encryptRequest();
|
||||
out.write(request.getPacket());
|
||||
PacketFCEncryptionResponse response = new PacketFCEncryptionResponse(in.readPacket());
|
||||
@ -60,13 +64,14 @@ public class InitialHandler implements Runnable
|
||||
}
|
||||
|
||||
// Check for multiple connections
|
||||
if (BungeeCord.instance.connections.containsKey(handshake.username))
|
||||
ProxiedPlayer old = ProxyServer.getInstance().getPlayer(handshake.username);
|
||||
if (old != null)
|
||||
{
|
||||
throw new KickException("You are already connected to the server");
|
||||
old.disconnect("You are already connected to the server");
|
||||
}
|
||||
|
||||
// fire post auth event
|
||||
BungeeCord.instance.pluginManager.onLogin(event);
|
||||
// fire login event
|
||||
LoginEvent event = new LoginEvent(this);
|
||||
if (event.isCancelled())
|
||||
{
|
||||
throw new KickException(event.getCancelReason());
|
||||
@ -82,9 +87,10 @@ public class InitialHandler implements Runnable
|
||||
customPackets.add(custom);
|
||||
}
|
||||
|
||||
UserConnection userCon = new UserConnection(socket, in, out, handshake, customPackets);
|
||||
String server = (BungeeCord.instance.config.forceDefaultServer) ? BungeeCord.instance.config.defaultServerName : BungeeCord.instance.config.getServer(handshake.username, handshake.host);
|
||||
userCon.connect(server);
|
||||
UserConnection userCon = new UserConnection(socket, this, in, out, handshake, customPackets);
|
||||
String server = ProxyServer.getInstance().getReconnectHandler().getServer(userCon);
|
||||
ServerInfo s = BungeeCord.getInstance().config.getServers().get(server);
|
||||
userCon.connect(s);
|
||||
break;
|
||||
case 0xFE:
|
||||
socket.setSoTimeout(100);
|
||||
@ -96,14 +102,14 @@ public class InitialHandler implements Runnable
|
||||
} catch (IOException ex)
|
||||
{
|
||||
}
|
||||
Configuration conf = BungeeCord.instance.config;
|
||||
Configuration conf = BungeeCord.getInstance().config;
|
||||
String ping = (newPing) ? ChatColor.COLOR_CHAR + "1"
|
||||
+ "\00" + BungeeCord.PROTOCOL_VERSION
|
||||
+ "\00" + BungeeCord.GAME_VERSION
|
||||
+ "\00" + conf.motd
|
||||
+ "\00" + BungeeCord.instance.connections.size()
|
||||
+ "\00" + conf.maxPlayers
|
||||
: conf.motd + ChatColor.COLOR_CHAR + BungeeCord.instance.connections.size() + ChatColor.COLOR_CHAR + conf.maxPlayers;
|
||||
+ "\00" + listener.getMotd()
|
||||
+ "\00" + ProxyServer.getInstance().getPlayers().size()
|
||||
+ "\00" + listener.getMaxPlayers()
|
||||
: listener.getMotd() + ChatColor.COLOR_CHAR + ProxyServer.getInstance().getPlayers().size() + ChatColor.COLOR_CHAR + listener.getMaxPlayers();
|
||||
throw new KickException(ping);
|
||||
default:
|
||||
if (id == 0xFA)
|
||||
@ -116,18 +122,20 @@ public class InitialHandler implements Runnable
|
||||
}
|
||||
} catch (KickException ex)
|
||||
{
|
||||
kick(ex.getMessage());
|
||||
disconnect(ex.getMessage());
|
||||
} catch (Exception ex)
|
||||
{
|
||||
kick("[Proxy Error] " + Util.exception(ex));
|
||||
disconnect("[Proxy Error] " + Util.exception(ex));
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void kick(String message)
|
||||
@Override
|
||||
public void disconnect(String reason)
|
||||
{
|
||||
try
|
||||
{
|
||||
out.write(new PacketFFKick(message).getPacket());
|
||||
out.write(new PacketFFKick(reason).getPacket());
|
||||
} catch (IOException ioe)
|
||||
{
|
||||
} finally
|
||||
@ -141,4 +149,28 @@ public class InitialHandler implements Runnable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return (handshake == null) ? null : handshake.username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getVersion()
|
||||
{
|
||||
return (handshake == null) ? -1 : handshake.procolVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getVirtualHost()
|
||||
{
|
||||
return (handshake == null) ? null : new InetSocketAddress(handshake.host, handshake.port);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getAddress()
|
||||
{
|
||||
return (InetSocketAddress) socket.getRemoteSocketAddress();
|
||||
}
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
package net.md_5.bungee;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import static net.md_5.bungee.Logger.$;
|
||||
import net.md_5.bungee.api.config.ListenerInfo;
|
||||
|
||||
/**
|
||||
* Thread to listen and dispatch incoming connections to the proxy.
|
||||
@ -14,26 +14,28 @@ public class ListenThread extends Thread
|
||||
{
|
||||
|
||||
public final ServerSocket socket;
|
||||
private final ListenerInfo info;
|
||||
|
||||
public ListenThread(InetSocketAddress addr) throws IOException
|
||||
public ListenThread(ListenerInfo info) throws IOException
|
||||
{
|
||||
super("Listen Thread");
|
||||
super("Listen Thread - "+ info);
|
||||
this.info = info;
|
||||
socket = new ServerSocket();
|
||||
socket.bind(addr);
|
||||
socket.bind(info.getHost());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
while (BungeeCord.instance.isRunning)
|
||||
while (BungeeCord.getInstance().isRunning)
|
||||
{
|
||||
try
|
||||
{
|
||||
Socket client = socket.accept();
|
||||
BungeeCord.instance.setSocketOptions(client);
|
||||
BungeeCord.getInstance().setSocketOptions(client);
|
||||
$().info(client.getInetAddress() + " has connected");
|
||||
InitialHandler handler = new InitialHandler(client);
|
||||
BungeeCord.instance.threadPool.submit(handler);
|
||||
InitialHandler handler = new InitialHandler(client,info);
|
||||
BungeeCord.getInstance().threadPool.submit(handler);
|
||||
} catch (SocketException ex)
|
||||
{
|
||||
} catch (IOException ex)
|
@ -23,7 +23,7 @@ public class Logger extends java.util.logging.Logger
|
||||
super("RubberBand", null);
|
||||
try
|
||||
{
|
||||
FileHandler handler = new FileHandler("proxy.log", BungeeCord.instance.config.logNumLines, 1, true);
|
||||
FileHandler handler = new FileHandler("proxy.log", 1 << 14, 1, true);
|
||||
handler.setFormatter(formatter);
|
||||
addHandler(handler);
|
||||
} catch (IOException ex)
|
@ -9,6 +9,7 @@ import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLEncoder;
|
||||
import static net.md_5.bungee.Logger.$;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
|
||||
public class Metrics extends Thread
|
||||
{
|
||||
@ -73,10 +74,10 @@ public class Metrics extends Thread
|
||||
{
|
||||
// Construct the post data
|
||||
final StringBuilder data = new StringBuilder();
|
||||
data.append(encode("guid")).append('=').append(encode(BungeeCord.instance.config.statsUuid));
|
||||
encodeDataPair(data, "version", BungeeCord.instance.version);
|
||||
data.append(encode("guid")).append('=').append(encode(BungeeCord.getInstance().config.getUuid()));
|
||||
encodeDataPair(data, "version", ProxyServer.getInstance().getVersion());
|
||||
encodeDataPair(data, "server", "0");
|
||||
encodeDataPair(data, "players", Integer.toString(BungeeCord.instance.connections.size()));
|
||||
encodeDataPair(data, "players", Integer.toString(ProxyServer.getInstance().getPlayers().size()));
|
||||
encodeDataPair(data, "revision", String.valueOf(REVISION));
|
||||
|
||||
// If we're pinging, append it
|
@ -7,6 +7,14 @@ import java.security.PublicKey;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import javax.crypto.SecretKey;
|
||||
import lombok.Getter;
|
||||
import net.md_5.bungee.api.Callback;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.ServerPing;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
import net.md_5.bungee.api.connection.Server;
|
||||
import net.md_5.bungee.api.event.ServerConnectedEvent;
|
||||
import net.md_5.bungee.packet.DefinedPacket;
|
||||
import net.md_5.bungee.packet.Packet1Login;
|
||||
import net.md_5.bungee.packet.Packet2Handshake;
|
||||
@ -22,27 +30,28 @@ import org.bouncycastle.crypto.io.CipherOutputStream;
|
||||
/**
|
||||
* Class representing a connection from the proxy to the server; ie upstream.
|
||||
*/
|
||||
public class ServerConnection extends GenericConnection
|
||||
public class ServerConnection extends GenericConnection implements Server
|
||||
{
|
||||
|
||||
public final String name;
|
||||
@Getter
|
||||
private final ServerInfo info;
|
||||
public final Packet1Login loginPacket;
|
||||
public Queue<DefinedPacket> packetQueue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
public ServerConnection(String name, Socket socket, PacketInputStream in, OutputStream out, Packet1Login loginPacket)
|
||||
public ServerConnection(Socket socket, ServerInfo info, PacketInputStream in, OutputStream out, Packet1Login loginPacket)
|
||||
{
|
||||
super(socket, in, out);
|
||||
this.name = name;
|
||||
this.info = info;
|
||||
this.loginPacket = loginPacket;
|
||||
}
|
||||
|
||||
public static ServerConnection connect(UserConnection user, String name, InetSocketAddress address, Packet2Handshake handshake, boolean retry)
|
||||
public static ServerConnection connect(UserConnection user, ServerInfo info, Packet2Handshake handshake, boolean retry)
|
||||
{
|
||||
try
|
||||
{
|
||||
Socket socket = new Socket();
|
||||
socket.connect(address, BungeeCord.instance.config.timeout);
|
||||
BungeeCord.instance.setSocketOptions(socket);
|
||||
socket.connect(info.getAddress(), BungeeCord.getInstance().config.getTimeout());
|
||||
BungeeCord.getInstance().setSocketOptions(socket);
|
||||
|
||||
PacketInputStream in = new PacketInputStream(socket.getInputStream());
|
||||
OutputStream out = socket.getOutputStream();
|
||||
@ -78,27 +87,45 @@ public class ServerConnection extends GenericConnection
|
||||
}
|
||||
Packet1Login login = new Packet1Login(loginResponse);
|
||||
|
||||
// Register all global plugin message channels
|
||||
// TODO: Allow player-specific plugin message channels for full mod support
|
||||
for (String channel : BungeeCord.instance.globalPluginChannels)
|
||||
{
|
||||
out.write(new PacketFAPluginMessage("REGISTER", channel.getBytes()).getPacket());
|
||||
}
|
||||
ServerConnection server = new ServerConnection(socket, info, in, out, login);
|
||||
ServerConnectedEvent event = new ServerConnectedEvent(user, server);
|
||||
ProxyServer.getInstance().getPluginManager().callEvent(event);
|
||||
|
||||
return new ServerConnection(name, socket, in, out, login);
|
||||
out.write(BungeeCord.getInstance().registerChannels().getPacket());
|
||||
|
||||
return server;
|
||||
} catch (KickException ex)
|
||||
{
|
||||
throw ex;
|
||||
} catch (Exception ex)
|
||||
{
|
||||
InetSocketAddress def = BungeeCord.instance.config.getServer(null);
|
||||
if (retry && !address.equals(def))
|
||||
ServerInfo def = ProxyServer.getInstance().getServers().get(user.getPendingConnection().getListener().getDefaultServer());
|
||||
if (retry && !info.equals(def))
|
||||
{
|
||||
return connect(user, BungeeCord.instance.config.defaultServerName, def, handshake, false);
|
||||
user.sendMessage(ChatColor.RED + "Could not connect to target server, you have been moved to the default server");
|
||||
return connect(user, def, handshake, false);
|
||||
} else
|
||||
{
|
||||
throw new RuntimeException("Could not connect to target server " + Util.exception(ex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendData(String channel, byte[] data)
|
||||
{
|
||||
packetQueue.add(new PacketFAPluginMessage(channel, data));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ping(Callback<ServerPing> callback)
|
||||
{
|
||||
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getAddress()
|
||||
{
|
||||
return getInfo().getAddress();
|
||||
}
|
||||
}
|
428
proxy/src/main/java/net/md_5/bungee/UserConnection.java
Normal file
428
proxy/src/main/java/net/md_5/bungee/UserConnection.java
Normal file
@ -0,0 +1,428 @@
|
||||
package net.md_5.bungee;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import lombok.Getter;
|
||||
import lombok.Synchronized;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
import net.md_5.bungee.api.connection.PendingConnection;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.connection.Server;
|
||||
import net.md_5.bungee.api.event.ChatEvent;
|
||||
import net.md_5.bungee.api.event.PluginMessageEvent;
|
||||
import net.md_5.bungee.api.event.ServerConnectEvent;
|
||||
import net.md_5.bungee.packet.*;
|
||||
|
||||
public class UserConnection extends GenericConnection implements ProxiedPlayer
|
||||
{
|
||||
|
||||
public final Packet2Handshake handshake;
|
||||
public Queue<DefinedPacket> packetQueue = new ConcurrentLinkedQueue<>();
|
||||
public List<byte[]> loginPackets = new ArrayList<>();
|
||||
@Getter
|
||||
private final PendingConnection pendingConnection;
|
||||
@Getter
|
||||
private ServerConnection server;
|
||||
private UpstreamBridge upBridge;
|
||||
private DownstreamBridge downBridge;
|
||||
// reconnect stuff
|
||||
private int clientEntityId;
|
||||
private int serverEntityId;
|
||||
private volatile boolean reconnecting;
|
||||
// ping stuff
|
||||
private int trackingPingId;
|
||||
private long pingTime;
|
||||
@Getter
|
||||
private int ping;
|
||||
// Permissions
|
||||
private final Collection<String> groups = new HashSet<>();
|
||||
private final Map<String, Boolean> permissions = new HashMap<>();
|
||||
private final Object permMutex = new Object();
|
||||
|
||||
public UserConnection(Socket socket, PendingConnection pendingConnection, PacketInputStream in, OutputStream out, Packet2Handshake handshake, List<byte[]> loginPackets)
|
||||
{
|
||||
super(socket, in, out);
|
||||
this.handshake = handshake;
|
||||
this.pendingConnection = pendingConnection;
|
||||
name = handshake.username;
|
||||
displayName = handshake.username;
|
||||
this.loginPackets = loginPackets;
|
||||
Collection<String> g = ProxyServer.getInstance().getConfigurationAdapter().getGroups(name);
|
||||
for (String s : g)
|
||||
{
|
||||
addGroups(s);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplayName(String name)
|
||||
{
|
||||
ProxyServer.getInstance().getTabListHandler().onDisconnect(this);
|
||||
displayName = name;
|
||||
ProxyServer.getInstance().getTabListHandler().onConnect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect(ServerInfo target)
|
||||
{
|
||||
if (server == null)
|
||||
{
|
||||
// First join
|
||||
BungeeCord.getInstance().connections.put(name, this);
|
||||
ProxyServer.getInstance().getTabListHandler().onConnect(this);
|
||||
}
|
||||
|
||||
ServerConnectEvent event = new ServerConnectEvent(this, target);
|
||||
BungeeCord.getInstance().getPluginManager().callEvent(event);
|
||||
target = event.getTarget(); // Update in case the event changed target
|
||||
|
||||
ProxyServer.getInstance().getTabListHandler().onServerChange(this);
|
||||
try
|
||||
{
|
||||
reconnecting = true;
|
||||
|
||||
if (server != null)
|
||||
{
|
||||
out.write(new Packet9Respawn((byte) 1, (byte) 0, (byte) 0, (short) 256, "DEFAULT").getPacket());
|
||||
out.write(new Packet9Respawn((byte) -1, (byte) 0, (byte) 0, (short) 256, "DEFAULT").getPacket());
|
||||
}
|
||||
|
||||
ServerConnection newServer = ServerConnection.connect(this, target, handshake, true);
|
||||
if (server == null)
|
||||
{
|
||||
// Once again, first connection
|
||||
clientEntityId = newServer.loginPacket.entityId;
|
||||
serverEntityId = newServer.loginPacket.entityId;
|
||||
out.write(newServer.loginPacket.getPacket());
|
||||
out.write(BungeeCord.getInstance().registerChannels().getPacket());
|
||||
|
||||
upBridge = new UpstreamBridge();
|
||||
upBridge.start();
|
||||
} else
|
||||
{
|
||||
try
|
||||
{
|
||||
downBridge.interrupt();
|
||||
downBridge.join();
|
||||
} catch (InterruptedException ie)
|
||||
{
|
||||
}
|
||||
|
||||
server.disconnect("Quitting");
|
||||
server.getInfo().removePlayer(this);
|
||||
|
||||
Packet1Login login = newServer.loginPacket;
|
||||
serverEntityId = login.entityId;
|
||||
out.write(new Packet9Respawn(login.dimension, login.difficulty, login.gameMode, (short) 256, login.levelType).getPacket());
|
||||
}
|
||||
|
||||
// Reconnect process has finished, lets get the player moving again
|
||||
reconnecting = false;
|
||||
|
||||
// Add to new
|
||||
target.addPlayer(this);
|
||||
|
||||
// Start the bridges and move on
|
||||
server = newServer;
|
||||
downBridge = new DownstreamBridge();
|
||||
downBridge.start();
|
||||
} catch (KickException ex)
|
||||
{
|
||||
destroySelf(ex.getMessage());
|
||||
} catch (Exception ex)
|
||||
{
|
||||
ex.printStackTrace(); // TODO: Remove
|
||||
destroySelf("Could not connect to server - " + ex.getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
private void destroySelf(String reason)
|
||||
{
|
||||
server.getInfo().removePlayer(this);
|
||||
ProxyServer.getInstance().getPlayers().remove(this);
|
||||
|
||||
disconnect(reason);
|
||||
if (server != null)
|
||||
{
|
||||
server.disconnect("Quitting");
|
||||
ProxyServer.getInstance().getReconnectHandler().setServer(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect(String reason)
|
||||
{
|
||||
ProxyServer.getInstance().getTabListHandler().onDisconnect(this);
|
||||
super.disconnect(reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(String message)
|
||||
{
|
||||
packetQueue.add(new Packet3Chat(message));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendData(String channel, byte[] data)
|
||||
{
|
||||
server.packetQueue.add(new PacketFAPluginMessage(channel, data));
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getAddress()
|
||||
{
|
||||
return (InetSocketAddress) socket.getRemoteSocketAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Synchronized("permMutex")
|
||||
public Collection<String> getGroups()
|
||||
{
|
||||
return Collections.unmodifiableCollection(groups);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Synchronized("permMutex")
|
||||
public void addGroups(String... groups)
|
||||
{
|
||||
for (String group : groups)
|
||||
{
|
||||
this.groups.add(group);
|
||||
for (String permission : ProxyServer.getInstance().getConfigurationAdapter().getPermissions(group))
|
||||
{
|
||||
setPermission(permission, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Synchronized( "permMutex")
|
||||
public void removeGroups(String... groups)
|
||||
{
|
||||
for (String group : groups)
|
||||
{
|
||||
this.groups.remove(group);
|
||||
for (String permission : ProxyServer.getInstance().getConfigurationAdapter().getPermissions(group))
|
||||
{
|
||||
setPermission(permission, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Synchronized("permMutex")
|
||||
public boolean hasPermission(String permission)
|
||||
{
|
||||
Boolean val = permissions.get(permission);
|
||||
return (val == null) ? false : val;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Synchronized( "permMutex")
|
||||
public void setPermission(String permission, boolean value)
|
||||
{
|
||||
permissions.put(permission, value);
|
||||
}
|
||||
|
||||
private class UpstreamBridge extends Thread
|
||||
{
|
||||
|
||||
public UpstreamBridge()
|
||||
{
|
||||
super("Upstream Bridge - " + name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
while (!socket.isClosed())
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] packet = in.readPacket();
|
||||
boolean sendPacket = true;
|
||||
int id = Util.getId(packet);
|
||||
|
||||
switch (id)
|
||||
{
|
||||
case 0x00:
|
||||
if (trackingPingId == new Packet0KeepAlive(packet).id)
|
||||
{
|
||||
int newPing = (int) (System.currentTimeMillis() - pingTime);
|
||||
ProxyServer.getInstance().getTabListHandler().onPingChange(UserConnection.this, newPing);
|
||||
ping = newPing;
|
||||
}
|
||||
break;
|
||||
case 0x03:
|
||||
Packet3Chat chat = new Packet3Chat(packet);
|
||||
if (chat.message.startsWith("/"))
|
||||
{
|
||||
sendPacket = !ProxyServer.getInstance().getPluginManager().dispatchCommand(UserConnection.this, chat.message.substring(1));
|
||||
} else
|
||||
{
|
||||
ChatEvent chatEvent = new ChatEvent(UserConnection.this, server, chat.message);
|
||||
ProxyServer.getInstance().getPluginManager().callEvent(chatEvent);
|
||||
sendPacket = !chatEvent.isCancelled();
|
||||
}
|
||||
break;
|
||||
case 0xFA:
|
||||
// Call the onPluginMessage event
|
||||
PacketFAPluginMessage message = new PacketFAPluginMessage(packet);
|
||||
PluginMessageEvent event = new PluginMessageEvent(UserConnection.this, server, message.tag, message.data);
|
||||
ProxyServer.getInstance().getPluginManager().callEvent(event);
|
||||
|
||||
if (event.isCancelled())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
while (!server.packetQueue.isEmpty())
|
||||
{
|
||||
DefinedPacket p = server.packetQueue.poll();
|
||||
if (p != null)
|
||||
{
|
||||
server.out.write(p.getPacket());
|
||||
}
|
||||
}
|
||||
|
||||
EntityMap.rewrite(packet, clientEntityId, serverEntityId);
|
||||
if (sendPacket && !server.socket.isClosed())
|
||||
{
|
||||
server.out.write(packet);
|
||||
}
|
||||
} catch (IOException ex)
|
||||
{
|
||||
destroySelf("Reached end of stream");
|
||||
} catch (Exception ex)
|
||||
{
|
||||
destroySelf(Util.exception(ex));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class DownstreamBridge extends Thread
|
||||
{
|
||||
|
||||
public DownstreamBridge()
|
||||
{
|
||||
super("Downstream Bridge - " + name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
outer:
|
||||
while (!reconnecting)
|
||||
{
|
||||
byte[] packet = server.in.readPacket();
|
||||
int id = Util.getId(packet);
|
||||
|
||||
switch (id)
|
||||
{
|
||||
case 0x00:
|
||||
trackingPingId = new Packet0KeepAlive(packet).id;
|
||||
pingTime = System.currentTimeMillis();
|
||||
break;
|
||||
case 0x03:
|
||||
Packet3Chat chat = new Packet3Chat(packet);
|
||||
ChatEvent chatEvent = new ChatEvent(server, UserConnection.this, chat.message);
|
||||
ProxyServer.getInstance().getPluginManager().callEvent(chatEvent);
|
||||
|
||||
if (chatEvent.isCancelled())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case 0xC9:
|
||||
PacketC9PlayerListItem playerList = new PacketC9PlayerListItem(packet);
|
||||
if (!ProxyServer.getInstance().getTabListHandler().onListUpdate(UserConnection.this, playerList.username, playerList.online, playerList.ping))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case 0xFA:
|
||||
// Call the onPluginMessage event
|
||||
PacketFAPluginMessage message = new PacketFAPluginMessage(packet);
|
||||
DataInputStream in = new DataInputStream(new ByteArrayInputStream(message.data));
|
||||
PluginMessageEvent event = new PluginMessageEvent(server, UserConnection.this, message.tag, message.data);
|
||||
ProxyServer.getInstance().getPluginManager().callEvent(event);
|
||||
|
||||
if (event.isCancelled())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (message.tag)
|
||||
{
|
||||
case "BungeeCord::Disconnect":
|
||||
break outer;
|
||||
case "BungeeCord::Forward":
|
||||
String target = in.readUTF();
|
||||
String channel = in.readUTF();
|
||||
short len = in.readShort();
|
||||
byte[] data = new byte[len];
|
||||
in.readFully(data);
|
||||
|
||||
if (target.equals("ALL"))
|
||||
{
|
||||
for (String s : BungeeCord.getInstance().getServers().keySet())
|
||||
{
|
||||
Server server = BungeeCord.getInstance().getServer(s);
|
||||
server.sendData(channel, data);
|
||||
}
|
||||
} else
|
||||
{
|
||||
Server server = BungeeCord.getInstance().getServer(target);
|
||||
server.sendData(channel, data);
|
||||
}
|
||||
|
||||
break;
|
||||
case "BungeeCord::Connect":
|
||||
ServerInfo server = BungeeCord.getInstance().config.getServers().get(in.readUTF());
|
||||
if (server != null)
|
||||
{
|
||||
connect(server);
|
||||
break outer;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (!packetQueue.isEmpty())
|
||||
{
|
||||
DefinedPacket p = packetQueue.poll();
|
||||
if (p != null)
|
||||
{
|
||||
out.write(p.getPacket());
|
||||
}
|
||||
}
|
||||
|
||||
EntityMap.rewrite(packet, serverEntityId, clientEntityId);
|
||||
out.write(packet);
|
||||
}
|
||||
} catch (Exception ex)
|
||||
{
|
||||
destroySelf(Util.exception(ex));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
package net.md_5.bungee;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Level;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.ReconnectHandler;
|
||||
import net.md_5.bungee.api.config.ListenerInfo;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
public class YamlReconnectHandler implements ReconnectHandler
|
||||
{
|
||||
|
||||
private final Yaml yaml = new Yaml();
|
||||
private final File file = new File("locations.yml");
|
||||
/*========================================================================*/
|
||||
private Map<String, String> data;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public YamlReconnectHandler()
|
||||
{
|
||||
try
|
||||
{
|
||||
file.createNewFile();
|
||||
try (FileReader rd = new FileReader(file))
|
||||
{
|
||||
data = yaml.loadAs(rd, Map.class);
|
||||
}
|
||||
} catch (IOException ex)
|
||||
{
|
||||
ProxyServer.getInstance().getLogger().log(Level.WARNING, "Could not load reconnect locations", ex);
|
||||
}
|
||||
|
||||
if (data == null)
|
||||
{
|
||||
data = new ConcurrentHashMap<>();
|
||||
} else
|
||||
{
|
||||
data = new ConcurrentHashMap<>(data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServer(ProxiedPlayer player)
|
||||
{
|
||||
ListenerInfo listener = player.getPendingConnection().getListener();
|
||||
if (listener.isForceDefault())
|
||||
{
|
||||
return listener.getDefaultServer();
|
||||
}
|
||||
String forced = listener.getForcedHosts().get(player.getPendingConnection().getVirtualHost().getHostName());
|
||||
String server = (forced == null) ? data.get(key(player)) : forced;
|
||||
return (server != null) ? server : listener.getDefaultServer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setServer(ProxiedPlayer player)
|
||||
{
|
||||
data.put(key(player), player.getServer().getInfo().getName());
|
||||
}
|
||||
|
||||
private String key(ProxiedPlayer player)
|
||||
{
|
||||
InetSocketAddress host = player.getPendingConnection().getVirtualHost();
|
||||
return player.getName() + ";" + host.getHostString() + ":" + host.getPort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
try (FileWriter wr = new FileWriter(file))
|
||||
{
|
||||
yaml.dump(data, wr);
|
||||
} catch (IOException ex)
|
||||
{
|
||||
ProxyServer.getInstance().getLogger().log(Level.WARNING, "Could not save reconnect locations", ex);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,21 +1,22 @@
|
||||
package net.md_5.bungee.command;
|
||||
|
||||
import net.md_5.bungee.BungeeCord;
|
||||
import net.md_5.bungee.ChatColor;
|
||||
import net.md_5.bungee.Permission;
|
||||
import net.md_5.bungee.UserConnection;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.plugin.Command;
|
||||
|
||||
public class CommandAlert extends Command
|
||||
{
|
||||
|
||||
public CommandAlert()
|
||||
{
|
||||
super("alert", "bungee.command.alert");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args)
|
||||
{
|
||||
if (getPermission(sender) != Permission.ADMIN)
|
||||
{
|
||||
sender.sendMessage(ChatColor.RED + "You do not have permission to execute this command!");
|
||||
return;
|
||||
}
|
||||
if (args.length == 0)
|
||||
{
|
||||
sender.sendMessage(ChatColor.RED + "You must supply a message.");
|
||||
@ -30,15 +31,15 @@ public class CommandAlert extends Command
|
||||
|
||||
for (String s : args)
|
||||
{
|
||||
s = s.replace("&h", "");
|
||||
s = s.substring(1, s.length());
|
||||
builder.append(ChatColor.translateAlternateColorCodes('&', s));
|
||||
builder.append(" ");
|
||||
}
|
||||
|
||||
String message = builder.substring(0, builder.length() - 1);
|
||||
for (UserConnection con : BungeeCord.instance.connections.values())
|
||||
for (ProxiedPlayer player : ProxyServer.getInstance().getPlayers())
|
||||
{
|
||||
con.sendMessage(message);
|
||||
player.sendMessage(message);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package net.md_5.bungee.command;
|
||||
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.plugin.Command;
|
||||
|
||||
public class CommandBungee extends Command
|
||||
{
|
||||
|
||||
public CommandBungee()
|
||||
{
|
||||
super("bungee");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args)
|
||||
{
|
||||
sender.sendMessage(ChatColor.BLUE + "This server is running BungeeCord version " + ProxyServer.getInstance().getVersion() + " by md_5");
|
||||
}
|
||||
}
|
23
proxy/src/main/java/net/md_5/bungee/command/CommandEnd.java
Normal file
23
proxy/src/main/java/net/md_5/bungee/command/CommandEnd.java
Normal file
@ -0,0 +1,23 @@
|
||||
package net.md_5.bungee.command;
|
||||
|
||||
import net.md_5.bungee.BungeeCord;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.plugin.Command;
|
||||
|
||||
/**
|
||||
* Command to terminate the proxy instance. May only be used by the console.
|
||||
*/
|
||||
public class CommandEnd extends Command
|
||||
{
|
||||
|
||||
public CommandEnd()
|
||||
{
|
||||
super("end", "bungeecord.command.end");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args)
|
||||
{
|
||||
BungeeCord.getInstance().stop();
|
||||
}
|
||||
}
|
@ -1,27 +1,28 @@
|
||||
package net.md_5.bungee.command;
|
||||
|
||||
import net.md_5.bungee.BungeeCord;
|
||||
import net.md_5.bungee.ChatColor;
|
||||
import net.md_5.bungee.Permission;
|
||||
import net.md_5.bungee.UserConnection;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.plugin.Command;
|
||||
|
||||
public class CommandIP extends Command
|
||||
{
|
||||
|
||||
public CommandIP()
|
||||
{
|
||||
super("ip", "bungeecord.command.ip");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args)
|
||||
{
|
||||
if (getPermission(sender) != Permission.MODERATOR && getPermission(sender) != Permission.ADMIN)
|
||||
{
|
||||
sender.sendMessage(ChatColor.RED + "You do not have permission to use this command");
|
||||
return;
|
||||
}
|
||||
if (args.length < 1)
|
||||
{
|
||||
sender.sendMessage(ChatColor.RED + "Please follow this command by a user name");
|
||||
return;
|
||||
}
|
||||
UserConnection user = BungeeCord.instance.connections.get(args[0]);
|
||||
ProxiedPlayer user = ProxyServer.getInstance().getPlayer(args[0]);
|
||||
if (user == null)
|
||||
{
|
||||
sender.sendMessage(ChatColor.RED + "That user is not online");
|
@ -1,9 +1,11 @@
|
||||
package net.md_5.bungee.command;
|
||||
|
||||
import java.util.Collection;
|
||||
import net.md_5.bungee.BungeeCord;
|
||||
import net.md_5.bungee.ChatColor;
|
||||
import net.md_5.bungee.UserConnection;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.plugin.Command;
|
||||
|
||||
/**
|
||||
* Command to list all players connected to the proxy.
|
||||
@ -11,11 +13,16 @@ import net.md_5.bungee.UserConnection;
|
||||
public class CommandList extends Command
|
||||
{
|
||||
|
||||
public CommandList()
|
||||
{
|
||||
super("list", "bungeecord.command.list");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args)
|
||||
{
|
||||
StringBuilder users = new StringBuilder();
|
||||
Collection<UserConnection> connections = BungeeCord.instance.connections.values();
|
||||
Collection<ProxiedPlayer> connections = ProxyServer.getInstance().getPlayers();
|
||||
|
||||
if (connections.isEmpty())
|
||||
{
|
||||
@ -23,18 +30,9 @@ public class CommandList extends Command
|
||||
return;
|
||||
}
|
||||
|
||||
for (UserConnection con : connections)
|
||||
for (ProxiedPlayer player : connections)
|
||||
{
|
||||
switch (getPermission(con))
|
||||
{
|
||||
case ADMIN:
|
||||
users.append(ChatColor.RED);
|
||||
break;
|
||||
case MODERATOR:
|
||||
users.append(ChatColor.GREEN);
|
||||
break;
|
||||
}
|
||||
users.append(con.username);
|
||||
users.append(player.getDisplayName());
|
||||
users.append(", ");
|
||||
users.append(ChatColor.RESET);
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package net.md_5.bungee.command;
|
||||
|
||||
import net.md_5.bungee.BungeeCord;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.plugin.Command;
|
||||
|
||||
public class CommandReload extends Command
|
||||
{
|
||||
|
||||
public CommandReload()
|
||||
{
|
||||
super("greload", "bungeecord.command.reload");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args)
|
||||
{
|
||||
BungeeCord.getInstance().config.load();
|
||||
sender.sendMessage(ChatColor.GREEN + "Reloaded config, please restart if you have any issues");
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package net.md_5.bungee.command;
|
||||
|
||||
import java.util.Map;
|
||||
import net.md_5.bungee.BungeeCord;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.plugin.Command;
|
||||
|
||||
/**
|
||||
* Command to list and switch a player between available servers.
|
||||
*/
|
||||
public class CommandServer extends Command
|
||||
{
|
||||
|
||||
public CommandServer()
|
||||
{
|
||||
super("server", "bungeecord.command.server");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args)
|
||||
{
|
||||
if (!(sender instanceof ProxiedPlayer))
|
||||
{
|
||||
return;
|
||||
}
|
||||
ProxiedPlayer player = (ProxiedPlayer) sender;
|
||||
Map<String, ServerInfo> servers = BungeeCord.getInstance().config.getServers();
|
||||
if (args.length == 0)
|
||||
{
|
||||
StringBuilder serverList = new StringBuilder();
|
||||
for (String server : servers.keySet())
|
||||
{
|
||||
serverList.append(server);
|
||||
serverList.append(", ");
|
||||
}
|
||||
serverList.setLength(serverList.length() - 2);
|
||||
player.sendMessage(ChatColor.GOLD + "You may connect to the following servers at this time: " + serverList.toString());
|
||||
} else
|
||||
{
|
||||
ServerInfo server = servers.get(args[0]);
|
||||
if (server == null)
|
||||
{
|
||||
player.sendMessage(ChatColor.RED + "The specified server does not exist");
|
||||
} else if (server.equals(player.getServer().getInfo()))
|
||||
{
|
||||
player.sendMessage(ChatColor.RED + "You are already on this server.");
|
||||
} else
|
||||
{
|
||||
player.connect(server);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package net.md_5.bungee.command;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import lombok.Getter;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
|
||||
/**
|
||||
* Command sender representing the proxy console.
|
||||
*/
|
||||
public class ConsoleCommandSender implements CommandSender
|
||||
{
|
||||
|
||||
@Getter
|
||||
private static final ConsoleCommandSender instance = new ConsoleCommandSender();
|
||||
|
||||
private ConsoleCommandSender()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(String message)
|
||||
{
|
||||
System.out.println(ChatColor.stripColor(message));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return "CONSOLE";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getGroups()
|
||||
{
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addGroups(String... groups)
|
||||
{
|
||||
throw new UnsupportedOperationException("Console may not have groups");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeGroups(String... groups)
|
||||
{
|
||||
throw new UnsupportedOperationException("Console may not have groups");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(String permission)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPermission(String permission, boolean value)
|
||||
{
|
||||
throw new UnsupportedOperationException("Console has all permissions");
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
package net.md_5.bungee.config;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import lombok.Getter;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.config.ConfigurationAdapter;
|
||||
import net.md_5.bungee.api.config.ListenerInfo;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
import net.md_5.bungee.tablist.GlobalPingTabList;
|
||||
import net.md_5.bungee.tablist.GlobalTabList;
|
||||
import net.md_5.bungee.tablist.ServerUniqueTabList;
|
||||
|
||||
/**
|
||||
* Core configuration for the proxy.
|
||||
*/
|
||||
@Getter
|
||||
public class Configuration
|
||||
{
|
||||
|
||||
/**
|
||||
* The default tab list options available for picking.
|
||||
*/
|
||||
private enum DefaultTabList
|
||||
{
|
||||
|
||||
GLOBAL, GLOBAL_PING, SERVER;
|
||||
}
|
||||
/**
|
||||
* Time before users are disconnected due to no network activity.
|
||||
*/
|
||||
private int timeout = 30000;
|
||||
/**
|
||||
* UUID used for metrics.
|
||||
*/
|
||||
private String uuid = UUID.randomUUID().toString();
|
||||
/**
|
||||
* Set of all listeners.
|
||||
*/
|
||||
private Collection<ListenerInfo> listeners;
|
||||
/**
|
||||
* Set of all servers.
|
||||
*/
|
||||
private Map<String, ServerInfo> servers;
|
||||
|
||||
public void load()
|
||||
{
|
||||
ConfigurationAdapter adapter = ProxyServer.getInstance().getConfigurationAdapter();
|
||||
|
||||
timeout = adapter.getInt("timeout", timeout);
|
||||
uuid = adapter.getString("stats", uuid);
|
||||
|
||||
DefaultTabList tab = DefaultTabList.valueOf(adapter.getString("tab_list", "GLOBAL_PING"));
|
||||
if (tab == null)
|
||||
{
|
||||
tab = DefaultTabList.GLOBAL_PING;
|
||||
}
|
||||
switch (tab)
|
||||
{
|
||||
case GLOBAL:
|
||||
ProxyServer.getInstance().setTabListHandler(new GlobalTabList());
|
||||
break;
|
||||
case GLOBAL_PING:
|
||||
ProxyServer.getInstance().setTabListHandler(new GlobalPingTabList());
|
||||
break;
|
||||
case SERVER:
|
||||
ProxyServer.getInstance().setTabListHandler(new ServerUniqueTabList());
|
||||
break;
|
||||
}
|
||||
|
||||
listeners = adapter.getListeners();
|
||||
Preconditions.checkArgument(listeners != null && !listeners.isEmpty(), "No listeners defined.");
|
||||
|
||||
servers = adapter.getServers();
|
||||
Preconditions.checkArgument(servers != null && !servers.isEmpty(), "No servers defined");
|
||||
|
||||
for (ListenerInfo listener : listeners)
|
||||
{
|
||||
Preconditions.checkArgument(servers.containsKey(listener.getDefaultServer()));
|
||||
}
|
||||
}
|
||||
}
|
205
proxy/src/main/java/net/md_5/bungee/config/YamlConfig.java
Normal file
205
proxy/src/main/java/net/md_5/bungee/config/YamlConfig.java
Normal file
@ -0,0 +1,205 @@
|
||||
package net.md_5.bungee.config;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import net.md_5.bungee.Util;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.config.ConfigurationAdapter;
|
||||
import net.md_5.bungee.api.config.ListenerInfo;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
import org.yaml.snakeyaml.DumperOptions;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
public class YamlConfig implements ConfigurationAdapter
|
||||
{
|
||||
|
||||
private boolean loaded;
|
||||
private Yaml yaml;
|
||||
private Map config;
|
||||
private final File file = new File("config.yml");
|
||||
|
||||
public void load()
|
||||
{
|
||||
try
|
||||
{
|
||||
file.createNewFile();
|
||||
DumperOptions options = new DumperOptions();
|
||||
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
||||
yaml = new Yaml(options);
|
||||
|
||||
try (InputStream is = new FileInputStream(file))
|
||||
{
|
||||
config = (Map) yaml.load(is);
|
||||
}
|
||||
|
||||
if (config == null)
|
||||
{
|
||||
config = new HashMap();
|
||||
}
|
||||
|
||||
loaded = true;
|
||||
} catch (IOException ex)
|
||||
{
|
||||
throw new RuntimeException("Could not load configuration!", ex);
|
||||
}
|
||||
|
||||
Map<String, Object> permissions = get("permissions", new HashMap<String, Object>());
|
||||
if (permissions.isEmpty())
|
||||
{
|
||||
permissions.put("default", Arrays.asList(new String[]
|
||||
{
|
||||
"bungeecord.command.server", "bungeecord.command.list"
|
||||
}));
|
||||
permissions.put("admin", Arrays.asList(new String[]
|
||||
{
|
||||
"bungeecord.command.alert", "bungeecord.command.end", "bungeecord.command.ip", "bungeecord.command.reload"
|
||||
}));
|
||||
}
|
||||
|
||||
Map<String, Object> groups = get("groups", new HashMap<String, Object>());
|
||||
if (groups.isEmpty())
|
||||
{
|
||||
groups.put("md_5", Collections.singletonList("admin"));
|
||||
}
|
||||
}
|
||||
|
||||
private <T> T get(String path, T def)
|
||||
{
|
||||
if (!loaded)
|
||||
{
|
||||
load();
|
||||
}
|
||||
return get(path, def, config);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T get(String path, T def, Map submap)
|
||||
{
|
||||
if (!loaded)
|
||||
{
|
||||
load();
|
||||
}
|
||||
|
||||
int index = path.indexOf('.');
|
||||
if (index == -1)
|
||||
{
|
||||
Object val = submap.get(path);
|
||||
if (val == null)
|
||||
{
|
||||
val = def;
|
||||
submap.put(path, def);
|
||||
save();
|
||||
}
|
||||
return (T) val;
|
||||
} else
|
||||
{
|
||||
String first = path.substring(0, index);
|
||||
String second = path.substring(index + 1, path.length());
|
||||
Map sub = (Map) submap.get(first);
|
||||
return (sub != null) ? get(second, def, sub) : def;
|
||||
}
|
||||
}
|
||||
|
||||
private void save()
|
||||
{
|
||||
try
|
||||
{
|
||||
try (FileWriter wr = new FileWriter(file))
|
||||
{
|
||||
yaml.dump(config, wr);
|
||||
}
|
||||
} catch (IOException ex)
|
||||
{
|
||||
ProxyServer.getInstance().getLogger().log(Level.WARNING, "Could not save config", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(String path, int def)
|
||||
{
|
||||
return get(path, def);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(String path, String def)
|
||||
{
|
||||
return get(path, def);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Map<String, ServerInfo> getServers()
|
||||
{
|
||||
Map<String, Map<String, Object>> base = get("servers", (Map) Collections.singletonMap("lobby", new HashMap<>()));
|
||||
Map<String, ServerInfo> ret = new HashMap<>();
|
||||
|
||||
for (Map.Entry<String, Map<String, Object>> entry : base.entrySet())
|
||||
{
|
||||
Map<String, Object> val = entry.getValue();
|
||||
String name = entry.getKey();
|
||||
String addr = get("address", "localhost:25565", val);
|
||||
InetSocketAddress address = Util.getAddr(addr);
|
||||
ServerInfo info = new ServerInfo(name, address);
|
||||
ret.put(name, info);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Collection<ListenerInfo> getListeners()
|
||||
{
|
||||
Collection<Map<String, Object>> base = get("listeners", (Collection) Arrays.asList(new Map[]
|
||||
{
|
||||
new HashMap()
|
||||
}));
|
||||
Map<String, String> forcedDef = new HashMap<>();
|
||||
forcedDef.put("pvp.md-5.net", "pvp");
|
||||
|
||||
Collection<ListenerInfo> ret = new HashSet<>();
|
||||
|
||||
for (Map<String, Object> val : base)
|
||||
{
|
||||
String motd = get("motd", "Another Bungee server", val);
|
||||
int maxPlayers = get("max_players", 1, val);
|
||||
String defaultServer = get("default_server", "lobby", val);
|
||||
boolean forceDefault = get("force_default_server", false, val);
|
||||
String host = get("host", "0.0.0.0:25577", val);
|
||||
InetSocketAddress address = Util.getAddr(host);
|
||||
Map<String, String> forced = get("forced_hosts", forcedDef, val);
|
||||
ListenerInfo info = new ListenerInfo(address, motd, maxPlayers, defaultServer, forceDefault, forced);
|
||||
ret.add(info);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Collection<String> getGroups(String player)
|
||||
{
|
||||
Collection<String> groups = get("groups." + player, Collections.EMPTY_LIST);
|
||||
Collection<String> ret = new HashSet<>(groups);
|
||||
ret.add("default");
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Collection<String> getPermissions(String group)
|
||||
{
|
||||
return get("permissions." + group, Collections.EMPTY_SET);
|
||||
}
|
||||
}
|
@ -2,11 +2,8 @@ package net.md_5.bungee.packet;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import net.md_5.bungee.Util;
|
||||
import net.md_5.mendax.PacketDefinitions;
|
||||
import net.md_5.mendax.datainput.DataInputPacketReader;
|
||||
|
||||
/**
|
@ -0,0 +1,32 @@
|
||||
package net.md_5.bungee.tablist;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import net.md_5.bungee.BungeeCord;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.packet.PacketC9PlayerListItem;
|
||||
|
||||
public class GlobalPingTabList extends GlobalTabList
|
||||
{
|
||||
|
||||
private static final int PING_THRESHOLD = 20;
|
||||
private final Map<ProxiedPlayer, Integer> lastPings = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void onDisconnect(ProxiedPlayer player)
|
||||
{
|
||||
lastPings.remove(player);
|
||||
super.onDisconnect(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPingChange(ProxiedPlayer player, int ping)
|
||||
{
|
||||
Integer lastPing = lastPings.get(player);
|
||||
if (lastPing == null || (ping - PING_THRESHOLD > lastPing && ping + PING_THRESHOLD < lastPing))
|
||||
{
|
||||
BungeeCord.getInstance().broadcast(new PacketC9PlayerListItem(player.getDisplayName(), true, ping));
|
||||
lastPings.put(player, ping);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package net.md_5.bungee.tablist;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import lombok.Synchronized;
|
||||
import net.md_5.bungee.BungeeCord;
|
||||
import net.md_5.bungee.UserConnection;
|
||||
import net.md_5.bungee.api.TabListHandler;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.packet.PacketC9PlayerListItem;
|
||||
|
||||
public class GlobalTabList implements TabListHandler
|
||||
{
|
||||
|
||||
private final Set<ProxiedPlayer> sentPings = new HashSet<>();
|
||||
|
||||
@Override
|
||||
public void onConnect(ProxiedPlayer player)
|
||||
{
|
||||
for (UserConnection c : BungeeCord.getInstance().connections.values())
|
||||
{
|
||||
c.packetQueue.add(new PacketC9PlayerListItem(c.displayName, true, c.getPing()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Synchronized(value = "sentPings")
|
||||
public void onPingChange(ProxiedPlayer player, int ping)
|
||||
{
|
||||
if (!sentPings.contains(player))
|
||||
{
|
||||
BungeeCord.getInstance().broadcast(new PacketC9PlayerListItem(player.getDisplayName(), true, player.getPing()));
|
||||
sentPings.add(player);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Synchronized(value = "sentPings")
|
||||
public void onDisconnect(ProxiedPlayer player)
|
||||
{
|
||||
BungeeCord.getInstance().broadcast(new PacketC9PlayerListItem(player.getDisplayName(), false, 9999));
|
||||
sentPings.remove(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServerChange(ProxiedPlayer player)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onListUpdate(ProxiedPlayer player, String name, boolean online, int ping)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
package net.md_5.bungee.tablist;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import net.md_5.bungee.UserConnection;
|
||||
import net.md_5.bungee.api.TabListHandler;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.packet.PacketC9PlayerListItem;
|
||||
|
||||
public class ServerUniqueTabList implements TabListHandler
|
||||
{
|
||||
|
||||
private final Map<ProxiedPlayer, Set<String>> sentUsernames = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void onConnect(ProxiedPlayer player)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPingChange(ProxiedPlayer player, int ping)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnect(ProxiedPlayer player)
|
||||
{
|
||||
sentUsernames.remove(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServerChange(ProxiedPlayer player)
|
||||
{
|
||||
Set<String> usernames = sentUsernames.get(player);
|
||||
if (usernames != null)
|
||||
{
|
||||
synchronized (usernames)
|
||||
{
|
||||
for (String username : usernames)
|
||||
{
|
||||
((UserConnection) player).packetQueue.add(new PacketC9PlayerListItem(username, false, 9999));
|
||||
}
|
||||
usernames.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onListUpdate(ProxiedPlayer player, String name, boolean online, int ping)
|
||||
{
|
||||
Set<String> usernames = sentUsernames.get(player);
|
||||
if (usernames == null)
|
||||
{
|
||||
usernames = new HashSet<>();
|
||||
sentUsernames.put(player, usernames);
|
||||
}
|
||||
|
||||
synchronized (usernames)
|
||||
{
|
||||
if (online)
|
||||
{
|
||||
usernames.add(name);
|
||||
} else
|
||||
{
|
||||
usernames.remove(name);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,284 +0,0 @@
|
||||
package net.md_5.bungee;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.logging.Level;
|
||||
import static net.md_5.bungee.Logger.$;
|
||||
import net.md_5.bungee.command.*;
|
||||
import net.md_5.bungee.packet.DefinedPacket;
|
||||
import net.md_5.bungee.packet.PacketFAPluginMessage;
|
||||
import net.md_5.bungee.plugin.JavaPluginManager;
|
||||
import net.md_5.bungee.tablist.GlobalPingTabList;
|
||||
import net.md_5.bungee.tablist.GlobalTabList;
|
||||
import net.md_5.bungee.tablist.ServerUniqueTabList;
|
||||
import net.md_5.bungee.tablist.TabListHandler;
|
||||
|
||||
/**
|
||||
* Main BungeeCord proxy class.
|
||||
*/
|
||||
public class BungeeCord
|
||||
{
|
||||
|
||||
/**
|
||||
* Server protocol version.
|
||||
*/
|
||||
public static final int PROTOCOL_VERSION = 51;
|
||||
/**
|
||||
* Server game version.
|
||||
*/
|
||||
public static final String GAME_VERSION = "1.4.6";
|
||||
/**
|
||||
* Current software instance.
|
||||
*/
|
||||
public static BungeeCord instance;
|
||||
/**
|
||||
* Current operation state.
|
||||
*/
|
||||
public volatile boolean isRunning;
|
||||
/**
|
||||
* Configuration.
|
||||
*/
|
||||
public final Configuration config = new Configuration();
|
||||
/**
|
||||
* Thread pool.
|
||||
*/
|
||||
public final ExecutorService threadPool = Executors.newCachedThreadPool();
|
||||
/**
|
||||
* locations.yml save thread.
|
||||
*/
|
||||
private final ReconnectSaveThread saveThread = new ReconnectSaveThread();
|
||||
/**
|
||||
* Server socket listener.
|
||||
*/
|
||||
private ListenThread listener;
|
||||
/**
|
||||
* Current version.
|
||||
*/
|
||||
public static String version = (BungeeCord.class.getPackage().getImplementationVersion() == null) ? "unknown" : BungeeCord.class.getPackage().getImplementationVersion();
|
||||
/**
|
||||
* Fully qualified connections.
|
||||
*/
|
||||
public Map<String, UserConnection> connections = new ConcurrentHashMap<>();
|
||||
/**
|
||||
* Registered commands.
|
||||
*/
|
||||
public Map<String, Command> commandMap = new HashMap<>();
|
||||
/**
|
||||
* Tab list handler
|
||||
*/
|
||||
public TabListHandler tabListHandler;
|
||||
/**
|
||||
* Registered Global Plugin Channels
|
||||
*/
|
||||
public Queue<String> globalPluginChannels = new ConcurrentLinkedQueue<>();
|
||||
/**
|
||||
* Plugin manager.
|
||||
*/
|
||||
public final JavaPluginManager pluginManager = new JavaPluginManager();
|
||||
|
||||
|
||||
{
|
||||
commandMap.put("greload",new CommandReload());
|
||||
commandMap.put("end", new CommandEnd());
|
||||
commandMap.put("glist", new CommandList());
|
||||
commandMap.put("server", new CommandServer());
|
||||
commandMap.put("ip", new CommandIP());
|
||||
commandMap.put("alert", new CommandAlert());
|
||||
commandMap.put("motd", new CommandMotd());
|
||||
commandMap.put("bungee", new CommandBungee());
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a new instance of BungeeCord.
|
||||
*
|
||||
* @param args command line arguments, currently none are used
|
||||
* @throws IOException when the server cannot be started
|
||||
*/
|
||||
public static void main(String[] args) throws IOException
|
||||
{
|
||||
instance = new BungeeCord();
|
||||
$().info("Enabled BungeeCord version " + instance.version);
|
||||
instance.start();
|
||||
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
|
||||
while (instance.isRunning)
|
||||
{
|
||||
String line = br.readLine();
|
||||
if (line != null)
|
||||
{
|
||||
boolean handled = instance.dispatchCommand(line, ConsoleCommandSender.instance);
|
||||
if (!handled)
|
||||
{
|
||||
System.err.println("Command not found");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch a command by formatting the arguments and then executing it.
|
||||
*
|
||||
* @param commandLine the entire command and arguments string
|
||||
* @param sender which executed the command
|
||||
* @return whether the command was handled or not.
|
||||
*/
|
||||
public boolean dispatchCommand(String commandLine, CommandSender sender)
|
||||
{
|
||||
String[] split = commandLine.trim().split(" ");
|
||||
String commandName = split[0].toLowerCase();
|
||||
Command command = commandMap.get(commandName);
|
||||
if (config.disabledCommands != null && config.disabledCommands.contains(commandName))
|
||||
{
|
||||
return false;
|
||||
} else if (command != null)
|
||||
{
|
||||
String[] args = Arrays.copyOfRange(split, 1, split.length);
|
||||
try
|
||||
{
|
||||
command.execute(sender, args);
|
||||
} catch (Exception ex)
|
||||
{
|
||||
sender.sendMessage(ChatColor.RED + "An error occurred while executing this command!");
|
||||
$().severe("----------------------- [Start of command error] -----------------------");
|
||||
$().log(Level.SEVERE, "", ex);
|
||||
$().severe("----------------------- [End of command error] -----------------------");
|
||||
}
|
||||
}
|
||||
|
||||
return command != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start this proxy instance by loading the configuration, plugins and
|
||||
* starting the connect thread.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public void start() throws IOException
|
||||
{
|
||||
config.load();
|
||||
isRunning = true;
|
||||
|
||||
pluginManager.loadPlugins();
|
||||
|
||||
switch (config.tabList)
|
||||
{
|
||||
default:
|
||||
case 1:
|
||||
tabListHandler = new GlobalPingTabList();
|
||||
break;
|
||||
case 2:
|
||||
tabListHandler = new GlobalTabList();
|
||||
break;
|
||||
case 3:
|
||||
tabListHandler = new ServerUniqueTabList();
|
||||
break;
|
||||
}
|
||||
|
||||
// Add RubberBand to the global plugin channel list
|
||||
globalPluginChannels.add("RubberBand");
|
||||
|
||||
InetSocketAddress addr = Util.getAddr(config.bindHost);
|
||||
listener = new ListenThread(addr);
|
||||
listener.start();
|
||||
|
||||
saveThread.start();
|
||||
$().info("Listening on " + addr);
|
||||
|
||||
if (config.metricsEnabled)
|
||||
{
|
||||
new Metrics().start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy this proxy instance cleanly by kicking all users, saving the
|
||||
* configuration and closing all sockets.
|
||||
*/
|
||||
public void stop()
|
||||
{
|
||||
this.isRunning = false;
|
||||
$().info("Disabling plugin");
|
||||
pluginManager.onDisable();
|
||||
|
||||
$().info("Closing listen thread");
|
||||
try
|
||||
{
|
||||
listener.socket.close();
|
||||
listener.join();
|
||||
} catch (InterruptedException | IOException ex)
|
||||
{
|
||||
$().severe("Could not close listen thread");
|
||||
}
|
||||
|
||||
$().info("Closing pending connections");
|
||||
threadPool.shutdown();
|
||||
|
||||
$().info("Disconnecting " + connections.size() + " connections");
|
||||
for (UserConnection user : connections.values())
|
||||
{
|
||||
user.disconnect("Proxy restarting, brb.");
|
||||
}
|
||||
|
||||
$().info("Saving reconnect locations");
|
||||
saveThread.interrupt();
|
||||
try
|
||||
{
|
||||
saveThread.join();
|
||||
} catch (InterruptedException ex)
|
||||
{
|
||||
}
|
||||
|
||||
$().info("Thank you and goodbye");
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Miscellaneous method to set options on a socket based on those in the
|
||||
* configuration.
|
||||
*
|
||||
* @param socket to set the options on
|
||||
* @throws IOException when the underlying set methods thrown an exception
|
||||
*/
|
||||
public void setSocketOptions(Socket socket) throws IOException
|
||||
{
|
||||
socket.setSoTimeout(config.timeout);
|
||||
socket.setTrafficClass(0x18);
|
||||
socket.setTcpNoDelay(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts a packet to all clients that is connected to this instance.
|
||||
*
|
||||
* @param packet the packet to send
|
||||
*/
|
||||
public void broadcast(DefinedPacket packet)
|
||||
{
|
||||
for (UserConnection con : connections.values())
|
||||
{
|
||||
con.packetQueue.add(packet);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a plugin channel for all users
|
||||
*
|
||||
* @param channel name
|
||||
*/
|
||||
public void registerPluginChannel(String channel)
|
||||
{
|
||||
globalPluginChannels.add(channel);
|
||||
broadcast(new PacketFAPluginMessage("REGISTER", channel.getBytes()));
|
||||
}
|
||||
}
|
@ -1,314 +0,0 @@
|
||||
package net.md_5.bungee;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import static net.md_5.bungee.Logger.$;
|
||||
import net.md_5.bungee.command.CommandSender;
|
||||
import net.md_5.bungee.command.ConsoleCommandSender;
|
||||
import org.yaml.snakeyaml.DumperOptions;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
/**
|
||||
* Core configuration for the proxy.
|
||||
*/
|
||||
public class Configuration
|
||||
{
|
||||
|
||||
/**
|
||||
* Reconnect locations file.
|
||||
*/
|
||||
private transient File reconnect = new File("locations.yml");
|
||||
/**
|
||||
* Loaded reconnect locations.
|
||||
*/
|
||||
private transient Map<String, String> reconnectLocations;
|
||||
/**
|
||||
* Config file.
|
||||
*/
|
||||
private transient File file = new File("config.yml");
|
||||
/**
|
||||
* Yaml instance.
|
||||
*/
|
||||
private transient Yaml yaml;
|
||||
/**
|
||||
* Loaded config.
|
||||
*/
|
||||
private transient Map<String, Object> config;
|
||||
/**
|
||||
* Bind host.
|
||||
*/
|
||||
public String bindHost = "0.0.0.0:25577";
|
||||
/**
|
||||
* Server ping motd.
|
||||
*/
|
||||
public String motd = "BungeeCord Proxy Instance";
|
||||
/**
|
||||
* Name of default server.
|
||||
*/
|
||||
public String defaultServerName = "default";
|
||||
/**
|
||||
* Max players as displayed in list ping. Soft limit.
|
||||
*/
|
||||
public int maxPlayers = 1;
|
||||
/**
|
||||
* Tab list 1: For a tab list that is global over all server (using their
|
||||
* Minecraft name) and updating their ping frequently 2: Same as 1 but does
|
||||
* not update their ping frequently, just once, 3: Makes the individual
|
||||
* servers handle the tab list (server unique).
|
||||
*/
|
||||
public int tabList = 1;
|
||||
/**
|
||||
* Socket timeout.
|
||||
*/
|
||||
public int timeout = 15000;
|
||||
/**
|
||||
* All servers.
|
||||
*/
|
||||
public Map<String, String> servers = new HashMap<String, String>()
|
||||
{
|
||||
|
||||
{
|
||||
put(defaultServerName, "127.0.0.1:1338");
|
||||
put("pvp", "127.0.0.1:1337");
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Forced servers.
|
||||
*/
|
||||
public Map<String, String> forcedServers = new HashMap<String, String>()
|
||||
{
|
||||
|
||||
{
|
||||
put("pvp.md-5.net", "pvp");
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Proxy admins.
|
||||
*/
|
||||
public List<String> admins = new ArrayList<String>()
|
||||
{
|
||||
|
||||
{
|
||||
add("Insert Admins Here");
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Proxy moderators.
|
||||
*/
|
||||
public List<String> moderators = new ArrayList<String>()
|
||||
{
|
||||
|
||||
{
|
||||
add("Insert Moderators Here");
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Commands which will be blocked completely.
|
||||
*/
|
||||
public List<String> disabledCommands = new ArrayList<String>()
|
||||
{
|
||||
|
||||
{
|
||||
add("glist");
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Maximum number of lines to log before old ones are removed.
|
||||
*/
|
||||
public int logNumLines = 1 << 14;
|
||||
/**
|
||||
* UUID for Metrics.
|
||||
*/
|
||||
public String statsUuid = UUID.randomUUID().toString();
|
||||
public boolean metricsEnabled = true;
|
||||
public boolean forceDefaultServer = false;
|
||||
|
||||
/**
|
||||
* Load the configuration and save default values.
|
||||
*/
|
||||
public void load()
|
||||
{
|
||||
try
|
||||
{
|
||||
file.createNewFile();
|
||||
DumperOptions options = new DumperOptions();
|
||||
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
||||
yaml = new Yaml(options);
|
||||
|
||||
try (InputStream is = new FileInputStream(file))
|
||||
{
|
||||
config = (Map) yaml.load(is);
|
||||
}
|
||||
|
||||
if (config == null)
|
||||
{
|
||||
config = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
$().info("-------------- Loading configuration ----------------");
|
||||
for (Field field : getClass().getDeclaredFields())
|
||||
{
|
||||
if (!Modifier.isTransient(field.getModifiers()))
|
||||
{
|
||||
String name = Util.normalize(field.getName());
|
||||
try
|
||||
{
|
||||
Object def = field.get(this);
|
||||
Object value = get(name, def);
|
||||
|
||||
field.set(this, value);
|
||||
|
||||
$().info(name + ": " + value);
|
||||
} catch (IllegalAccessException ex)
|
||||
{
|
||||
$().severe("Could not get config node: " + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
$().info("-----------------------------------------------------");
|
||||
|
||||
if (servers.get(defaultServerName) == null)
|
||||
{
|
||||
throw new IllegalArgumentException("Server '" + defaultServerName + "' not defined");
|
||||
}
|
||||
|
||||
if (forcedServers != null)
|
||||
{
|
||||
for (String server : forcedServers.values())
|
||||
{
|
||||
if (!servers.containsKey(server))
|
||||
{
|
||||
throw new IllegalArgumentException("Forced server " + server + " is not defined in servers");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
motd = ChatColor.translateAlternateColorCodes('&', motd);
|
||||
|
||||
reconnect.createNewFile();
|
||||
try (FileInputStream recon = new FileInputStream(reconnect))
|
||||
{
|
||||
reconnectLocations = (Map) yaml.load(recon);
|
||||
}
|
||||
if (reconnectLocations == null)
|
||||
{
|
||||
reconnectLocations = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
} catch (IOException ex)
|
||||
{
|
||||
$().severe("Could not load config!");
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private <T> T get(String path, T def)
|
||||
{
|
||||
if (!config.containsKey(path))
|
||||
{
|
||||
config.put(path, def);
|
||||
save(file, config);
|
||||
}
|
||||
return (T) config.get(path);
|
||||
}
|
||||
|
||||
private void save(File fileToSave, Map toSave)
|
||||
{
|
||||
try
|
||||
{
|
||||
try (FileWriter wr = new FileWriter(fileToSave))
|
||||
{
|
||||
yaml.dump(toSave, wr);
|
||||
}
|
||||
} catch (IOException ex)
|
||||
{
|
||||
$().severe("Could not save config file " + fileToSave);
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get which server a user should be connected to, taking into account their
|
||||
* name and virtual host.
|
||||
*
|
||||
* @param user to get a server for
|
||||
* @param requestedHost the host which they connected to
|
||||
* @return the name of the server which they should be connected to.
|
||||
*/
|
||||
public String getServer(String user, String requestedHost)
|
||||
{
|
||||
String server = (forcedServers == null) ? null : forcedServers.get(requestedHost.toLowerCase());
|
||||
if (server == null)
|
||||
{
|
||||
server = reconnectLocations.get(user);
|
||||
}
|
||||
if (server == null)
|
||||
{
|
||||
server = defaultServerName;
|
||||
}
|
||||
return server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the last server which the user was on.
|
||||
*
|
||||
* @param user the name of the user
|
||||
* @param server which they were last on
|
||||
*/
|
||||
public void setServer(UserConnection user, String server)
|
||||
{
|
||||
reconnectLocations.put(user.username, server);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the connectable address of a server defined in the configuration.
|
||||
*
|
||||
* @param name the friendly name of a server
|
||||
* @return the usable {@link InetSocketAddress} mapped to this server
|
||||
*/
|
||||
public InetSocketAddress getServer(String name)
|
||||
{
|
||||
String server = servers.get((name == null) ? defaultServerName : name);
|
||||
return (server != null) ? Util.getAddr(server) : getServer(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the current mappings of users to servers.
|
||||
*/
|
||||
public void saveHosts()
|
||||
{
|
||||
save(reconnect, reconnectLocations);
|
||||
$().info("Saved reconnect locations to " + reconnect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the highest permission a player has.
|
||||
*
|
||||
* @param sender to get permissions of
|
||||
* @return their permission
|
||||
*/
|
||||
public Permission getPermission(CommandSender sender)
|
||||
{
|
||||
Permission permission = Permission.DEFAULT;
|
||||
if (admins.contains(sender.getName()) || sender instanceof ConsoleCommandSender)
|
||||
{
|
||||
permission = Permission.ADMIN;
|
||||
} else if (moderators.contains(sender.getName()))
|
||||
{
|
||||
permission = Permission.MODERATOR;
|
||||
}
|
||||
return permission;
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
package net.md_5.bungee;
|
||||
|
||||
public enum Permission
|
||||
{
|
||||
|
||||
/**
|
||||
* Can access all commands.
|
||||
*/
|
||||
ADMIN,
|
||||
/**
|
||||
* Can access commands which do not affect everyone.
|
||||
*/
|
||||
MODERATOR,
|
||||
/**
|
||||
* Can access other commands.
|
||||
*/
|
||||
DEFAULT;
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
package net.md_5.bungee;
|
||||
|
||||
/**
|
||||
* Class to call the {@link Configuration#saveHosts() } method at 5 minute
|
||||
* intervals.
|
||||
*/
|
||||
public class ReconnectSaveThread extends Thread
|
||||
{
|
||||
|
||||
public ReconnectSaveThread()
|
||||
{
|
||||
super("Location Save Thread");
|
||||
setPriority(Thread.MIN_PRIORITY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
while (BungeeCord.instance.isRunning)
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(5 * 1000 * 60); // 5 minutes
|
||||
} catch (InterruptedException ex)
|
||||
{
|
||||
}
|
||||
BungeeCord.instance.config.saveHosts();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,356 +0,0 @@
|
||||
package net.md_5.bungee;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import net.md_5.bungee.command.CommandSender;
|
||||
import net.md_5.bungee.packet.*;
|
||||
import net.md_5.bungee.plugin.ChatEvent;
|
||||
import net.md_5.bungee.plugin.PluginMessageEvent;
|
||||
import net.md_5.bungee.plugin.PluginMessageEvent.Destination;
|
||||
import net.md_5.bungee.plugin.ServerConnectEvent;
|
||||
|
||||
public class UserConnection extends GenericConnection implements CommandSender
|
||||
{
|
||||
|
||||
public final Packet2Handshake handshake;
|
||||
public Queue<DefinedPacket> packetQueue = new ConcurrentLinkedQueue<>();
|
||||
public List<byte[]> loginPackets = new ArrayList<>();
|
||||
private ServerConnection server;
|
||||
private UpstreamBridge upBridge;
|
||||
private DownstreamBridge downBridge;
|
||||
// reconnect stuff
|
||||
private int clientEntityId;
|
||||
private int serverEntityId;
|
||||
private volatile boolean reconnecting;
|
||||
// ping stuff
|
||||
private int trackingPingId;
|
||||
private long pingTime;
|
||||
private int ping;
|
||||
public UserConnection instance = this;
|
||||
|
||||
public UserConnection(Socket socket, PacketInputStream in, OutputStream out, Packet2Handshake handshake, List<byte[]> loginPackets)
|
||||
{
|
||||
super(socket, in, out);
|
||||
this.handshake = handshake;
|
||||
username = handshake.username;
|
||||
tabListName = handshake.username;
|
||||
this.loginPackets = loginPackets;
|
||||
BungeeCord.instance.connections.put(username, this);
|
||||
BungeeCord.instance.tabListHandler.onJoin(this);
|
||||
}
|
||||
|
||||
public void setTabListName(String newName)
|
||||
{
|
||||
BungeeCord.instance.tabListHandler.onDisconnect(this);
|
||||
tabListName = newName;
|
||||
BungeeCord.instance.tabListHandler.onJoin(this);
|
||||
}
|
||||
|
||||
public void connect(String server)
|
||||
{
|
||||
ServerConnectEvent event = new ServerConnectEvent(this.server == null, this, server);
|
||||
event.setNewServer(server);
|
||||
BungeeCord.instance.pluginManager.onServerConnect(event);
|
||||
if (event.getMessage() != null)
|
||||
{
|
||||
this.sendMessage(event.getMessage());
|
||||
}
|
||||
if (event.getNewServer() == null)
|
||||
{
|
||||
if (event.isFirstTime())
|
||||
{
|
||||
event.setNewServer(BungeeCord.instance.config.defaultServerName);
|
||||
} else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
InetSocketAddress addr = BungeeCord.instance.config.getServer(event.getNewServer());
|
||||
connect(server, addr);
|
||||
}
|
||||
|
||||
private void connect(String name, InetSocketAddress serverAddr)
|
||||
{
|
||||
BungeeCord.instance.tabListHandler.onServerChange(this);
|
||||
try
|
||||
{
|
||||
reconnecting = true;
|
||||
|
||||
if (server != null)
|
||||
{
|
||||
out.write(new Packet9Respawn((byte) 1, (byte) 0, (byte) 0, (short) 256, "DEFAULT").getPacket());
|
||||
out.write(new Packet9Respawn((byte) -1, (byte) 0, (byte) 0, (short) 256, "DEFAULT").getPacket());
|
||||
}
|
||||
|
||||
ServerConnection newServer = ServerConnection.connect(this, name, serverAddr, handshake, true);
|
||||
if (server == null)
|
||||
{
|
||||
clientEntityId = newServer.loginPacket.entityId;
|
||||
serverEntityId = newServer.loginPacket.entityId;
|
||||
out.write(newServer.loginPacket.getPacket());
|
||||
upBridge = new UpstreamBridge();
|
||||
upBridge.start();
|
||||
} else
|
||||
{
|
||||
try
|
||||
{
|
||||
downBridge.interrupt();
|
||||
downBridge.join();
|
||||
} catch (InterruptedException ie)
|
||||
{
|
||||
}
|
||||
|
||||
server.disconnect("Quitting");
|
||||
|
||||
Packet1Login login = newServer.loginPacket;
|
||||
serverEntityId = login.entityId;
|
||||
out.write(new Packet9Respawn(login.dimension, login.difficulty, login.gameMode, (short) 256, login.levelType).getPacket());
|
||||
}
|
||||
reconnecting = false;
|
||||
downBridge = new DownstreamBridge();
|
||||
server = newServer;
|
||||
downBridge.start();
|
||||
} catch (KickException ex)
|
||||
{
|
||||
destroySelf(ex.getMessage());
|
||||
} catch (Exception ex)
|
||||
{
|
||||
destroySelf("Could not connect to server - " + ex.getClass().getSimpleName());
|
||||
ex.printStackTrace(); // TODO: Remove
|
||||
}
|
||||
}
|
||||
|
||||
public String getServer()
|
||||
{
|
||||
return server.name;
|
||||
}
|
||||
|
||||
public SocketAddress getAddress()
|
||||
{
|
||||
return socket.getRemoteSocketAddress();
|
||||
}
|
||||
|
||||
public int getPing()
|
||||
{
|
||||
return ping;
|
||||
}
|
||||
|
||||
private void setPing(int ping)
|
||||
{
|
||||
BungeeCord.instance.tabListHandler.onPingChange(this, ping);
|
||||
this.ping = ping;
|
||||
}
|
||||
|
||||
private void destroySelf(String reason)
|
||||
{
|
||||
if (BungeeCord.instance.isRunning)
|
||||
{
|
||||
BungeeCord.instance.connections.remove(username);
|
||||
}
|
||||
disconnect(reason);
|
||||
if (server != null)
|
||||
{
|
||||
server.disconnect("Quitting");
|
||||
BungeeCord.instance.config.setServer(this, server.name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect(String reason)
|
||||
{
|
||||
BungeeCord.instance.tabListHandler.onDisconnect(this);
|
||||
super.disconnect(reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(String message)
|
||||
{
|
||||
packetQueue.add(new Packet3Chat(message));
|
||||
}
|
||||
|
||||
public void sendPluginMessage(String tag, byte[] data)
|
||||
{
|
||||
server.packetQueue.add(new PacketFAPluginMessage(tag, data));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return username;
|
||||
}
|
||||
|
||||
private class UpstreamBridge extends Thread
|
||||
{
|
||||
|
||||
public UpstreamBridge()
|
||||
{
|
||||
super("Upstream Bridge - " + username);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
while (!socket.isClosed())
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] packet = in.readPacket();
|
||||
boolean sendPacket = true;
|
||||
|
||||
int id = Util.getId(packet);
|
||||
if (id == 0xFA)
|
||||
{
|
||||
// Call the onPluginMessage event
|
||||
PacketFAPluginMessage message = new PacketFAPluginMessage(packet);
|
||||
PluginMessageEvent event = new PluginMessageEvent(Destination.SERVER, instance);
|
||||
event.setTag(message.tag);
|
||||
event.setData(new String(message.data));
|
||||
BungeeCord.instance.pluginManager.onPluginMessage(event);
|
||||
|
||||
if (event.isCancelled())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
} else if (id == 0x03)
|
||||
{
|
||||
Packet3Chat chat = new Packet3Chat(packet);
|
||||
String message = chat.message;
|
||||
if (message.startsWith("/"))
|
||||
{
|
||||
sendPacket = !BungeeCord.instance.dispatchCommand(message.substring(1), UserConnection.this);
|
||||
} else
|
||||
{
|
||||
ChatEvent chatEvent = new ChatEvent(ChatEvent.Destination.SERVER, instance);
|
||||
chatEvent.setText(message);
|
||||
BungeeCord.instance.pluginManager.onChat(chatEvent);
|
||||
sendPacket = !chatEvent.isCancelled();
|
||||
}
|
||||
} else if (id == 0x00)
|
||||
{
|
||||
if (trackingPingId == new Packet0KeepAlive(packet).id)
|
||||
{
|
||||
setPing((int) (System.currentTimeMillis() - pingTime));
|
||||
}
|
||||
}
|
||||
|
||||
while (!server.packetQueue.isEmpty())
|
||||
{
|
||||
DefinedPacket p = server.packetQueue.poll();
|
||||
if (p != null)
|
||||
{
|
||||
server.out.write(p.getPacket());
|
||||
}
|
||||
}
|
||||
|
||||
EntityMap.rewrite(packet, clientEntityId, serverEntityId);
|
||||
if (sendPacket && !server.socket.isClosed())
|
||||
{
|
||||
server.out.write(packet);
|
||||
}
|
||||
} catch (IOException ex)
|
||||
{
|
||||
destroySelf("Reached end of stream");
|
||||
} catch (Exception ex)
|
||||
{
|
||||
destroySelf(Util.exception(ex));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class DownstreamBridge extends Thread
|
||||
{
|
||||
|
||||
public DownstreamBridge()
|
||||
{
|
||||
super("Downstream Bridge - " + username);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!reconnecting)
|
||||
{
|
||||
byte[] packet = server.in.readPacket();
|
||||
|
||||
int id = Util.getId(packet);
|
||||
if (id == 0xFA)
|
||||
{
|
||||
// Call the onPluginMessage event
|
||||
PacketFAPluginMessage message = new PacketFAPluginMessage(packet);
|
||||
PluginMessageEvent event = new PluginMessageEvent(Destination.CLIENT, instance);
|
||||
event.setTag(message.tag);
|
||||
event.setData(new String(message.data));
|
||||
BungeeCord.instance.pluginManager.onPluginMessage(event);
|
||||
|
||||
if (event.isCancelled())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
message.tag = event.getTag();
|
||||
message.data = event.getData().getBytes();
|
||||
|
||||
// Allow a message for killing the connection outright
|
||||
if (message.tag.equals("KillCon"))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (message.tag.equals("RubberBand"))
|
||||
{
|
||||
String server = new String(message.data);
|
||||
connect(server);
|
||||
break;
|
||||
}
|
||||
} else if (id == 0x00)
|
||||
{
|
||||
trackingPingId = new Packet0KeepAlive(packet).id;
|
||||
pingTime = System.currentTimeMillis();
|
||||
} else if (id == 0x03)
|
||||
{
|
||||
Packet3Chat chat = new Packet3Chat(packet);
|
||||
String message = chat.message;
|
||||
ChatEvent chatEvent = new ChatEvent(ChatEvent.Destination.CLIENT, instance);
|
||||
chatEvent.setText(message);
|
||||
BungeeCord.instance.pluginManager.onChat(chatEvent);
|
||||
if (chatEvent.isCancelled())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
} else if (id == 0xC9)
|
||||
{
|
||||
if (!BungeeCord.instance.tabListHandler.onPacketC9(UserConnection.this, new PacketC9PlayerListItem(packet)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
while (!packetQueue.isEmpty())
|
||||
{
|
||||
DefinedPacket p = packetQueue.poll();
|
||||
if (p != null)
|
||||
{
|
||||
out.write(p.getPacket());
|
||||
}
|
||||
}
|
||||
|
||||
EntityMap.rewrite(packet, serverEntityId, clientEntityId);
|
||||
out.write(packet);
|
||||
}
|
||||
} catch (Exception ex)
|
||||
{
|
||||
destroySelf(Util.exception(ex));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
package net.md_5.bungee.command;
|
||||
|
||||
import net.md_5.bungee.BungeeCord;
|
||||
import net.md_5.bungee.Permission;
|
||||
|
||||
/**
|
||||
* Class which represents a proxy command. The {@link #execute(net.md_5.bungee.command.CommandSender, java.lang.String[])
|
||||
* } method will be called to dispatch the command.
|
||||
*/
|
||||
public abstract class Command
|
||||
{
|
||||
|
||||
/**
|
||||
* Execute this command.
|
||||
*
|
||||
* @param sender the sender executing this command
|
||||
* @param args the parameters to this command, does not include the '/' or
|
||||
* the original command.
|
||||
*/
|
||||
public abstract void execute(CommandSender sender, String[] args);
|
||||
|
||||
public Permission getPermission(CommandSender sender)
|
||||
{
|
||||
return BungeeCord.instance.config.getPermission(sender);
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
package net.md_5.bungee.command;
|
||||
|
||||
import net.md_5.bungee.BungeeCord;
|
||||
import net.md_5.bungee.ChatColor;
|
||||
|
||||
public class CommandBungee extends Command
|
||||
{
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args)
|
||||
{
|
||||
sender.sendMessage(ChatColor.BLUE + "This server is running BungeeCord version " + BungeeCord.version + " by md_5");
|
||||
sender.sendMessage(ChatColor.BLUE + "Your current permission level is " + getPermission(sender).name());
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
package net.md_5.bungee.command;
|
||||
|
||||
import net.md_5.bungee.BungeeCord;
|
||||
import net.md_5.bungee.ChatColor;
|
||||
import net.md_5.bungee.Permission;
|
||||
|
||||
/**
|
||||
* Command to terminate the proxy instance. May only be used by the console.
|
||||
*/
|
||||
public class CommandEnd extends Command
|
||||
{
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args)
|
||||
{
|
||||
if (getPermission(sender) != Permission.ADMIN)
|
||||
{
|
||||
sender.sendMessage(ChatColor.RED + "You do not have permission to use this command");
|
||||
} else
|
||||
{
|
||||
BungeeCord.instance.stop();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
package net.md_5.bungee.command;
|
||||
|
||||
import net.md_5.bungee.BungeeCord;
|
||||
import net.md_5.bungee.ChatColor;
|
||||
import net.md_5.bungee.Permission;
|
||||
|
||||
/**
|
||||
* Command to set a temp copy of the motd in real-time without stopping the
|
||||
* proxy.
|
||||
*/
|
||||
public class CommandMotd extends Command
|
||||
{
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args)
|
||||
{
|
||||
if (getPermission(sender) != Permission.ADMIN)
|
||||
{
|
||||
sender.sendMessage(ChatColor.RED + "You do not have permission to use this command");
|
||||
} else
|
||||
{
|
||||
String newMOTD = "";
|
||||
for (String s : args)
|
||||
{
|
||||
newMOTD = newMOTD + s + " ";
|
||||
}
|
||||
newMOTD = newMOTD.substring(0, newMOTD.length() - 1);
|
||||
BungeeCord.instance.config.motd = ChatColor.translateAlternateColorCodes('&', newMOTD);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
package net.md_5.bungee.command;
|
||||
|
||||
import net.md_5.bungee.BungeeCord;
|
||||
import net.md_5.bungee.ChatColor;
|
||||
import net.md_5.bungee.Permission;
|
||||
|
||||
public class CommandReload extends Command
|
||||
{
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args)
|
||||
{
|
||||
if (getPermission(sender) != Permission.ADMIN)
|
||||
{
|
||||
sender.sendMessage(ChatColor.RED + "You do not have permission to execute this command!");
|
||||
return;
|
||||
}
|
||||
BungeeCord.instance.config.load();
|
||||
sender.sendMessage(ChatColor.GREEN + "Reloaded config, please restart if you have any issues");
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
package net.md_5.bungee.command;
|
||||
|
||||
public interface CommandSender
|
||||
{
|
||||
|
||||
/**
|
||||
* Sends a message to the client at the earliest available opportunity.
|
||||
*
|
||||
* @param message the message to send
|
||||
*/
|
||||
public abstract void sendMessage(String message);
|
||||
|
||||
/**
|
||||
* Get the senders name or CONSOLE for console.
|
||||
*
|
||||
* @return the friendly name of the player.
|
||||
*/
|
||||
public abstract String getName();
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
package net.md_5.bungee.command;
|
||||
|
||||
import java.util.Collection;
|
||||
import net.md_5.bungee.BungeeCord;
|
||||
import net.md_5.bungee.ChatColor;
|
||||
import net.md_5.bungee.UserConnection;
|
||||
|
||||
/**
|
||||
* Command to list and switch a player between available servers.
|
||||
*/
|
||||
public class CommandServer extends Command
|
||||
{
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args)
|
||||
{
|
||||
if (!(sender instanceof UserConnection))
|
||||
{
|
||||
return;
|
||||
}
|
||||
UserConnection con = (UserConnection) sender;
|
||||
Collection<String> servers = BungeeCord.instance.config.servers.keySet();
|
||||
if (args.length <= 0)
|
||||
{
|
||||
StringBuilder serverList = new StringBuilder();
|
||||
for (String server : servers)
|
||||
{
|
||||
serverList.append(server);
|
||||
serverList.append(", ");
|
||||
}
|
||||
serverList.setLength(serverList.length() - 2);
|
||||
con.sendMessage(ChatColor.GOLD + "You may connect to the following servers at this time: " + serverList.toString());
|
||||
} else
|
||||
{
|
||||
String server = args[0];
|
||||
if (!servers.contains(server))
|
||||
{
|
||||
con.sendMessage(ChatColor.RED + "The specified server does not exist");
|
||||
} else if (args[0].equals(con.getServer()))
|
||||
{
|
||||
con.sendMessage(ChatColor.RED + "You are already on this server.");
|
||||
} else
|
||||
{
|
||||
con.connect(server);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
package net.md_5.bungee.command;
|
||||
|
||||
import net.md_5.bungee.ChatColor;
|
||||
|
||||
/**
|
||||
* Command sender representing the proxy console.
|
||||
*/
|
||||
public class ConsoleCommandSender implements CommandSender
|
||||
{
|
||||
|
||||
public static final ConsoleCommandSender instance = new ConsoleCommandSender();
|
||||
|
||||
@Override
|
||||
public void sendMessage(String message)
|
||||
{
|
||||
System.out.println(ChatColor.stripColor(message));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return "CONSOLE";
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
package net.md_5.bungee.plugin;
|
||||
|
||||
/**
|
||||
* An event which may be canceled and this be prevented from happening.
|
||||
*/
|
||||
public interface Cancellable
|
||||
{
|
||||
|
||||
/**
|
||||
* Sets the canceled state of this event.
|
||||
*
|
||||
* @param canceled whether this event is canceled or not
|
||||
*/
|
||||
public void setCancelled(boolean canceled);
|
||||
|
||||
/**
|
||||
* Gets the canceled state of this event.
|
||||
*
|
||||
* @return whether this event is canceled or not
|
||||
*/
|
||||
public boolean isCancelled();
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
package net.md_5.bungee.plugin;
|
||||
|
||||
import lombok.Data;
|
||||
import net.md_5.bungee.UserConnection;
|
||||
|
||||
@Data
|
||||
public class ChatEvent implements Cancellable
|
||||
{
|
||||
|
||||
/**
|
||||
* Canceled state.
|
||||
*/
|
||||
private boolean cancelled;
|
||||
/**
|
||||
* Whether this packet is destined for the server or the client.
|
||||
*/
|
||||
private final Destination destination;
|
||||
/**
|
||||
* User in question.
|
||||
*/
|
||||
private final UserConnection connection;
|
||||
/**
|
||||
* Text contained in this chat.
|
||||
*/
|
||||
private String text;
|
||||
|
||||
/**
|
||||
* An enum that signifies the destination for this packet.
|
||||
*/
|
||||
public enum Destination
|
||||
{
|
||||
|
||||
SERVER,
|
||||
CLIENT
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package net.md_5.bungee.plugin;
|
||||
|
||||
/**
|
||||
* Exception thrown when a plugin could not be loaded for any reason.
|
||||
*/
|
||||
public class InvalidPluginException extends RuntimeException
|
||||
{
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public InvalidPluginException(String message, Throwable cause)
|
||||
{
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public InvalidPluginException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
package net.md_5.bungee.plugin;
|
||||
|
||||
import net.md_5.bungee.BungeeCord;
|
||||
import net.md_5.bungee.command.Command;
|
||||
|
||||
/**
|
||||
* Base class which all proxy plugins should extend.
|
||||
*/
|
||||
public abstract class JavaPlugin
|
||||
{
|
||||
|
||||
/**
|
||||
* Description file.
|
||||
*/
|
||||
PluginDescription description;
|
||||
|
||||
/**
|
||||
* Called on enable.
|
||||
*/
|
||||
public void onEnable()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called on disable.
|
||||
*/
|
||||
public void onDisable()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a user connects with their name and address. To keep things
|
||||
* simple this name has not been checked with minecraft.net.
|
||||
*/
|
||||
public void onHandshake(LoginEvent event)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after a user has been authed with minecraftt.net and is about to
|
||||
* log into the proxy.
|
||||
*/
|
||||
public void onLogin(LoginEvent event)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a user is connecting to a new server.
|
||||
*/
|
||||
public void onServerConnect(ServerConnectEvent event)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a plugin message is sent to the client or server
|
||||
*/
|
||||
public void onPluginMessage(PluginMessageEvent event)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a chat message is sent to the client or server
|
||||
*/
|
||||
public void onChat(ChatEvent event)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a command for use with the proxy.
|
||||
*/
|
||||
protected final void registerCommand(String label, Command command)
|
||||
{
|
||||
BungeeCord.instance.commandMap.put(label, command);
|
||||
}
|
||||
}
|
@ -1,127 +0,0 @@
|
||||
package net.md_5.bungee.plugin;
|
||||
|
||||
import com.google.common.io.PatternFilenameFilter;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.zip.ZipEntry;
|
||||
import lombok.Getter;
|
||||
import static net.md_5.bungee.Logger.$;
|
||||
|
||||
/**
|
||||
* Plugin manager to handle loading and saving other JavaPlugin's. This class is
|
||||
* itself a plugin for ease of use.
|
||||
*/
|
||||
public class JavaPluginManager extends JavaPlugin
|
||||
{
|
||||
|
||||
/**
|
||||
* Set of loaded plugins.
|
||||
*/
|
||||
@Getter
|
||||
private final Set<JavaPlugin> plugins = new HashSet<>();
|
||||
|
||||
/**
|
||||
* Load all plugins from the plugins folder. This method must only be called
|
||||
* once per instance.
|
||||
*/
|
||||
public void loadPlugins()
|
||||
{
|
||||
File dir = new File("plugins");
|
||||
dir.mkdir();
|
||||
|
||||
for (File file : dir.listFiles(new PatternFilenameFilter(".*\\.jar")))
|
||||
{
|
||||
try
|
||||
{
|
||||
JarFile jar = new JarFile(file);
|
||||
ZipEntry entry = jar.getEntry("plugin.yml");
|
||||
if (entry == null)
|
||||
{
|
||||
throw new InvalidPluginException("Jar does not contain a plugin.yml");
|
||||
}
|
||||
|
||||
PluginDescription description;
|
||||
try (InputStream is = jar.getInputStream(entry))
|
||||
{
|
||||
description = PluginDescription.load(is);
|
||||
}
|
||||
URLClassLoader classloader = new URLClassLoader(new URL[]
|
||||
{
|
||||
file.toURI().toURL()
|
||||
}, getClass().getClassLoader());
|
||||
Class<?> clazz = Class.forName(description.getMain(), true, classloader);
|
||||
Class<? extends JavaPlugin> subClazz = clazz.asSubclass(JavaPlugin.class);
|
||||
JavaPlugin plugin = subClazz.getDeclaredConstructor().newInstance();
|
||||
|
||||
plugin.description = description;
|
||||
plugin.onEnable();
|
||||
plugins.add(plugin);
|
||||
|
||||
$().info("Loaded plugin: " + plugin.description.getName());
|
||||
} catch (Exception ex)
|
||||
{
|
||||
$().severe("Could not load plugin: " + file);
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable()
|
||||
{
|
||||
for (JavaPlugin p : plugins)
|
||||
{
|
||||
p.onDisable();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHandshake(LoginEvent event)
|
||||
{
|
||||
for (JavaPlugin p : plugins)
|
||||
{
|
||||
p.onHandshake(event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLogin(LoginEvent event)
|
||||
{
|
||||
for (JavaPlugin p : plugins)
|
||||
{
|
||||
p.onLogin(event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServerConnect(ServerConnectEvent event)
|
||||
{
|
||||
for (JavaPlugin p : plugins)
|
||||
{
|
||||
p.onServerConnect(event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPluginMessage(PluginMessageEvent event)
|
||||
{
|
||||
for (JavaPlugin p : plugins)
|
||||
{
|
||||
p.onPluginMessage(event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChat(ChatEvent event)
|
||||
{
|
||||
for (JavaPlugin p : plugins)
|
||||
{
|
||||
p.onChat(event);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
package net.md_5.bungee.plugin;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Event called to represent a player logging in.
|
||||
*/
|
||||
@Data
|
||||
public class LoginEvent implements Cancellable
|
||||
{
|
||||
|
||||
/**
|
||||
* Canceled state.
|
||||
*/
|
||||
private boolean cancelled;
|
||||
/**
|
||||
* Message to use when kicking if this event is canceled.
|
||||
*/
|
||||
private String cancelReason;
|
||||
/**
|
||||
* Username which the player wishes to use.
|
||||
*/
|
||||
private final String username;
|
||||
/**
|
||||
* IP address of the remote connection.
|
||||
*/
|
||||
private final InetAddress address;
|
||||
/**
|
||||
* Hostname which the user tried to connect to.
|
||||
*/
|
||||
private final String hostname;
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
package net.md_5.bungee.plugin;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import lombok.Data;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
/**
|
||||
* File which contains information about a plugin, its authors, and how to load
|
||||
* it.
|
||||
*/
|
||||
@Data
|
||||
public class PluginDescription
|
||||
{
|
||||
|
||||
private String name;
|
||||
private String main;
|
||||
private String version;
|
||||
private String author;
|
||||
|
||||
private PluginDescription()
|
||||
{
|
||||
}
|
||||
|
||||
public static PluginDescription load(InputStream is)
|
||||
{
|
||||
PluginDescription ret = new Yaml().loadAs(is, PluginDescription.class);
|
||||
if (ret == null)
|
||||
{
|
||||
throw new InvalidPluginException("Could not load plugin description file.");
|
||||
}
|
||||
|
||||
for (Field f : PluginDescription.class.getDeclaredFields())
|
||||
{
|
||||
try
|
||||
{
|
||||
if (f.get(ret) == null)
|
||||
{
|
||||
throw new InvalidPluginException(f.getName() + " is not set properly in plugin description");
|
||||
}
|
||||
} catch (IllegalArgumentException | IllegalAccessException ex)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
package net.md_5.bungee.plugin;
|
||||
|
||||
import lombok.Data;
|
||||
import net.md_5.bungee.UserConnection;
|
||||
|
||||
/**
|
||||
* Event called when a plugin message is sent to the client or server
|
||||
*/
|
||||
@Data
|
||||
public class PluginMessageEvent implements Cancellable
|
||||
{
|
||||
|
||||
/**
|
||||
* Canceled state.
|
||||
*/
|
||||
private boolean cancelled;
|
||||
/**
|
||||
* Message to use when kicking if this event is canceled.
|
||||
*/
|
||||
private String cancelReason;
|
||||
/**
|
||||
* Whether this packet is destined for the server or the client
|
||||
*/
|
||||
private final Destination destination;
|
||||
/**
|
||||
* User in question
|
||||
*/
|
||||
private final UserConnection connection;
|
||||
/**
|
||||
* Tag specified for this plugin message.
|
||||
*/
|
||||
private String tag;
|
||||
/**
|
||||
* Data contained in this plugin message.
|
||||
*/
|
||||
private String data;
|
||||
|
||||
/**
|
||||
* An enum that signifies the destination for this packet
|
||||
*/
|
||||
public enum Destination
|
||||
{
|
||||
|
||||
SERVER,
|
||||
CLIENT
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
package net.md_5.bungee.plugin;
|
||||
|
||||
import lombok.Data;
|
||||
import net.md_5.bungee.UserConnection;
|
||||
|
||||
/**
|
||||
* Event called when the decision is made to decide which server to connect to.
|
||||
*/
|
||||
@Data
|
||||
public class ServerConnectEvent
|
||||
{
|
||||
|
||||
/**
|
||||
* If the player currently has no server, this is true
|
||||
*/
|
||||
private final boolean firstTime;
|
||||
/**
|
||||
* Message to send just before the change. null for no message
|
||||
*/
|
||||
private String message;
|
||||
/**
|
||||
* User in question.
|
||||
*/
|
||||
private final UserConnection connection;
|
||||
/**
|
||||
* Name of the server they are connecting to.
|
||||
*/
|
||||
private final String server;
|
||||
/**
|
||||
* Name of the server which they will be forwarded to instead.
|
||||
*/
|
||||
private String newServer;
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
package net.md_5.bungee.tablist;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
import net.md_5.bungee.BungeeCord;
|
||||
import net.md_5.bungee.UserConnection;
|
||||
import net.md_5.bungee.packet.PacketC9PlayerListItem;
|
||||
|
||||
public class GlobalPingTabList extends GlobalTabList
|
||||
{
|
||||
|
||||
public static final int PING_THRESHOLD = 20;
|
||||
private Map<UserConnection, Integer> lastPings = Collections.synchronizedMap(new WeakHashMap<UserConnection, Integer>());
|
||||
|
||||
@Override
|
||||
public void onPingChange(final UserConnection con, final int ping)
|
||||
{
|
||||
Integer lastPing = lastPings.get(con);
|
||||
if (lastPing == null || (ping - PING_THRESHOLD > lastPing && ping + PING_THRESHOLD < lastPing))
|
||||
{
|
||||
BungeeCord.instance.broadcast(new PacketC9PlayerListItem(con.tabListName, true, ping));
|
||||
lastPings.put(con, ping);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
package net.md_5.bungee.tablist;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import net.md_5.bungee.BungeeCord;
|
||||
import net.md_5.bungee.UserConnection;
|
||||
import net.md_5.bungee.packet.PacketC9PlayerListItem;
|
||||
|
||||
public class GlobalTabList implements TabListHandler
|
||||
{
|
||||
|
||||
private Set<UserConnection> sentPings = Collections.synchronizedSet(new HashSet<UserConnection>());
|
||||
|
||||
@Override
|
||||
public void onJoin(UserConnection con)
|
||||
{
|
||||
for (UserConnection c : BungeeCord.instance.connections.values())
|
||||
{
|
||||
con.packetQueue.add(new PacketC9PlayerListItem(c.tabListName, true, c.getPing()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServerChange(UserConnection con)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPingChange(final UserConnection con, final int ping)
|
||||
{
|
||||
if (!sentPings.contains(con))
|
||||
{
|
||||
BungeeCord.instance.broadcast(new PacketC9PlayerListItem(con.tabListName, true, con.getPing()));
|
||||
sentPings.add(con);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnect(final UserConnection con)
|
||||
{
|
||||
BungeeCord.instance.broadcast(new PacketC9PlayerListItem(con.tabListName, false, 9999));
|
||||
sentPings.remove(con);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPacketC9(UserConnection con, PacketC9PlayerListItem packet)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
package net.md_5.bungee.tablist;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
import net.md_5.bungee.UserConnection;
|
||||
import net.md_5.bungee.packet.PacketC9PlayerListItem;
|
||||
|
||||
public class ServerUniqueTabList implements TabListHandler
|
||||
{
|
||||
|
||||
private Map<UserConnection, Set<String>> sentUsernames = Collections.synchronizedMap(new WeakHashMap<UserConnection, Set<String>>());
|
||||
|
||||
@Override
|
||||
public void onJoin(UserConnection con)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServerChange(UserConnection con)
|
||||
{
|
||||
Set<String> usernames = sentUsernames.get(con);
|
||||
if (usernames != null)
|
||||
{
|
||||
synchronized (usernames)
|
||||
{
|
||||
for (String username : usernames)
|
||||
{
|
||||
con.packetQueue.add(new PacketC9PlayerListItem(username, false, 9999));
|
||||
}
|
||||
usernames.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPingChange(UserConnection con, int ping)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnect(UserConnection con)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPacketC9(final UserConnection con, final PacketC9PlayerListItem packet)
|
||||
{
|
||||
Set<String> usernames = sentUsernames.get(con);
|
||||
if (usernames == null)
|
||||
{
|
||||
usernames = new LinkedHashSet<>();
|
||||
sentUsernames.put(con, usernames);
|
||||
}
|
||||
|
||||
if (packet.online)
|
||||
{
|
||||
usernames.add(packet.username);
|
||||
} else
|
||||
{
|
||||
usernames.remove(packet.username);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
package net.md_5.bungee.tablist;
|
||||
|
||||
import net.md_5.bungee.UserConnection;
|
||||
import net.md_5.bungee.packet.PacketC9PlayerListItem;
|
||||
|
||||
public interface TabListHandler
|
||||
{
|
||||
|
||||
public void onJoin(UserConnection con);
|
||||
|
||||
public void onServerChange(UserConnection con);
|
||||
|
||||
public void onPingChange(UserConnection con, int ping);
|
||||
|
||||
public void onDisconnect(UserConnection con);
|
||||
|
||||
public boolean onPacketC9(UserConnection con, PacketC9PlayerListItem packet);
|
||||
}
|
Loading…
Reference in New Issue
Block a user