mirror of
https://github.com/proxiodev/RedisBungee.git
synced 2024-12-23 10:38:01 +00:00
added Velocity support (NOT TESTED), it will not compile yet due config impl not done yet
This commit is contained in:
parent
ea665fd70f
commit
bdda99bc81
125
RedisBungee-Velocity/RedisBungee-Bungee/pom.xml
Normal file
125
RedisBungee-Velocity/RedisBungee-Bungee/pom.xml
Normal file
@ -0,0 +1,125 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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">
|
||||
<parent>
|
||||
<artifactId>RedisBungee</artifactId>
|
||||
<groupId>com.imaginarycode.minecraft</groupId>
|
||||
<version>0.8.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>RedisBungee-Bungee</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
</properties>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>bungeecord-repo</id>
|
||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.3.2</version>
|
||||
<configuration>
|
||||
<source>8</source>
|
||||
<reportOutputDirectory>../javadoc</reportOutputDirectory>
|
||||
<destDir>${project.name}</destDir>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.2.4</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals><goal>shade</goal></goals>
|
||||
<configuration>
|
||||
<relocations>
|
||||
<relocation>
|
||||
<pattern>redis.clients.jedis</pattern>
|
||||
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.jedis
|
||||
</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>redis.clients.util</pattern>
|
||||
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.jedisutil
|
||||
</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>org.apache.commons.pool</pattern>
|
||||
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.commonspool
|
||||
</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>com.squareup.okhttp</pattern>
|
||||
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.okhttp
|
||||
</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>okio</pattern>
|
||||
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.okio
|
||||
</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>com.google</pattern>
|
||||
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.google
|
||||
</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>org.json</pattern>
|
||||
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.json
|
||||
</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>org.checkerframework</pattern>
|
||||
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.checkframework
|
||||
</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.imaginarycode.minecraft</groupId>
|
||||
<artifactId>RedisBungee-API</artifactId>
|
||||
<version>${parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-api</artifactId>
|
||||
<version>1.17-R0.1-SNAPSHOT</version>
|
||||
<type>jar</type>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -0,0 +1,35 @@
|
||||
package com.imaginarycode.minecraft.redisbungee;
|
||||
|
||||
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
|
||||
import com.imaginarycode.minecraft.redisbungee.internal.DataManager;
|
||||
import com.imaginarycode.minecraft.redisbungee.internal.RedisBungeePlugin;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
|
||||
import net.md_5.bungee.api.event.PostLoginEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
|
||||
public class BungeeDataManager extends DataManager<ProxiedPlayer, PostLoginEvent, PlayerDisconnectEvent, PubSubMessageEvent> implements Listener {
|
||||
|
||||
public BungeeDataManager(RedisBungeePlugin<ProxiedPlayer> plugin) {
|
||||
super(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
@EventHandler
|
||||
public void onPostLogin(PostLoginEvent event) {
|
||||
invalidate(event.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@EventHandler
|
||||
public void onPlayerDisconnect(PlayerDisconnectEvent event) {
|
||||
invalidate(event.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@EventHandler
|
||||
public void onPubSubMessage(PubSubMessageEvent event) {
|
||||
handlePubSubMessage(event.getChannel(), event.getMessage());
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package com.imaginarycode.minecraft.redisbungee;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.imaginarycode.minecraft.redisbungee.internal.DataManager;
|
||||
import net.md_5.bungee.api.connection.PendingConnection;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import redis.clients.jedis.Pipeline;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class RBUtils {
|
||||
|
||||
private static final Gson gson = new Gson();
|
||||
|
||||
protected static void createPlayer(ProxiedPlayer player, Pipeline pipeline, boolean fireEvent) {
|
||||
createPlayer(player.getPendingConnection(), pipeline, fireEvent);
|
||||
if (player.getServer() != null)
|
||||
pipeline.hset("player:" + player.getUniqueId().toString(), "server", player.getServer().getInfo().getName());
|
||||
}
|
||||
|
||||
protected static void createPlayer(PendingConnection connection, Pipeline pipeline, boolean fireEvent) {
|
||||
Map<String, String> playerData = new HashMap<>(4);
|
||||
playerData.put("online", "0");
|
||||
playerData.put("ip", connection.getAddress().getAddress().getHostAddress());
|
||||
playerData.put("proxy", RedisBungeeAPI.getRedisBungeeApi().getServerId());
|
||||
|
||||
pipeline.sadd("proxy:" + RedisBungeeAPI.getRedisBungeeApi().getServerId() + ":usersOnline", connection.getUniqueId().toString());
|
||||
pipeline.hmset("player:" + connection.getUniqueId().toString(), playerData);
|
||||
|
||||
if (fireEvent) {
|
||||
pipeline.publish("redisbungee-data", gson.toJson(new DataManager.DataManagerMessage<>(
|
||||
connection.getUniqueId(), RedisBungeeAPI.getRedisBungeeApi().getServerId(), DataManager.DataManagerMessage.Action.JOIN,
|
||||
new DataManager.LoginPayload(connection.getAddress().getAddress()))));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,639 @@
|
||||
package com.imaginarycode.minecraft.redisbungee;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.gson.Gson;
|
||||
import com.imaginarycode.minecraft.redisbungee.commands.RedisBungeeCommands;
|
||||
import com.imaginarycode.minecraft.redisbungee.events.PlayerChangedServerNetworkEvent;
|
||||
import com.imaginarycode.minecraft.redisbungee.events.PlayerJoinedNetworkEvent;
|
||||
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
|
||||
import com.imaginarycode.minecraft.redisbungee.internal.*;
|
||||
import com.imaginarycode.minecraft.redisbungee.internal.summoners.JedisSummoner;
|
||||
import com.imaginarycode.minecraft.redisbungee.internal.summoners.SinglePoolJedisSummoner;
|
||||
import com.imaginarycode.minecraft.redisbungee.internal.util.IOUtil;
|
||||
import com.imaginarycode.minecraft.redisbungee.internal.util.LuaManager;
|
||||
import com.imaginarycode.minecraft.redisbungee.internal.util.uuid.NameFetcher;
|
||||
import com.imaginarycode.minecraft.redisbungee.internal.util.uuid.UUIDFetcher;
|
||||
import com.imaginarycode.minecraft.redisbungee.internal.util.uuid.UUIDTranslator;
|
||||
import com.squareup.okhttp.Dispatcher;
|
||||
import com.squareup.okhttp.OkHttpClient;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.plugin.Event;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.config.Configuration;
|
||||
import net.md_5.bungee.config.ConfigurationProvider;
|
||||
import net.md_5.bungee.config.YamlConfiguration;
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.JedisPool;
|
||||
import redis.clients.jedis.JedisPoolConfig;
|
||||
import redis.clients.jedis.Pipeline;
|
||||
import redis.clients.jedis.exceptions.JedisConnectionException;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.InetAddress;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
public class RedisBungeeBungeePlugin extends Plugin implements RedisBungeePlugin<ProxiedPlayer> {
|
||||
|
||||
private static final Gson gson = new Gson();
|
||||
private RedisBungeeAPI api;
|
||||
private PubSubListener psl = null;
|
||||
private JedisSummoner jedisSummoner;
|
||||
private UUIDTranslator uuidTranslator;
|
||||
private RedisBungeeConfiguration configuration;
|
||||
private BungeeDataManager dataManager;
|
||||
private OkHttpClient httpClient;
|
||||
private volatile List<String> serverIds;
|
||||
private final AtomicInteger nagAboutServers = new AtomicInteger();
|
||||
private final AtomicInteger globalPlayerCount = new AtomicInteger();
|
||||
private Future<?> integrityCheck;
|
||||
private Future<?> heartbeatTask;
|
||||
private LuaManager.Script serverToPlayersScript;
|
||||
private LuaManager.Script getPlayerCountScript;
|
||||
|
||||
private static final Object SERVER_TO_PLAYERS_KEY = new Object();
|
||||
private final Cache<Object, Multimap<String, UUID>> serverToPlayersCache = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(5, TimeUnit.SECONDS)
|
||||
.build();
|
||||
|
||||
|
||||
@Override
|
||||
public RedisBungeeConfiguration getConfiguration() {
|
||||
return this.configuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return this.globalPlayerCount.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentCount() {
|
||||
Long count = (Long) getPlayerCountScript.eval(ImmutableList.<String>of(), ImmutableList.<String>of());
|
||||
return count.intValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getLocalPlayersAsUuidStrings() {
|
||||
ImmutableSet.Builder<String> builder = ImmutableSet.builder();
|
||||
for (ProxiedPlayer player : getProxy().getPlayers()) {
|
||||
builder.add(player.getUniqueId().toString());
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataManager<ProxiedPlayer, ?, ?, ?> getDataManager() {
|
||||
return this.dataManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> getPlayers() {
|
||||
ImmutableSet.Builder<UUID> setBuilder = ImmutableSet.builder();
|
||||
if (isJedisAvailable()) {
|
||||
try (Jedis rsc = requestJedis()) {
|
||||
List<String> keys = new ArrayList<>();
|
||||
for (String i : getServerIds()) {
|
||||
keys.add("proxy:" + i + ":usersOnline");
|
||||
}
|
||||
if (!keys.isEmpty()) {
|
||||
Set<String> users = rsc.sunion(keys.toArray(new String[keys.size()]));
|
||||
if (users != null && !users.isEmpty()) {
|
||||
for (String user : users) {
|
||||
try {
|
||||
setBuilder = setBuilder.add(UUID.fromString(user));
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (JedisConnectionException e) {
|
||||
// Redis server has disappeared!
|
||||
getLogger().log(Level.SEVERE, "Unable to get connection from pool - did your Redis server go away?", e);
|
||||
throw new RuntimeException("Unable to get all players online", e);
|
||||
}
|
||||
}
|
||||
return setBuilder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Jedis requestJedis() {
|
||||
return this.jedisSummoner.requestJedis();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isJedisAvailable() {
|
||||
return this.jedisSummoner.isJedisAvailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RedisBungeeAPI getApi() {
|
||||
return this.api;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUIDTranslator getUuidTranslator() {
|
||||
return this.uuidTranslator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Multimap<String, UUID> serversToPlayers() {
|
||||
try {
|
||||
return serverToPlayersCache.get(SERVER_TO_PLAYERS_KEY, new Callable<Multimap<String, UUID>>() {
|
||||
@Override
|
||||
public Multimap<String, UUID> call() throws Exception {
|
||||
Collection<String> data = (Collection<String>) serverToPlayersScript.eval(ImmutableList.<String>of(), getServerIds());
|
||||
ImmutableMultimap.Builder<String, UUID> builder = ImmutableMultimap.builder();
|
||||
String key = null;
|
||||
for (String s : data) {
|
||||
if (key == null) {
|
||||
key = s;
|
||||
continue;
|
||||
}
|
||||
|
||||
builder.put(key, UUID.fromString(s));
|
||||
key = null;
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
});
|
||||
} catch (ExecutionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> getPlayersOnProxy(String proxyId) {
|
||||
checkArgument(getServerIds().contains(proxyId), proxyId + " is not a valid proxy ID");
|
||||
try (Jedis jedis = requestJedis()) {
|
||||
Set<String> users = jedis.smembers("proxy:" + proxyId + ":usersOnline");
|
||||
ImmutableSet.Builder<UUID> builder = ImmutableSet.builder();
|
||||
for (String user : users) {
|
||||
builder.add(UUID.fromString(user));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendProxyCommand(String serverId, String command) {
|
||||
checkArgument(getServerIds().contains(serverId) || serverId.equals("allservers"), "proxyId is invalid");
|
||||
sendChannelMessage("redisbungee-" + serverId, command);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getServerIds() {
|
||||
return serverIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getCurrentServerIds(boolean nag, boolean lagged) {
|
||||
try (Jedis jedis = requestJedis()) {
|
||||
long time = getRedisTime(jedis.time());
|
||||
int nagTime = 0;
|
||||
if (nag) {
|
||||
nagTime = nagAboutServers.decrementAndGet();
|
||||
if (nagTime <= 0) {
|
||||
nagAboutServers.set(10);
|
||||
}
|
||||
}
|
||||
ImmutableList.Builder<String> servers = ImmutableList.builder();
|
||||
Map<String, String> heartbeats = jedis.hgetAll("heartbeats");
|
||||
for (Map.Entry<String, String> entry : heartbeats.entrySet()) {
|
||||
try {
|
||||
long stamp = Long.parseLong(entry.getValue());
|
||||
if (lagged ? time >= stamp + 30 : time <= stamp + 30)
|
||||
servers.add(entry.getKey());
|
||||
else if (nag && nagTime <= 0) {
|
||||
getLogger().warning(entry.getKey() + " is " + (time - stamp) + " seconds behind! (Time not synchronized or server down?) and was removed from heartbeat.");
|
||||
jedis.hdel("heartbeats", entry.getKey());
|
||||
}
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
return servers.build();
|
||||
} catch (JedisConnectionException e) {
|
||||
getLogger().log(Level.SEVERE, "Unable to fetch server IDs", e);
|
||||
return Collections.singletonList(configuration.getServerId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PubSubListener getPubSubListener() {
|
||||
return this.psl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendChannelMessage(String channel, String message) {
|
||||
try (Jedis jedis = requestJedis()) {
|
||||
jedis.publish(channel, message);
|
||||
} catch (JedisConnectionException e) {
|
||||
// Redis server has disappeared!
|
||||
getLogger().log(Level.SEVERE, "Unable to get connection from pool - did your Redis server go away?", e);
|
||||
throw new RuntimeException("Unable to publish channel message", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeAsync(Runnable runnable) {
|
||||
this.getProxy().getScheduler().runAsync(this, runnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeAsyncAfter(Runnable runnable, TimeUnit timeUnit, int time) {
|
||||
this.getProxy().getScheduler().schedule(this, runnable, time, timeUnit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void callEvent(Object event) {
|
||||
this.getProxy().getPluginManager().callEvent((Event) event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnlineMode() {
|
||||
return this.getProxy().getConfig().isOnlineMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logInfo(String msg) {
|
||||
this.getLogger().info(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logWarn(String msg) {
|
||||
this.getLogger().warning(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logFatal(String msg) {
|
||||
this.getLogger().severe(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProxiedPlayer getPlayer(UUID uuid) {
|
||||
return this.getProxy().getPlayer(uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProxiedPlayer getPlayer(String name) {
|
||||
return this.getProxy().getPlayer(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getPlayerUUID(String player) {
|
||||
return this.getProxy().getPlayer(player).getUniqueId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPlayerName(UUID player) {
|
||||
return this.getProxy().getPlayer(player).getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPlayerServerName(ProxiedPlayer player) {
|
||||
return player.getServer().getInfo().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPlayerOnAServer(ProxiedPlayer player) {
|
||||
return player.getServer() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetAddress getPlayerIp(ProxiedPlayer player) {
|
||||
return player.getAddress().getAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendProxyCommand(String cmd) {
|
||||
checkArgument(getServerIds().contains(this.configuration.getServerId()) || this.configuration.getServerId().equals("allservers"), "proxyId is invalid");
|
||||
sendChannelMessage("redisbungee-" + this.configuration.getServerId(), cmd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getRedisTime(List<String> timeRes) {
|
||||
return Long.parseLong(timeRes.get(0));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
ThreadFactory factory = ((ThreadPoolExecutor) getExecutorService()).getThreadFactory();
|
||||
ScheduledExecutorService service = Executors.newScheduledThreadPool(24, factory);
|
||||
try {
|
||||
Field field = Plugin.class.getDeclaredField("service");
|
||||
field.setAccessible(true);
|
||||
ExecutorService builtinService = (ExecutorService) field.get(this);
|
||||
field.set(this, service);
|
||||
builtinService.shutdownNow();
|
||||
} catch (IllegalAccessException | NoSuchFieldException e) {
|
||||
getLogger().log(Level.WARNING, "Can't replace BungeeCord thread pool with our own");
|
||||
getLogger().log(Level.INFO, "skipping replacement.....");
|
||||
}
|
||||
try {
|
||||
loadConfig();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Unable to load/save config", e);
|
||||
} catch (JedisConnectionException e) {
|
||||
throw new RuntimeException("Unable to connect to your Redis server!", e);
|
||||
}
|
||||
this.api = new RedisBungeeAPI(this);
|
||||
// call old plugin class to support old plugins
|
||||
new RedisBungee(api);
|
||||
if (isJedisAvailable()) {
|
||||
try (Jedis tmpRsc = requestJedis()) {
|
||||
// This is more portable than INFO <section>
|
||||
String info = tmpRsc.info();
|
||||
for (String s : info.split("\r\n")) {
|
||||
if (s.startsWith("redis_version:")) {
|
||||
String version = s.split(":")[1];
|
||||
getLogger().info(version + " <- redis version");
|
||||
if (!RedisUtil.isRedisVersionRight(version)) {
|
||||
getLogger().warning("Your version of Redis (" + version + ") is not at least version 6.0 RedisBungee requires a newer version of Redis.");
|
||||
throw new RuntimeException("Unsupported Redis version detected");
|
||||
} else {
|
||||
LuaManager manager = new LuaManager(this);
|
||||
serverToPlayersScript = manager.createScript(IOUtil.readInputStreamAsString(getResourceAsStream("lua/server_to_players.lua")));
|
||||
getPlayerCountScript = manager.createScript(IOUtil.readInputStreamAsString(getResourceAsStream("lua/get_player_count.lua")));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
tmpRsc.hset("heartbeats", configuration.getServerId(), tmpRsc.time().get(0));
|
||||
|
||||
long uuidCacheSize = tmpRsc.hlen("uuid-cache");
|
||||
if (uuidCacheSize > 750000) {
|
||||
getLogger().info("Looks like you have a really big UUID cache! Run https://www.spigotmc.org/resources/redisbungeecleaner.8505/ as soon as possible.");
|
||||
}
|
||||
}
|
||||
serverIds = getCurrentServerIds(true, false);
|
||||
uuidTranslator = new UUIDTranslator(this);
|
||||
heartbeatTask = service.scheduleAtFixedRate(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try (Jedis rsc = requestJedis()) {
|
||||
long redisTime = getRedisTime(rsc.time());
|
||||
rsc.hset("heartbeats", configuration.getServerId(), String.valueOf(redisTime));
|
||||
} catch (JedisConnectionException e) {
|
||||
// Redis server has disappeared!
|
||||
getLogger().log(Level.SEVERE, "Unable to update heartbeat - did your Redis server go away?", e);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
serverIds = getCurrentServerIds(true, false);
|
||||
globalPlayerCount.set(getCurrentCount());
|
||||
} catch (Throwable e) {
|
||||
getLogger().log(Level.SEVERE, "Unable to update data - did your Redis server go away?", e);
|
||||
}
|
||||
}
|
||||
}, 0, 3, TimeUnit.SECONDS);
|
||||
dataManager = new BungeeDataManager(this);
|
||||
getProxy().getPluginManager().registerListener(this, new RedisBungeeListener(this, configuration.getExemptAddresses()));
|
||||
getProxy().getPluginManager().registerListener(this, dataManager);
|
||||
psl = new PubSubListener(this);
|
||||
getProxy().getScheduler().runAsync(this, psl);
|
||||
integrityCheck = service.scheduleAtFixedRate(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try (Jedis tmpRsc = requestJedis()) {
|
||||
Set<String> players = getLocalPlayersAsUuidStrings();
|
||||
Set<String> playersInRedis = tmpRsc.smembers("proxy:" + configuration.getServerId() + ":usersOnline");
|
||||
List<String> lagged = getCurrentServerIds(false, true);
|
||||
|
||||
// Clean up lagged players.
|
||||
for (String s : lagged) {
|
||||
Set<String> laggedPlayers = tmpRsc.smembers("proxy:" + s + ":usersOnline");
|
||||
tmpRsc.del("proxy:" + s + ":usersOnline");
|
||||
if (!laggedPlayers.isEmpty()) {
|
||||
getLogger().info("Cleaning up lagged proxy " + s + " (" + laggedPlayers.size() + " players)...");
|
||||
for (String laggedPlayer : laggedPlayers) {
|
||||
RedisUtil.cleanUpPlayer(laggedPlayer, tmpRsc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Set<String> absentLocally = new HashSet<>(playersInRedis);
|
||||
absentLocally.removeAll(players);
|
||||
Set<String> absentInRedis = new HashSet<>(players);
|
||||
absentInRedis.removeAll(playersInRedis);
|
||||
|
||||
for (String member : absentLocally) {
|
||||
boolean found = false;
|
||||
for (String proxyId : getServerIds()) {
|
||||
if (proxyId.equals(configuration.getServerId())) continue;
|
||||
if (tmpRsc.sismember("proxy:" + proxyId + ":usersOnline", member)) {
|
||||
// Just clean up the set.
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
RedisUtil.cleanUpPlayer(member, tmpRsc);
|
||||
getLogger().warning("Player found in set that was not found locally and globally: " + member);
|
||||
} else {
|
||||
tmpRsc.srem("proxy:" + configuration.getServerId() + ":usersOnline", member);
|
||||
getLogger().warning("Player found in set that was not found locally, but is on another proxy: " + member);
|
||||
}
|
||||
}
|
||||
|
||||
Pipeline pipeline = tmpRsc.pipelined();
|
||||
|
||||
for (String player : absentInRedis) {
|
||||
// Player not online according to Redis but not BungeeCord.
|
||||
getLogger().warning("Player " + player + " is on the proxy but not in Redis.");
|
||||
|
||||
ProxiedPlayer proxiedPlayer = ProxyServer.getInstance().getPlayer(UUID.fromString(player));
|
||||
if (proxiedPlayer == null)
|
||||
continue; // We'll deal with it later.
|
||||
|
||||
RBUtils.createPlayer(proxiedPlayer, pipeline, true);
|
||||
}
|
||||
|
||||
pipeline.sync();
|
||||
} catch (Throwable e) {
|
||||
getLogger().log(Level.SEVERE, "Unable to fix up stored player data", e);
|
||||
}
|
||||
}
|
||||
}, 0, 1, TimeUnit.MINUTES);
|
||||
}
|
||||
getProxy().registerChannel("legacy:redisbungee");
|
||||
getProxy().registerChannel("RedisBungee");
|
||||
// register commands
|
||||
if (configuration.doOverrideBungeeCommands()) {
|
||||
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.GlistCommand(this));
|
||||
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.FindCommand(this));
|
||||
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.LastSeenCommand(this));
|
||||
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.IpCommand(this));
|
||||
}
|
||||
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.SendToAll(this));
|
||||
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.ServerId(this));
|
||||
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.ServerIds(this));
|
||||
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.PlayerProxyCommand(this));
|
||||
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.PlistCommand(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
if (isJedisAvailable()) {
|
||||
// Poison the PubSub listener
|
||||
psl.poison();
|
||||
integrityCheck.cancel(true);
|
||||
heartbeatTask.cancel(true);
|
||||
getProxy().getPluginManager().unregisterListeners(this);
|
||||
|
||||
try (Jedis tmpRsc = requestJedis()) {
|
||||
tmpRsc.hdel("heartbeats", configuration.getServerId());
|
||||
if (tmpRsc.scard("proxy:" + configuration.getServerId() + ":usersOnline") > 0) {
|
||||
Set<String> players = tmpRsc.smembers("proxy:" + configuration.getServerId() + ":usersOnline");
|
||||
for (String member : players)
|
||||
RedisUtil.cleanUpPlayer(member, tmpRsc);
|
||||
}
|
||||
}
|
||||
try {
|
||||
this.jedisSummoner.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadConfig() throws IOException {
|
||||
if (!getDataFolder().exists()) {
|
||||
getDataFolder().mkdir();
|
||||
}
|
||||
|
||||
File file = new File(getDataFolder(), "config.yml");
|
||||
|
||||
if (!file.exists()) {
|
||||
file.createNewFile();
|
||||
try (InputStream in = getResourceAsStream("example_config.yml");
|
||||
OutputStream out = new FileOutputStream(file)) {
|
||||
ByteStreams.copy(in, out);
|
||||
}
|
||||
}
|
||||
|
||||
final Configuration yamlConfiguration = ConfigurationProvider.getProvider(YamlConfiguration.class).load(file);
|
||||
|
||||
final String redisServer = yamlConfiguration.getString("redis-server", "localhost");
|
||||
final int redisPort = yamlConfiguration.getInt("redis-port", 6379);
|
||||
final boolean useSSL = yamlConfiguration.getBoolean("useSSL", false);
|
||||
String redisPassword = yamlConfiguration.getString("redis-password", "");
|
||||
String serverId = yamlConfiguration.getString("server-id");
|
||||
|
||||
// check redis password
|
||||
if (redisPassword != null && (redisPassword.isEmpty() || redisPassword.equals("none"))) {
|
||||
redisPassword = null;
|
||||
getLogger().warning("INSECURE setup was detected Please set password for your redis instance.");
|
||||
}
|
||||
if (!useSSL) {
|
||||
getLogger().warning("INSECURE setup was detected Please setup ssl for your redis instance.");
|
||||
}
|
||||
// Configuration sanity checks.
|
||||
if (serverId == null || serverId.isEmpty()) {
|
||||
/*
|
||||
* this check causes the config comments to disappear somehow
|
||||
* I think due snake yaml limitations so as todo: write our own yaml parser?
|
||||
*/
|
||||
String genId = UUID.randomUUID().toString();
|
||||
getLogger().info("Generated server id " + genId + " and saving it to config.");
|
||||
yamlConfiguration.set("server-id", genId);
|
||||
ConfigurationProvider.getProvider(YamlConfiguration.class).save(yamlConfiguration, new File(getDataFolder(), "config.yml"));
|
||||
getLogger().info("Server id was generated: " + serverId);
|
||||
} else {
|
||||
getLogger().info("Loaded server id " + serverId + '.');
|
||||
}
|
||||
this.configuration = new RedisBungeeConfiguration(serverId, yamlConfiguration.getStringList("exempt-ip-addresses"), yamlConfiguration.getBoolean("register-bungee-commands", true));
|
||||
|
||||
if (redisServer != null && !redisServer.isEmpty()) {
|
||||
try {
|
||||
JedisPoolConfig config = new JedisPoolConfig();
|
||||
config.setMaxTotal(yamlConfiguration.getInt("max-redis-connections", 8));
|
||||
this.jedisSummoner = new SinglePoolJedisSummoner(new JedisPool(config, redisServer, redisPort, 0, redisPassword, useSSL));
|
||||
|
||||
} catch (JedisConnectionException e) {
|
||||
throw new RuntimeException("Unable to create Redis pool", e);
|
||||
}
|
||||
|
||||
// Test the connection
|
||||
try (Jedis rsc = requestJedis()) {
|
||||
rsc.ping();
|
||||
// If that worked, now we can check for an existing, alive Bungee:
|
||||
File crashFile = new File(getDataFolder(), "restarted_from_crash.txt");
|
||||
if (crashFile.exists()) {
|
||||
crashFile.delete();
|
||||
} else if (rsc.hexists("heartbeats", serverId)) {
|
||||
try {
|
||||
long value = Long.parseLong(rsc.hget("heartbeats", serverId));
|
||||
long redisTime = getRedisTime(rsc.time());
|
||||
if (redisTime < value + 20) {
|
||||
getLogger().severe("You have launched a possible impostor BungeeCord instance. Another instance is already running.");
|
||||
getLogger().severe("For data consistency reasons, RedisBungee will now disable itself.");
|
||||
getLogger().severe("If this instance is coming up from a crash, create a file in your RedisBungee plugins directory with the name 'restarted_from_crash.txt' and RedisBungee will not perform this check.");
|
||||
throw new RuntimeException("Possible impostor instance!");
|
||||
}
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
httpClient = new OkHttpClient();
|
||||
Dispatcher dispatcher = new Dispatcher(getExecutorService());
|
||||
httpClient.setDispatcher(dispatcher);
|
||||
NameFetcher.setHttpClient(httpClient);
|
||||
UUIDFetcher.setHttpClient(httpClient);
|
||||
|
||||
getLogger().log(Level.INFO, "Successfully connected to Redis.");
|
||||
} catch (JedisConnectionException e) {
|
||||
this.jedisSummoner.close();
|
||||
throw e;
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException("No redis server specified!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getPubSubEventClass() {
|
||||
return PubSubMessageEvent.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getNetworkJoinEventClass() {
|
||||
return PlayerJoinedNetworkEvent.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getServerChangeEventClass() {
|
||||
return PlayerChangedServerNetworkEvent.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getNetworkQuitEventClass() {
|
||||
return PlayerJoinedNetworkEvent.class;
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
package com.imaginarycode.minecraft.redisbungee;
|
||||
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
public class RedisBungeeCommandSender implements CommandSender {
|
||||
private static final RedisBungeeCommandSender singleton;
|
||||
|
||||
static {
|
||||
singleton = new RedisBungeeCommandSender();
|
||||
}
|
||||
|
||||
public static RedisBungeeCommandSender getSingleton() {
|
||||
return singleton;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "RedisBungee";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(String s) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessages(String... strings) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(BaseComponent... baseComponents) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(BaseComponent baseComponent) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getGroups() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addGroups(String... strings) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeGroups(String... strings) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(String s) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPermission(String s, boolean b) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getPermissions() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
@ -0,0 +1,261 @@
|
||||
package com.imaginarycode.minecraft.redisbungee;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.io.ByteArrayDataInput;
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.imaginarycode.minecraft.redisbungee.internal.AbstractRedisBungeeListener;
|
||||
import com.imaginarycode.minecraft.redisbungee.internal.DataManager;
|
||||
import com.imaginarycode.minecraft.redisbungee.internal.RedisBungeePlugin;
|
||||
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
|
||||
import com.imaginarycode.minecraft.redisbungee.internal.RedisUtil;
|
||||
import com.imaginarycode.minecraft.redisbungee.internal.util.RedisCallable;
|
||||
import net.md_5.bungee.api.AbstractReconnectHandler;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
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.event.*;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.Pipeline;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.*;
|
||||
|
||||
public class RedisBungeeListener extends AbstractRedisBungeeListener<LoginEvent, PostLoginEvent, PlayerDisconnectEvent, ServerConnectedEvent, ProxyPingEvent, PluginMessageEvent, PubSubMessageEvent> implements Listener {
|
||||
|
||||
|
||||
public RedisBungeeListener(RedisBungeePlugin<?> plugin, List<InetAddress> exemptAddresses) {
|
||||
super(plugin, exemptAddresses);
|
||||
}
|
||||
|
||||
@Override
|
||||
@EventHandler
|
||||
public void onLogin(LoginEvent event) {
|
||||
event.registerIntent((Plugin) plugin);
|
||||
plugin.executeAsync(new RedisCallable<Void>(plugin) {
|
||||
@Override
|
||||
protected Void call(Jedis jedis) {
|
||||
try {
|
||||
if (event.isCancelled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// We make sure they aren't trying to use an existing player's name.
|
||||
// This is problematic for online-mode servers as they always disconnect old clients.
|
||||
if (plugin.isOnlineMode()) {
|
||||
ProxiedPlayer player = (ProxiedPlayer) plugin.getPlayer(event.getConnection().getName());
|
||||
|
||||
if (player != null) {
|
||||
event.setCancelled(true);
|
||||
// TODO: Make it accept a BaseComponent[] like everything else.
|
||||
event.setCancelReason(ONLINE_MODE_RECONNECT);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
for (String s : plugin.getServerIds()) {
|
||||
if (jedis.sismember("proxy:" + s + ":usersOnline", event.getConnection().getUniqueId().toString())) {
|
||||
event.setCancelled(true);
|
||||
// TODO: Make it accept a BaseComponent[] like everything else.
|
||||
event.setCancelReason(ALREADY_LOGGED_IN);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} finally {
|
||||
event.completeIntent((Plugin) plugin);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@EventHandler
|
||||
public void onPostLogin(PostLoginEvent event) {
|
||||
plugin.executeAsync(new RedisCallable<Void>(plugin) {
|
||||
@Override
|
||||
protected Void call(Jedis jedis) {
|
||||
// this code was moved out from login event due being async..
|
||||
// and it can be cancelled but it will show as false in redis-bungee
|
||||
// which will register the player into the redis database.
|
||||
Pipeline pipeline = jedis.pipelined();
|
||||
plugin.getUuidTranslator().persistInfo(event.getPlayer().getName(), event.getPlayer().getUniqueId(), pipeline);
|
||||
RBUtils.createPlayer(event.getPlayer(), pipeline, false);
|
||||
pipeline.sync();
|
||||
// the end of moved code.
|
||||
|
||||
jedis.publish("redisbungee-data", gson.toJson(new DataManager.DataManagerMessage(
|
||||
event.getPlayer().getUniqueId(), plugin.getApi().getServerId(), DataManager.DataManagerMessage.Action.JOIN,
|
||||
new DataManager.LoginPayload(event.getPlayer().getAddress().getAddress()))));
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@EventHandler
|
||||
public void onPlayerDisconnect(PlayerDisconnectEvent event) {
|
||||
plugin.executeAsync(new RedisCallable<Void>(plugin) {
|
||||
@Override
|
||||
protected Void call(Jedis jedis) {
|
||||
Pipeline pipeline = jedis.pipelined();
|
||||
RedisUtil.cleanUpPlayer(event.getPlayer().getUniqueId().toString(), pipeline);
|
||||
pipeline.sync();
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@EventHandler
|
||||
public void onServerChange(ServerConnectedEvent event) {
|
||||
final String currentServer = event.getPlayer().getServer() == null ? null : event.getPlayer().getServer().getInfo().getName();
|
||||
plugin.executeAsync(new RedisCallable<Void>(plugin) {
|
||||
@Override
|
||||
protected Void call(Jedis jedis) {
|
||||
jedis.hset("player:" + event.getPlayer().getUniqueId().toString(), "server", event.getServer().getInfo().getName());
|
||||
jedis.publish("redisbungee-data", gson.toJson(new DataManager.DataManagerMessage(
|
||||
event.getPlayer().getUniqueId(), plugin.getApi().getServerId(), DataManager.DataManagerMessage.Action.SERVER_CHANGE,
|
||||
new DataManager.ServerChangePayload(event.getServer().getInfo().getName(), currentServer))));
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@EventHandler
|
||||
public void onPing(ProxyPingEvent event) {
|
||||
if (exemptAddresses.contains(event.getConnection().getAddress().getAddress())) {
|
||||
return;
|
||||
}
|
||||
ServerInfo forced = AbstractReconnectHandler.getForcedHost(event.getConnection());
|
||||
|
||||
if (forced != null && event.getConnection().getListener().isPingPassthrough()) {
|
||||
return;
|
||||
}
|
||||
event.getResponse().getPlayers().setOnline(plugin.getCount());
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
@EventHandler
|
||||
public void onPluginMessage(PluginMessageEvent event) {
|
||||
if ((event.getTag().equals("legacy:redisbungee") || event.getTag().equals("RedisBungee")) && event.getSender() instanceof Server) {
|
||||
final String currentChannel = event.getTag();
|
||||
final byte[] data = Arrays.copyOf(event.getData(), event.getData().length);
|
||||
plugin.executeAsync(() -> {
|
||||
ByteArrayDataInput in = ByteStreams.newDataInput(data);
|
||||
|
||||
String subchannel = in.readUTF();
|
||||
ByteArrayDataOutput out = ByteStreams.newDataOutput();
|
||||
String type;
|
||||
|
||||
switch (subchannel) {
|
||||
case "PlayerList":
|
||||
out.writeUTF("PlayerList");
|
||||
Set<UUID> original = Collections.emptySet();
|
||||
type = in.readUTF();
|
||||
if (type.equals("ALL")) {
|
||||
out.writeUTF("ALL");
|
||||
original = plugin.getPlayers();
|
||||
} else {
|
||||
try {
|
||||
original = plugin.getApi().getPlayersOnServer(type);
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
}
|
||||
Set<String> players = new HashSet<>();
|
||||
for (UUID uuid : original)
|
||||
players.add(plugin.getUuidTranslator().getNameFromUuid(uuid, false));
|
||||
out.writeUTF(Joiner.on(',').join(players));
|
||||
break;
|
||||
case "PlayerCount":
|
||||
out.writeUTF("PlayerCount");
|
||||
type = in.readUTF();
|
||||
if (type.equals("ALL")) {
|
||||
out.writeUTF("ALL");
|
||||
out.writeInt(plugin.getCount());
|
||||
} else {
|
||||
out.writeUTF(type);
|
||||
try {
|
||||
out.writeInt(plugin.getApi().getPlayersOnServer(type).size());
|
||||
} catch (IllegalArgumentException e) {
|
||||
out.writeInt(0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "LastOnline":
|
||||
String user = in.readUTF();
|
||||
out.writeUTF("LastOnline");
|
||||
out.writeUTF(user);
|
||||
out.writeLong(plugin.getApi().getLastOnline(plugin.getUuidTranslator().getTranslatedUuid(user, true)));
|
||||
break;
|
||||
case "ServerPlayers":
|
||||
String type1 = in.readUTF();
|
||||
out.writeUTF("ServerPlayers");
|
||||
Multimap<String, UUID> multimap = plugin.getApi().getServerToPlayers();
|
||||
|
||||
boolean includesUsers;
|
||||
|
||||
switch (type1) {
|
||||
case "COUNT":
|
||||
includesUsers = false;
|
||||
break;
|
||||
case "PLAYERS":
|
||||
includesUsers = true;
|
||||
break;
|
||||
default:
|
||||
// TODO: Should I raise an error?
|
||||
return;
|
||||
}
|
||||
|
||||
out.writeUTF(type1);
|
||||
|
||||
if (includesUsers) {
|
||||
Multimap<String, String> human = HashMultimap.create();
|
||||
for (Map.Entry<String, UUID> entry : multimap.entries()) {
|
||||
human.put(entry.getKey(), plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false));
|
||||
}
|
||||
serializeMultimap(human, true, out);
|
||||
} else {
|
||||
serializeMultiset(multimap.keys(), out);
|
||||
}
|
||||
break;
|
||||
case "Proxy":
|
||||
out.writeUTF("Proxy");
|
||||
out.writeUTF(plugin.getConfiguration().getServerId());
|
||||
break;
|
||||
case "PlayerProxy":
|
||||
String username = in.readUTF();
|
||||
out.writeUTF("PlayerProxy");
|
||||
out.writeUTF(username);
|
||||
out.writeUTF(plugin.getApi().getProxy(plugin.getUuidTranslator().getTranslatedUuid(username, true)));
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
((Server) event.getSender()).sendData(currentChannel, out.toByteArray());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@EventHandler
|
||||
public void onPubSubMessage(PubSubMessageEvent event) {
|
||||
if (event.getChannel().equals("redisbungee-allservers") || event.getChannel().equals("redisbungee-" + plugin.getApi().getServerId())) {
|
||||
String message = event.getMessage();
|
||||
if (message.startsWith("/"))
|
||||
message = message.substring(1);
|
||||
plugin.logInfo("Invoking command via PubSub: /" + message);
|
||||
((Plugin) plugin).getProxy().getPluginManager().dispatchCommand(RedisBungeeCommandSender.getSingleton(), message);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,343 @@
|
||||
package com.imaginarycode.minecraft.redisbungee.commands;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.imaginarycode.minecraft.redisbungee.RedisBungeeAPI;
|
||||
import com.imaginarycode.minecraft.redisbungee.RedisBungeeBungeePlugin;
|
||||
import com.imaginarycode.minecraft.redisbungee.internal.RedisBungeePlugin;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.ComponentBuilder;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.plugin.Command;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* This class contains subclasses that are used for the commands RedisBungee overrides or includes: /glist, /find and /lastseen.
|
||||
* <p>
|
||||
* All classes use the {@link RedisBungeeAPI}.
|
||||
*
|
||||
* @author tuxed
|
||||
* @since 0.2.3
|
||||
*/
|
||||
public class RedisBungeeCommands {
|
||||
private static final BaseComponent[] NO_PLAYER_SPECIFIED =
|
||||
new ComponentBuilder("You must specify a player name.").color(ChatColor.RED).create();
|
||||
private static final BaseComponent[] PLAYER_NOT_FOUND =
|
||||
new ComponentBuilder("No such player found.").color(ChatColor.RED).create();
|
||||
private static final BaseComponent[] NO_COMMAND_SPECIFIED =
|
||||
new ComponentBuilder("You must specify a command to be run.").color(ChatColor.RED).create();
|
||||
|
||||
private static String playerPlural(int num) {
|
||||
return num == 1 ? num + " player is" : num + " players are";
|
||||
}
|
||||
|
||||
public static class GlistCommand extends Command {
|
||||
private final RedisBungeeBungeePlugin plugin;
|
||||
|
||||
public GlistCommand(RedisBungeeBungeePlugin plugin) {
|
||||
super("glist", "bungeecord.command.list", "redisbungee", "rglist");
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(final CommandSender sender, final String[] args) {
|
||||
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
int count = plugin.getApi().getPlayerCount();
|
||||
BaseComponent[] playersOnline = new ComponentBuilder("").color(ChatColor.YELLOW)
|
||||
.append(playerPlural(count) + " currently online.").create();
|
||||
if (args.length > 0 && args[0].equals("showall")) {
|
||||
Multimap<String, UUID> serverToPlayers = plugin.getApi().getServerToPlayers();
|
||||
Multimap<String, String> human = HashMultimap.create();
|
||||
for (Map.Entry<String, UUID> entry : serverToPlayers.entries()) {
|
||||
human.put(entry.getKey(), plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false));
|
||||
}
|
||||
for (String server : new TreeSet<>(serverToPlayers.keySet())) {
|
||||
TextComponent serverName = new TextComponent();
|
||||
serverName.setColor(ChatColor.GREEN);
|
||||
serverName.setText("[" + server + "] ");
|
||||
TextComponent serverCount = new TextComponent();
|
||||
serverCount.setColor(ChatColor.YELLOW);
|
||||
serverCount.setText("(" + serverToPlayers.get(server).size() + "): ");
|
||||
TextComponent serverPlayers = new TextComponent();
|
||||
serverPlayers.setColor(ChatColor.WHITE);
|
||||
serverPlayers.setText(Joiner.on(", ").join(human.get(server)));
|
||||
sender.sendMessage(serverName, serverCount, serverPlayers);
|
||||
}
|
||||
sender.sendMessage(playersOnline);
|
||||
} else {
|
||||
sender.sendMessage(playersOnline);
|
||||
sender.sendMessage(new ComponentBuilder("To see all players online, use /glist showall.").color(ChatColor.YELLOW).create());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static class FindCommand extends Command {
|
||||
private final RedisBungeeBungeePlugin plugin;
|
||||
|
||||
public FindCommand(RedisBungeeBungeePlugin plugin) {
|
||||
super("find", "bungeecord.command.find", "rfind");
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(final CommandSender sender, final String[] args) {
|
||||
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (args.length > 0) {
|
||||
UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
|
||||
if (uuid == null) {
|
||||
sender.sendMessage(PLAYER_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
ServerInfo si = plugin.getProxy().getServerInfo(plugin.getApi().getServerFor(uuid));
|
||||
if (si != null) {
|
||||
TextComponent message = new TextComponent();
|
||||
message.setColor(ChatColor.BLUE);
|
||||
message.setText(args[0] + " is on " + si.getName() + ".");
|
||||
sender.sendMessage(message);
|
||||
} else {
|
||||
sender.sendMessage(PLAYER_NOT_FOUND);
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage(NO_PLAYER_SPECIFIED);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static class LastSeenCommand extends Command {
|
||||
private final RedisBungeeBungeePlugin plugin;
|
||||
|
||||
public LastSeenCommand(RedisBungeeBungeePlugin plugin) {
|
||||
super("lastseen", "redisbungee.command.lastseen", "rlastseen");
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(final CommandSender sender, final String[] args) {
|
||||
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (args.length > 0) {
|
||||
UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
|
||||
if (uuid == null) {
|
||||
sender.sendMessage(PLAYER_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
long secs = plugin.getApi().getLastOnline(uuid);
|
||||
TextComponent message = new TextComponent();
|
||||
if (secs == 0) {
|
||||
message.setColor(ChatColor.GREEN);
|
||||
message.setText(args[0] + " is currently online.");
|
||||
} else if (secs != -1) {
|
||||
message.setColor(ChatColor.BLUE);
|
||||
message.setText(args[0] + " was last online on " + new SimpleDateFormat().format(secs) + ".");
|
||||
} else {
|
||||
message.setColor(ChatColor.RED);
|
||||
message.setText(args[0] + " has never been online.");
|
||||
}
|
||||
sender.sendMessage(message);
|
||||
} else {
|
||||
sender.sendMessage(NO_PLAYER_SPECIFIED);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static class IpCommand extends Command {
|
||||
private final RedisBungeeBungeePlugin plugin;
|
||||
|
||||
public IpCommand(RedisBungeeBungeePlugin plugin) {
|
||||
super("ip", "redisbungee.command.ip", "playerip", "rip", "rplayerip");
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(final CommandSender sender, final String[] args) {
|
||||
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (args.length > 0) {
|
||||
UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
|
||||
if (uuid == null) {
|
||||
sender.sendMessage(PLAYER_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
InetAddress ia = plugin.getApi().getPlayerIp(uuid);
|
||||
if (ia != null) {
|
||||
TextComponent message = new TextComponent();
|
||||
message.setColor(ChatColor.GREEN);
|
||||
message.setText(args[0] + " is connected from " + ia.toString() + ".");
|
||||
sender.sendMessage(message);
|
||||
} else {
|
||||
sender.sendMessage(PLAYER_NOT_FOUND);
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage(NO_PLAYER_SPECIFIED);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static class PlayerProxyCommand extends Command {
|
||||
private final RedisBungeeBungeePlugin plugin;
|
||||
|
||||
public PlayerProxyCommand(RedisBungeeBungeePlugin plugin) {
|
||||
super("pproxy", "redisbungee.command.pproxy");
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(final CommandSender sender, final String[] args) {
|
||||
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (args.length > 0) {
|
||||
UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
|
||||
if (uuid == null) {
|
||||
sender.sendMessage(PLAYER_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
String proxy = plugin.getApi().getProxy(uuid);
|
||||
if (proxy != null) {
|
||||
TextComponent message = new TextComponent();
|
||||
message.setColor(ChatColor.GREEN);
|
||||
message.setText(args[0] + " is connected to " + proxy + ".");
|
||||
sender.sendMessage(message);
|
||||
} else {
|
||||
sender.sendMessage(PLAYER_NOT_FOUND);
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage(NO_PLAYER_SPECIFIED);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static class SendToAll extends Command {
|
||||
private final RedisBungeeBungeePlugin plugin;
|
||||
|
||||
public SendToAll(RedisBungeeBungeePlugin plugin) {
|
||||
super("sendtoall", "redisbungee.command.sendtoall", "rsendtoall");
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
if (args.length > 0) {
|
||||
String command = Joiner.on(" ").skipNulls().join(args);
|
||||
plugin.getApi().sendProxyCommand(command);
|
||||
TextComponent message = new TextComponent();
|
||||
message.setColor(ChatColor.GREEN);
|
||||
message.setText("Sent the command /" + command + " to all proxies.");
|
||||
sender.sendMessage(message);
|
||||
} else {
|
||||
sender.sendMessage(NO_COMMAND_SPECIFIED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class ServerId extends Command {
|
||||
private final RedisBungeeBungeePlugin plugin;
|
||||
|
||||
public ServerId(RedisBungeeBungeePlugin plugin) {
|
||||
super("serverid", "redisbungee.command.serverid", "rserverid");
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
TextComponent textComponent = new TextComponent();
|
||||
textComponent.setText("You are on " + plugin.getApi().getServerId() + ".");
|
||||
textComponent.setColor(ChatColor.YELLOW);
|
||||
sender.sendMessage(textComponent);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ServerIds extends Command {
|
||||
private final RedisBungeeBungeePlugin plugin;
|
||||
public ServerIds(RedisBungeeBungeePlugin plugin) {
|
||||
super("serverids", "redisbungee.command.serverids");
|
||||
this.plugin =plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] strings) {
|
||||
TextComponent textComponent = new TextComponent();
|
||||
textComponent.setText("All server IDs: " + Joiner.on(", ").join(plugin.getApi().getAllServers()));
|
||||
textComponent.setColor(ChatColor.YELLOW);
|
||||
sender.sendMessage(textComponent);
|
||||
}
|
||||
}
|
||||
|
||||
public static class PlistCommand extends Command {
|
||||
private final RedisBungeeBungeePlugin plugin;
|
||||
|
||||
public PlistCommand(RedisBungeeBungeePlugin plugin) {
|
||||
super("plist", "redisbungee.command.plist", "rplist");
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(final CommandSender sender, final String[] args) {
|
||||
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
String proxy = args.length >= 1 ? args[0] : plugin.getConfiguration().getServerId();
|
||||
if (!plugin.getServerIds().contains(proxy)) {
|
||||
sender.sendMessage(new ComponentBuilder(proxy + " is not a valid proxy. See /serverids for valid proxies.").color(ChatColor.RED).create());
|
||||
return;
|
||||
}
|
||||
Set<UUID> players = plugin.getApi().getPlayersOnProxy(proxy);
|
||||
BaseComponent[] playersOnline = new ComponentBuilder("").color(ChatColor.YELLOW)
|
||||
.append(playerPlural(players.size()) + " currently on proxy " + proxy + ".").create();
|
||||
if (args.length >= 2 && args[1].equals("showall")) {
|
||||
Multimap<String, UUID> serverToPlayers = plugin.getApi().getServerToPlayers();
|
||||
Multimap<String, String> human = HashMultimap.create();
|
||||
for (Map.Entry<String, UUID> entry : serverToPlayers.entries()) {
|
||||
if (players.contains(entry.getValue())) {
|
||||
human.put(entry.getKey(), plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false));
|
||||
}
|
||||
}
|
||||
for (String server : new TreeSet<>(human.keySet())) {
|
||||
TextComponent serverName = new TextComponent();
|
||||
serverName.setColor(ChatColor.RED);
|
||||
serverName.setText("[" + server + "] ");
|
||||
TextComponent serverCount = new TextComponent();
|
||||
serverCount.setColor(ChatColor.YELLOW);
|
||||
serverCount.setText("(" + human.get(server).size() + "): ");
|
||||
TextComponent serverPlayers = new TextComponent();
|
||||
serverPlayers.setColor(ChatColor.WHITE);
|
||||
serverPlayers.setText(Joiner.on(", ").join(human.get(server)));
|
||||
sender.sendMessage(serverName, serverCount, serverPlayers);
|
||||
}
|
||||
sender.sendMessage(playersOnline);
|
||||
} else {
|
||||
sender.sendMessage(playersOnline);
|
||||
sender.sendMessage(new ComponentBuilder("To see all players online, use /plist " + proxy + " showall.").color(ChatColor.YELLOW).create());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package com.imaginarycode.minecraft.redisbungee.events;
|
||||
|
||||
import net.md_5.bungee.api.plugin.Event;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* This event is sent when a player connects to a new server. RedisBungee sends the event only when
|
||||
* the proxy the player has been connected to is different than the local proxy.
|
||||
* <p>
|
||||
* This event corresponds to {@link net.md_5.bungee.api.event.ServerConnectedEvent}, and is fired
|
||||
* asynchronously.
|
||||
*
|
||||
* @since 0.3.4
|
||||
*/
|
||||
public class PlayerChangedServerNetworkEvent extends Event {
|
||||
private final UUID uuid;
|
||||
private final String previousServer;
|
||||
private final String server;
|
||||
|
||||
public PlayerChangedServerNetworkEvent(UUID uuid, String previousServer, String server) {
|
||||
this.uuid = uuid;
|
||||
this.previousServer = previousServer;
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
public UUID getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public String getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
public String getPreviousServer() {
|
||||
return previousServer;
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package com.imaginarycode.minecraft.redisbungee.events;
|
||||
|
||||
import net.md_5.bungee.api.plugin.Event;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* This event is sent when a player joins the network. RedisBungee sends the event only when
|
||||
* the proxy the player has been connected to is different than the local proxy.
|
||||
* <p>
|
||||
* This event corresponds to {@link net.md_5.bungee.api.event.PostLoginEvent}, and is fired
|
||||
* asynchronously.
|
||||
*
|
||||
* @since 0.3.4
|
||||
*/
|
||||
public class PlayerJoinedNetworkEvent extends Event {
|
||||
private final UUID uuid;
|
||||
|
||||
public PlayerJoinedNetworkEvent(UUID uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public UUID getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package com.imaginarycode.minecraft.redisbungee.events;
|
||||
|
||||
import net.md_5.bungee.api.plugin.Event;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* This event is sent when a player disconnects. RedisBungee sends the event only when
|
||||
* the proxy the player has been connected to is different than the local proxy.
|
||||
* <p>
|
||||
* This event corresponds to {@link net.md_5.bungee.api.event.PlayerDisconnectEvent}, and is fired
|
||||
* asynchronously.
|
||||
*
|
||||
* @since 0.3.4
|
||||
*/
|
||||
public class PlayerLeftNetworkEvent extends Event {
|
||||
private final UUID uuid;
|
||||
|
||||
public PlayerLeftNetworkEvent(UUID uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public UUID getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package com.imaginarycode.minecraft.redisbungee.events;
|
||||
|
||||
import net.md_5.bungee.api.plugin.Event;
|
||||
|
||||
/**
|
||||
* This event is posted when a PubSub message is received.
|
||||
* <p>
|
||||
* <strong>Warning</strong>: This event is fired in a separate thread!
|
||||
*
|
||||
* @since 0.2.6
|
||||
*/
|
||||
|
||||
public class PubSubMessageEvent extends Event {
|
||||
private final String channel;
|
||||
private final String message;
|
||||
|
||||
public PubSubMessageEvent(String channel, String message) {
|
||||
this.channel = channel;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
name: RedisBungee
|
||||
main: com.imaginarycode.minecraft.redisbungee.RedisBungeeVelocityPlugin
|
||||
version: ${project.version}
|
||||
author: Chunkr and Govindas limework
|
||||
authors:
|
||||
- chunkr
|
||||
- Govindas Limework
|
||||
# This is used so that we can automatically override default BungeeCord behavior.
|
||||
softDepends: ["cmd_find", "cmd_list"]
|
125
RedisBungee-Velocity/pom.xml
Normal file
125
RedisBungee-Velocity/pom.xml
Normal file
@ -0,0 +1,125 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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">
|
||||
<parent>
|
||||
<artifactId>RedisBungee</artifactId>
|
||||
<groupId>com.imaginarycode.minecraft</groupId>
|
||||
<version>0.8.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>RedisBungee-Velocity</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
</properties>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>papermc-repo</id>
|
||||
<url>https://repo.papermc.io/repository/maven-public/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.3.2</version>
|
||||
<configuration>
|
||||
<source>8</source>
|
||||
<reportOutputDirectory>../javadoc</reportOutputDirectory>
|
||||
<destDir>${project.name}</destDir>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.2.4</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals><goal>shade</goal></goals>
|
||||
<configuration>
|
||||
<relocations>
|
||||
<relocation>
|
||||
<pattern>redis.clients.jedis</pattern>
|
||||
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.jedis
|
||||
</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>redis.clients.util</pattern>
|
||||
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.jedisutil
|
||||
</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>org.apache.commons.pool</pattern>
|
||||
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.commonspool
|
||||
</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>com.squareup.okhttp</pattern>
|
||||
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.okhttp
|
||||
</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>okio</pattern>
|
||||
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.okio
|
||||
</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>com.google</pattern>
|
||||
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.google
|
||||
</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>org.json</pattern>
|
||||
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.json
|
||||
</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>org.checkerframework</pattern>
|
||||
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.checkframework
|
||||
</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.imaginarycode.minecraft</groupId>
|
||||
<artifactId>RedisBungee-API</artifactId>
|
||||
<version>${parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.velocitypowered</groupId>
|
||||
<artifactId>velocity-api</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<type>jar</type>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -0,0 +1,38 @@
|
||||
package com.imaginarycode.minecraft.redisbungee;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.imaginarycode.minecraft.redisbungee.internal.DataManager;
|
||||
import com.velocitypowered.api.proxy.InboundConnection;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ServerConnection;
|
||||
import redis.clients.jedis.Pipeline;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public class RBUtils {
|
||||
|
||||
private static final Gson gson = new Gson();
|
||||
|
||||
protected static void createPlayer(Player player, Pipeline pipeline, boolean fireEvent) {
|
||||
Optional<ServerConnection> server = player.getCurrentServer();
|
||||
server.ifPresent(serverConnection -> pipeline.hset("player:" + player.getUniqueId().toString(), "server", serverConnection.getServerInfo().getName()));
|
||||
|
||||
Map<String, String> playerData = new HashMap<>(4);
|
||||
playerData.put("online", "0");
|
||||
playerData.put("ip", player.getRemoteAddress().getHostName());
|
||||
playerData.put("proxy", RedisBungeeAPI.getRedisBungeeApi().getServerId());
|
||||
|
||||
pipeline.sadd("proxy:" + RedisBungeeAPI.getRedisBungeeApi().getServerId() + ":usersOnline", player.getUniqueId().toString());
|
||||
pipeline.hmset("player:" + player.getUniqueId().toString(), playerData);
|
||||
|
||||
if (fireEvent) {
|
||||
pipeline.publish("redisbungee-data", gson.toJson(new DataManager.DataManagerMessage<>(
|
||||
player.getUniqueId(), RedisBungeeAPI.getRedisBungeeApi().getServerId(), DataManager.DataManagerMessage.Action.JOIN,
|
||||
new DataManager.LoginPayload(player.getRemoteAddress().getAddress()))));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package com.imaginarycode.minecraft.redisbungee;
|
||||
|
||||
|
||||
import com.velocitypowered.api.command.CommandSource;
|
||||
import com.velocitypowered.api.permission.Tristate;
|
||||
import net.kyori.adventure.permission.PermissionChecker;
|
||||
import net.kyori.adventure.util.TriState;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
public class RedisBungeeCommandSource implements CommandSource {
|
||||
private static final RedisBungeeCommandSource singleton;
|
||||
|
||||
static {
|
||||
singleton = new RedisBungeeCommandSource();
|
||||
}
|
||||
|
||||
public static RedisBungeeCommandSource getSingleton() {
|
||||
return singleton;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(String permission) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tristate getPermissionValue(String s) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PermissionChecker getPermissionChecker() {
|
||||
return PermissionChecker.always(TriState.TRUE);
|
||||
}
|
||||
}
|
@ -0,0 +1,161 @@
|
||||
package com.imaginarycode.minecraft.redisbungee;
|
||||
|
||||
import com.imaginarycode.minecraft.redisbungee.internal.AbstractRedisBungeeListener;
|
||||
import com.imaginarycode.minecraft.redisbungee.internal.DataManager;
|
||||
import com.imaginarycode.minecraft.redisbungee.internal.RedisBungeePlugin;
|
||||
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
|
||||
import com.imaginarycode.minecraft.redisbungee.internal.RedisUtil;
|
||||
import com.imaginarycode.minecraft.redisbungee.internal.util.RedisCallable;
|
||||
import com.velocitypowered.api.event.Continuation;
|
||||
import com.velocitypowered.api.event.PostOrder;
|
||||
import com.velocitypowered.api.event.ResultedEvent;
|
||||
import com.velocitypowered.api.event.Subscribe;
|
||||
import com.velocitypowered.api.event.connection.DisconnectEvent;
|
||||
import com.velocitypowered.api.event.connection.LoginEvent;
|
||||
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
||||
import com.velocitypowered.api.event.connection.PostLoginEvent;
|
||||
import com.velocitypowered.api.event.player.ServerConnectedEvent;
|
||||
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ServerConnection;
|
||||
import com.velocitypowered.api.proxy.server.ServerPing;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.Pipeline;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.*;
|
||||
|
||||
public class RedisBungeeListener extends AbstractRedisBungeeListener<LoginEvent, PostLoginEvent, DisconnectEvent, ServerConnectedEvent, ProxyPingEvent, PluginMessageEvent, PubSubMessageEvent, Continuation> {
|
||||
|
||||
|
||||
public RedisBungeeListener(RedisBungeePlugin<?> plugin, List<InetAddress> exemptAddresses) {
|
||||
super(plugin, exemptAddresses);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Subscribe
|
||||
public void onLogin(LoginEvent event, Continuation continuation) {
|
||||
plugin.executeAsync(new RedisCallable<Void>(plugin) {
|
||||
@Override
|
||||
protected Void call(Jedis jedis) {
|
||||
try {
|
||||
if (!event.getResult().isAllowed()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// We make sure they aren't trying to use an existing player's name.
|
||||
// This is problematic for online-mode servers as they always disconnect old clients.
|
||||
if (plugin.isOnlineMode()) {
|
||||
Player player = (Player) plugin.getPlayer(event.getPlayer().getUsername());
|
||||
|
||||
if (player != null) {
|
||||
event.setResult(ResultedEvent.ComponentResult.denied(Component.text(ONLINE_MODE_RECONNECT)));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
for (String s : plugin.getServerIds()) {
|
||||
if (jedis.sismember("proxy:" + s + ":usersOnline", event.getPlayer().getUniqueId().toString())) {
|
||||
event.setResult(ResultedEvent.ComponentResult.denied(Component.text(ALREADY_LOGGED_IN)));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} finally {
|
||||
continuation.resume();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@Subscribe
|
||||
public void onPostLogin(PostLoginEvent event) {
|
||||
plugin.executeAsync(new RedisCallable<Void>(plugin) {
|
||||
@Override
|
||||
protected Void call(Jedis jedis) {
|
||||
// this code was moved out from login event due being async..
|
||||
// and it can be cancelled but it will show as false in redis-bungee
|
||||
// which will register the player into the redis database.
|
||||
Pipeline pipeline = jedis.pipelined();
|
||||
plugin.getUuidTranslator().persistInfo(event.getPlayer().getUsername(), event.getPlayer().getUniqueId(), pipeline);
|
||||
RBUtils.createPlayer(event.getPlayer(), pipeline, false);
|
||||
pipeline.sync();
|
||||
// the end of moved code.
|
||||
|
||||
jedis.publish("redisbungee-data", gson.toJson(new DataManager.DataManagerMessage<>(
|
||||
event.getPlayer().getUniqueId(), plugin.getApi().getServerId(), DataManager.DataManagerMessage.Action.JOIN,
|
||||
new DataManager.LoginPayload(event.getPlayer().getRemoteAddress().getAddress()))));
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@Subscribe
|
||||
public void onPlayerDisconnect(DisconnectEvent event) {
|
||||
plugin.executeAsync(new RedisCallable<Void>(plugin) {
|
||||
@Override
|
||||
protected Void call(Jedis jedis) {
|
||||
Pipeline pipeline = jedis.pipelined();
|
||||
RedisUtil.cleanUpPlayer(event.getPlayer().getUniqueId().toString(), pipeline);
|
||||
pipeline.sync();
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@Subscribe
|
||||
public void onServerChange(ServerConnectedEvent event) {
|
||||
Optional<ServerConnection> optionalServerConnection = event.getPlayer().getCurrentServer();
|
||||
final String currentServer = optionalServerConnection.map(serverConnection -> serverConnection.getServerInfo().getName()).orElse(null);
|
||||
plugin.executeAsync(new RedisCallable<Void>(plugin) {
|
||||
@Override
|
||||
protected Void call(Jedis jedis) {
|
||||
jedis.hset("player:" + event.getPlayer().getUniqueId().toString(), "server", event.getServer().getServerInfo().getName());
|
||||
jedis.publish("redisbungee-data", gson.toJson(new DataManager.DataManagerMessage<>(
|
||||
event.getPlayer().getUniqueId(), plugin.getApi().getServerId(), DataManager.DataManagerMessage.Action.SERVER_CHANGE,
|
||||
new DataManager.ServerChangePayload(event.getServer().getServerInfo().getName(), currentServer))));
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@Subscribe(order = PostOrder.EARLY)
|
||||
public void onPing(ProxyPingEvent event) {
|
||||
if (exemptAddresses.contains(event.getConnection().getRemoteAddress().getAddress())) {
|
||||
return;
|
||||
}
|
||||
ServerPing oldPing = event.getPing();
|
||||
int max = oldPing.getPlayers().map(ServerPing.Players::getMax).orElse(0);
|
||||
List<ServerPing.SamplePlayer> list = oldPing.getPlayers().map(ServerPing.Players::getSample).orElse(Collections.emptyList());
|
||||
event.setPing(new ServerPing(oldPing.getVersion(), new ServerPing.Players(plugin.getCount(), max, list), oldPing.getDescriptionComponent(), oldPing.getFavicon().orElse(null)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPluginMessage(PluginMessageEvent event) {
|
||||
/*
|
||||
* Ham1255 note: for some reason plugin messages were not working in velocity?
|
||||
* not sure how to fix, but for now i have removed the code until a fix is made.
|
||||
*
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Subscribe
|
||||
public void onPubSubMessage(PubSubMessageEvent event) {
|
||||
if (event.getChannel().equals("redisbungee-allservers") || event.getChannel().equals("redisbungee-" + plugin.getApi().getServerId())) {
|
||||
String message = event.getMessage();
|
||||
if (message.startsWith("/"))
|
||||
message = message.substring(1);
|
||||
plugin.logInfo("Invoking command via PubSub: /" + message);
|
||||
((RedisBungeeVelocityPlugin)plugin).getProxy().getCommandManager().executeAsync(RedisBungeeCommandSource.getSingleton(), message);//.dispatchCommand(RedisBungeeCommandSource.getSingleton(), message);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,667 @@
|
||||
package com.imaginarycode.minecraft.redisbungee;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.inject.Inject;
|
||||
import com.imaginarycode.minecraft.redisbungee.commands.RedisBungeeCommands;
|
||||
import com.imaginarycode.minecraft.redisbungee.events.PlayerChangedServerNetworkEvent;
|
||||
import com.imaginarycode.minecraft.redisbungee.events.PlayerJoinedNetworkEvent;
|
||||
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
|
||||
import com.imaginarycode.minecraft.redisbungee.internal.*;
|
||||
import com.imaginarycode.minecraft.redisbungee.internal.summoners.JedisSummoner;
|
||||
import com.imaginarycode.minecraft.redisbungee.internal.summoners.SinglePoolJedisSummoner;
|
||||
import com.imaginarycode.minecraft.redisbungee.internal.util.IOUtil;
|
||||
import com.imaginarycode.minecraft.redisbungee.internal.util.LuaManager;
|
||||
import com.imaginarycode.minecraft.redisbungee.internal.util.uuid.NameFetcher;
|
||||
import com.imaginarycode.minecraft.redisbungee.internal.util.uuid.UUIDFetcher;
|
||||
import com.imaginarycode.minecraft.redisbungee.internal.util.uuid.UUIDTranslator;
|
||||
import com.squareup.okhttp.Dispatcher;
|
||||
import com.squareup.okhttp.OkHttpClient;
|
||||
import com.velocitypowered.api.event.Subscribe;
|
||||
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
|
||||
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
|
||||
import com.velocitypowered.api.plugin.annotation.DataDirectory;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.api.scheduler.ScheduledTask;
|
||||
import org.slf4j.Logger;
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.JedisPool;
|
||||
import redis.clients.jedis.JedisPoolConfig;
|
||||
import redis.clients.jedis.Pipeline;
|
||||
import redis.clients.jedis.exceptions.JedisConnectionException;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
public class RedisBungeeVelocityPlugin implements RedisBungeePlugin<Player> {
|
||||
|
||||
private static final Gson gson = new Gson();
|
||||
private final ProxyServer server;
|
||||
private final Logger logger;
|
||||
private final File dataFolder;
|
||||
private RedisBungeeAPI api;
|
||||
private PubSubListener psl = null;
|
||||
private JedisSummoner jedisSummoner;
|
||||
private UUIDTranslator uuidTranslator;
|
||||
private RedisBungeeConfiguration configuration;
|
||||
private VelocityDataManager dataManager;
|
||||
private OkHttpClient httpClient;
|
||||
private volatile List<String> serverIds;
|
||||
private final AtomicInteger nagAboutServers = new AtomicInteger();
|
||||
private final AtomicInteger globalPlayerCount = new AtomicInteger();
|
||||
private ScheduledTask integrityCheck;
|
||||
private ScheduledTask heartbeatTask;
|
||||
private LuaManager.Script serverToPlayersScript;
|
||||
private LuaManager.Script getPlayerCountScript;
|
||||
|
||||
private static final Object SERVER_TO_PLAYERS_KEY = new Object();
|
||||
private final Cache<Object, Multimap<String, UUID>> serverToPlayersCache = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(5, TimeUnit.SECONDS)
|
||||
.build();
|
||||
|
||||
|
||||
@Inject
|
||||
public RedisBungeeVelocityPlugin(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory) {
|
||||
this.server = server;
|
||||
this.logger = logger;
|
||||
this.dataFolder = dataDirectory.toFile();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RedisBungeeConfiguration getConfiguration() {
|
||||
return this.configuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return this.globalPlayerCount.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentCount() {
|
||||
Long count = (Long) getPlayerCountScript.eval(ImmutableList.<String>of(), ImmutableList.<String>of());
|
||||
return count.intValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getLocalPlayersAsUuidStrings() {
|
||||
ImmutableSet.Builder<String> builder = ImmutableSet.builder();
|
||||
for (Player player : getProxy().getAllPlayers()) {
|
||||
builder.add(player.getUniqueId().toString());
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataManager<Player, ?, ?, ?> getDataManager() {
|
||||
return this.dataManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> getPlayers() {
|
||||
ImmutableSet.Builder<UUID> setBuilder = ImmutableSet.builder();
|
||||
if (isJedisAvailable()) {
|
||||
try (Jedis rsc = requestJedis()) {
|
||||
List<String> keys = new ArrayList<>();
|
||||
for (String i : getServerIds()) {
|
||||
keys.add("proxy:" + i + ":usersOnline");
|
||||
}
|
||||
if (!keys.isEmpty()) {
|
||||
Set<String> users = rsc.sunion(keys.toArray(new String[keys.size()]));
|
||||
if (users != null && !users.isEmpty()) {
|
||||
for (String user : users) {
|
||||
try {
|
||||
setBuilder = setBuilder.add(UUID.fromString(user));
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (JedisConnectionException e) {
|
||||
// Redis server has disappeared!
|
||||
getLogger().error("Unable to get connection from pool - did your Redis server go away?", e);
|
||||
throw new RuntimeException("Unable to get all players online", e);
|
||||
}
|
||||
}
|
||||
return setBuilder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Jedis requestJedis() {
|
||||
return this.jedisSummoner.requestJedis();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isJedisAvailable() {
|
||||
return this.jedisSummoner.isJedisAvailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RedisBungeeAPI getApi() {
|
||||
return this.api;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUIDTranslator getUuidTranslator() {
|
||||
return this.uuidTranslator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Multimap<String, UUID> serversToPlayers() {
|
||||
try {
|
||||
return serverToPlayersCache.get(SERVER_TO_PLAYERS_KEY, new Callable<Multimap<String, UUID>>() {
|
||||
@Override
|
||||
public Multimap<String, UUID> call() throws Exception {
|
||||
Collection<String> data = (Collection<String>) serverToPlayersScript.eval(ImmutableList.<String>of(), getServerIds());
|
||||
ImmutableMultimap.Builder<String, UUID> builder = ImmutableMultimap.builder();
|
||||
String key = null;
|
||||
for (String s : data) {
|
||||
if (key == null) {
|
||||
key = s;
|
||||
continue;
|
||||
}
|
||||
|
||||
builder.put(key, UUID.fromString(s));
|
||||
key = null;
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
});
|
||||
} catch (ExecutionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> getPlayersOnProxy(String proxyId) {
|
||||
checkArgument(getServerIds().contains(proxyId), proxyId + " is not a valid proxy ID");
|
||||
try (Jedis jedis = requestJedis()) {
|
||||
Set<String> users = jedis.smembers("proxy:" + proxyId + ":usersOnline");
|
||||
ImmutableSet.Builder<UUID> builder = ImmutableSet.builder();
|
||||
for (String user : users) {
|
||||
builder.add(UUID.fromString(user));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendProxyCommand(String serverId, String command) {
|
||||
checkArgument(getServerIds().contains(serverId) || serverId.equals("allservers"), "proxyId is invalid");
|
||||
sendChannelMessage("redisbungee-" + serverId, command);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getServerIds() {
|
||||
return serverIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getCurrentServerIds(boolean nag, boolean lagged) {
|
||||
try (Jedis jedis = requestJedis()) {
|
||||
long time = getRedisTime(jedis.time());
|
||||
int nagTime = 0;
|
||||
if (nag) {
|
||||
nagTime = nagAboutServers.decrementAndGet();
|
||||
if (nagTime <= 0) {
|
||||
nagAboutServers.set(10);
|
||||
}
|
||||
}
|
||||
ImmutableList.Builder<String> servers = ImmutableList.builder();
|
||||
Map<String, String> heartbeats = jedis.hgetAll("heartbeats");
|
||||
for (Map.Entry<String, String> entry : heartbeats.entrySet()) {
|
||||
try {
|
||||
long stamp = Long.parseLong(entry.getValue());
|
||||
if (lagged ? time >= stamp + 30 : time <= stamp + 30)
|
||||
servers.add(entry.getKey());
|
||||
else if (nag && nagTime <= 0) {
|
||||
getLogger().warn(entry.getKey() + " is " + (time - stamp) + " seconds behind! (Time not synchronized or server down?) and was removed from heartbeat.");
|
||||
jedis.hdel("heartbeats", entry.getKey());
|
||||
}
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
return servers.build();
|
||||
} catch (JedisConnectionException e) {
|
||||
getLogger().error("Unable to fetch server IDs", e);
|
||||
return Collections.singletonList(configuration.getServerId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PubSubListener getPubSubListener() {
|
||||
return this.psl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendChannelMessage(String channel, String message) {
|
||||
try (Jedis jedis = requestJedis()) {
|
||||
jedis.publish(channel, message);
|
||||
} catch (JedisConnectionException e) {
|
||||
// Redis server has disappeared!
|
||||
getLogger().error("Unable to get connection from pool - did your Redis server go away?", e);
|
||||
throw new RuntimeException("Unable to publish channel message", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeAsync(Runnable runnable) {
|
||||
this.getProxy().getScheduler().buildTask(this, runnable).schedule();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeAsyncAfter(Runnable runnable, TimeUnit timeUnit, int time) {
|
||||
this.getProxy().getScheduler().buildTask(this, runnable).delay(time, timeUnit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void callEvent(Object event) {
|
||||
this.getProxy().getEventManager().fire(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnlineMode() {
|
||||
return this.getProxy().getConfiguration().isOnlineMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logInfo(String msg) {
|
||||
this.getLogger().info(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logWarn(String msg) {
|
||||
this.getLogger().warn(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logFatal(String msg) {
|
||||
this.getLogger().error(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player getPlayer(UUID uuid) {
|
||||
return this.getProxy().getPlayer(uuid).orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player getPlayer(String name) {
|
||||
return this.getProxy().getPlayer(name).orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getPlayerUUID(String player) {
|
||||
return this.getProxy().getPlayer(player).map(Player::getUniqueId).orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPlayerName(UUID player) {
|
||||
return this.getProxy().getPlayer(player).map(Player::getUsername).orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPlayerServerName(Player player) {
|
||||
return player.getCurrentServer().map(serverConnection -> serverConnection.getServerInfo().getName()).orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPlayerOnAServer(Player player) {
|
||||
return player.getCurrentServer().isPresent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetAddress getPlayerIp(Player player) {
|
||||
return player.getRemoteAddress().getAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendProxyCommand(String cmd) {
|
||||
checkArgument(getServerIds().contains(this.configuration.getServerId()) || this.configuration.getServerId().equals("allservers"), "proxyId is invalid");
|
||||
sendChannelMessage("redisbungee-" + this.configuration.getServerId(), cmd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getRedisTime(List<String> timeRes) {
|
||||
return Long.parseLong(timeRes.get(0));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
try {
|
||||
loadConfig();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Unable to load/save config", e);
|
||||
} catch (JedisConnectionException e) {
|
||||
throw new RuntimeException("Unable to connect to your Redis server!", e);
|
||||
}
|
||||
this.api = new RedisBungeeAPI(this);
|
||||
// call old plugin class to support old plugins
|
||||
new RedisBungee(api);
|
||||
if (isJedisAvailable()) {
|
||||
try (Jedis tmpRsc = requestJedis()) {
|
||||
// This is more portable than INFO <section>
|
||||
String info = tmpRsc.info();
|
||||
for (String s : info.split("\r\n")) {
|
||||
if (s.startsWith("redis_version:")) {
|
||||
String version = s.split(":")[1];
|
||||
getLogger().info(version + " <- redis version");
|
||||
if (!RedisUtil.isRedisVersionRight(version)) {
|
||||
getLogger().warn("Your version of Redis (" + version + ") is not at least version 6.0 RedisBungee requires a newer version of Redis.");
|
||||
throw new RuntimeException("Unsupported Redis version detected");
|
||||
} else {
|
||||
LuaManager manager = new LuaManager(this);
|
||||
serverToPlayersScript = manager.createScript(IOUtil.readInputStreamAsString(getResourceAsStream("lua/server_to_players.lua")));
|
||||
getPlayerCountScript = manager.createScript(IOUtil.readInputStreamAsString(getResourceAsStream("lua/get_player_count.lua")));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
tmpRsc.hset("heartbeats", configuration.getServerId(), tmpRsc.time().get(0));
|
||||
|
||||
long uuidCacheSize = tmpRsc.hlen("uuid-cache");
|
||||
if (uuidCacheSize > 750000) {
|
||||
getLogger().info("Looks like you have a really big UUID cache! Run https://www.spigotmc.org/resources/redisbungeecleaner.8505/ as soon as possible.");
|
||||
}
|
||||
}
|
||||
serverIds = getCurrentServerIds(true, false);
|
||||
uuidTranslator = new UUIDTranslator(this);
|
||||
heartbeatTask = getProxy().getScheduler().buildTask(this, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try (Jedis rsc = requestJedis()) {
|
||||
long redisTime = getRedisTime(rsc.time());
|
||||
rsc.hset("heartbeats", configuration.getServerId(), String.valueOf(redisTime));
|
||||
} catch (JedisConnectionException e) {
|
||||
// Redis server has disappeared!
|
||||
getLogger().error("Unable to update heartbeat - did your Redis server go away?", e);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
serverIds = getCurrentServerIds(true, false);
|
||||
globalPlayerCount.set(getCurrentCount());
|
||||
} catch (Throwable e) {
|
||||
getLogger().error("Unable to update data - did your Redis server go away?", e);
|
||||
}
|
||||
}
|
||||
}).repeat(3, TimeUnit.SECONDS).schedule();
|
||||
dataManager = new VelocityDataManager(this);
|
||||
getProxy().getEventManager().register(this, new RedisBungeeListener(this, configuration.getExemptAddresses()));
|
||||
getProxy().getEventManager().register(this, dataManager);
|
||||
psl = new PubSubListener(this);
|
||||
getProxy().getScheduler().buildTask(this, psl).schedule();
|
||||
integrityCheck = getProxy().getScheduler().buildTask(null,new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try (Jedis tmpRsc = requestJedis()) {
|
||||
Set<String> players = getLocalPlayersAsUuidStrings();
|
||||
Set<String> playersInRedis = tmpRsc.smembers("proxy:" + configuration.getServerId() + ":usersOnline");
|
||||
List<String> lagged = getCurrentServerIds(false, true);
|
||||
|
||||
// Clean up lagged players.
|
||||
for (String s : lagged) {
|
||||
Set<String> laggedPlayers = tmpRsc.smembers("proxy:" + s + ":usersOnline");
|
||||
tmpRsc.del("proxy:" + s + ":usersOnline");
|
||||
if (!laggedPlayers.isEmpty()) {
|
||||
getLogger().info("Cleaning up lagged proxy " + s + " (" + laggedPlayers.size() + " players)...");
|
||||
for (String laggedPlayer : laggedPlayers) {
|
||||
RedisUtil.cleanUpPlayer(laggedPlayer, tmpRsc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Set<String> absentLocally = new HashSet<>(playersInRedis);
|
||||
absentLocally.removeAll(players);
|
||||
Set<String> absentInRedis = new HashSet<>(players);
|
||||
absentInRedis.removeAll(playersInRedis);
|
||||
|
||||
for (String member : absentLocally) {
|
||||
boolean found = false;
|
||||
for (String proxyId : getServerIds()) {
|
||||
if (proxyId.equals(configuration.getServerId())) continue;
|
||||
if (tmpRsc.sismember("proxy:" + proxyId + ":usersOnline", member)) {
|
||||
// Just clean up the set.
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
RedisUtil.cleanUpPlayer(member, tmpRsc);
|
||||
getLogger().warn("Player found in set that was not found locally and globally: " + member);
|
||||
} else {
|
||||
tmpRsc.srem("proxy:" + configuration.getServerId() + ":usersOnline", member);
|
||||
getLogger().warn("Player found in set that was not found locally, but is on another proxy: " + member);
|
||||
}
|
||||
}
|
||||
|
||||
Pipeline pipeline = tmpRsc.pipelined();
|
||||
|
||||
for (String player : absentInRedis) {
|
||||
// Player not online according to Redis but not BungeeCord.
|
||||
getLogger().warn("Player " + player + " is on the proxy but not in Redis.");
|
||||
|
||||
Player playerProxied = getProxy().getPlayer(UUID.fromString(player)).orElse(null);
|
||||
if (playerProxied == null)
|
||||
continue; // We'll deal with it later.
|
||||
|
||||
RBUtils.createPlayer(playerProxied, pipeline, true);
|
||||
}
|
||||
|
||||
pipeline.sync();
|
||||
} catch (Throwable e) {
|
||||
getLogger().error("Unable to fix up stored player data", e);
|
||||
}
|
||||
}
|
||||
}).repeat(1, TimeUnit.MINUTES).schedule();
|
||||
}
|
||||
// plugin messages are disabled for now
|
||||
//getProxy().registerChannel("legacy:redisbungee");
|
||||
//getProxy().registerChannel("RedisBungee");
|
||||
|
||||
// register commands
|
||||
// those still bungeecord commands will migrate them later.
|
||||
// if (configuration.doOverrideBungeeCommands()) {
|
||||
// getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.GlistCommand(this));
|
||||
// getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.FindCommand(this));
|
||||
// getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.LastSeenCommand(this));
|
||||
// getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.IpCommand(this));
|
||||
//
|
||||
// }
|
||||
// getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.SendToAll(this));
|
||||
// getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.ServerId(this));
|
||||
// getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.ServerIds(this));
|
||||
// getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.PlayerProxyCommand(this));
|
||||
// getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.PlistCommand(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
if (isJedisAvailable()) {
|
||||
// Poison the PubSub listener
|
||||
psl.poison();
|
||||
integrityCheck.cancel();
|
||||
heartbeatTask.cancel();
|
||||
try (Jedis tmpRsc = requestJedis()) {
|
||||
tmpRsc.hdel("heartbeats", configuration.getServerId());
|
||||
if (tmpRsc.scard("proxy:" + configuration.getServerId() + ":usersOnline") > 0) {
|
||||
Set<String> players = tmpRsc.smembers("proxy:" + configuration.getServerId() + ":usersOnline");
|
||||
for (String member : players)
|
||||
RedisUtil.cleanUpPlayer(member, tmpRsc);
|
||||
}
|
||||
}
|
||||
try {
|
||||
this.jedisSummoner.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
this.httpClient.getDispatcher().getExecutorService().shutdown();
|
||||
try {
|
||||
this.httpClient.getDispatcher().getExecutorService().awaitTermination(20, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadConfig() throws IOException {
|
||||
if (!getDataFolder().exists()) {
|
||||
getDataFolder().mkdir();
|
||||
}
|
||||
|
||||
File file = new File(getDataFolder(), "config.yml");
|
||||
|
||||
if (!file.exists()) {
|
||||
file.createNewFile();
|
||||
try (InputStream in = getResourceAsStream("example_config.yml");
|
||||
OutputStream out = Files.newOutputStream(file.toPath())) {
|
||||
ByteStreams.copy(in, out);
|
||||
}
|
||||
}
|
||||
|
||||
final Configuration yamlConfiguration = ConfigurationProvider.getProvider(YamlConfiguration.class).load(file);
|
||||
|
||||
final String redisServer = yamlConfiguration.getString("redis-server", "localhost");
|
||||
final int redisPort = yamlConfiguration.getInt("redis-port", 6379);
|
||||
final boolean useSSL = yamlConfiguration.getBoolean("useSSL", false);
|
||||
String redisPassword = yamlConfiguration.getString("redis-password", "");
|
||||
String serverId = yamlConfiguration.getString("server-id");
|
||||
|
||||
// check redis password
|
||||
if (redisPassword != null && (redisPassword.isEmpty() || redisPassword.equals("none"))) {
|
||||
redisPassword = null;
|
||||
getLogger().warning("INSECURE setup was detected Please set password for your redis instance.");
|
||||
}
|
||||
if (!useSSL) {
|
||||
getLogger().warning("INSECURE setup was detected Please setup ssl for your redis instance.");
|
||||
}
|
||||
// Configuration sanity checks.
|
||||
if (serverId == null || serverId.isEmpty()) {
|
||||
/*
|
||||
* this check causes the config comments to disappear somehow
|
||||
* I think due snake yaml limitations so as todo: write our own yaml parser?
|
||||
*/
|
||||
String genId = UUID.randomUUID().toString();
|
||||
getLogger().info("Generated server id " + genId + " and saving it to config.");
|
||||
yamlConfiguration.set("server-id", genId);
|
||||
ConfigurationProvider.getProvider(YamlConfiguration.class).save(yamlConfiguration, new File(getDataFolder(), "config.yml"));
|
||||
getLogger().info("Server id was generated: " + serverId);
|
||||
} else {
|
||||
getLogger().info("Loaded server id " + serverId + '.');
|
||||
}
|
||||
this.configuration = new RedisBungeeConfiguration(serverId, yamlConfiguration.getStringList("exempt-ip-addresses"), yamlConfiguration.getBoolean("register-bungee-commands", true));
|
||||
|
||||
if (redisServer != null && !redisServer.isEmpty()) {
|
||||
try {
|
||||
JedisPoolConfig config = new JedisPoolConfig();
|
||||
config.setMaxTotal(yamlConfiguration.getInt("max-redis-connections", 8));
|
||||
this.jedisSummoner = new SinglePoolJedisSummoner(new JedisPool(config, redisServer, redisPort, 0, redisPassword, useSSL));
|
||||
|
||||
} catch (JedisConnectionException e) {
|
||||
throw new RuntimeException("Unable to create Redis pool", e);
|
||||
}
|
||||
|
||||
// Test the connection
|
||||
try (Jedis rsc = requestJedis()) {
|
||||
rsc.ping();
|
||||
// If that worked, now we can check for an existing, alive Bungee:
|
||||
File crashFile = new File(getDataFolder(), "restarted_from_crash.txt");
|
||||
if (crashFile.exists()) {
|
||||
crashFile.delete();
|
||||
} else if (rsc.hexists("heartbeats", serverId)) {
|
||||
try {
|
||||
long value = Long.parseLong(rsc.hget("heartbeats", serverId));
|
||||
long redisTime = getRedisTime(rsc.time());
|
||||
if (redisTime < value + 20) {
|
||||
getLogger().error("You have launched a possible impostor BungeeCord instance. Another instance is already running.");
|
||||
getLogger().error("For data consistency reasons, RedisBungee will now disable itself.");
|
||||
getLogger().error("If this instance is coming up from a crash, create a file in your RedisBungee plugins directory with the name 'restarted_from_crash.txt' and RedisBungee will not perform this check.");
|
||||
throw new RuntimeException("Possible impostor instance!");
|
||||
}
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
httpClient = new OkHttpClient();
|
||||
Dispatcher dispatcher = new Dispatcher(Executors.newFixedThreadPool(6));
|
||||
httpClient.setDispatcher(dispatcher);
|
||||
NameFetcher.setHttpClient(httpClient);
|
||||
UUIDFetcher.setHttpClient(httpClient);
|
||||
|
||||
getLogger().info("Successfully connected to Redis.");
|
||||
} catch (JedisConnectionException e) {
|
||||
this.jedisSummoner.close();
|
||||
throw e;
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException("No redis server specified!");
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void proxyInit(ProxyInitializeEvent event) {
|
||||
start();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void proxyShutdownEvent(ProxyShutdownEvent event) {
|
||||
stop();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Class<?> getPubSubEventClass() {
|
||||
return PubSubMessageEvent.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getNetworkJoinEventClass() {
|
||||
return PlayerJoinedNetworkEvent.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getServerChangeEventClass() {
|
||||
return PlayerChangedServerNetworkEvent.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getNetworkQuitEventClass() {
|
||||
return PlayerJoinedNetworkEvent.class;
|
||||
}
|
||||
|
||||
|
||||
public ProxyServer getProxy() {
|
||||
return server;
|
||||
}
|
||||
|
||||
public Logger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
public File getDataFolder() {
|
||||
return this.dataFolder;
|
||||
}
|
||||
|
||||
public final InputStream getResourceAsStream(String name) {
|
||||
return getClass().getClassLoader().getResourceAsStream(name);
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package com.imaginarycode.minecraft.redisbungee;
|
||||
|
||||
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
|
||||
import com.imaginarycode.minecraft.redisbungee.internal.DataManager;
|
||||
import com.imaginarycode.minecraft.redisbungee.internal.RedisBungeePlugin;
|
||||
import com.velocitypowered.api.event.Subscribe;
|
||||
import com.velocitypowered.api.event.connection.DisconnectEvent;
|
||||
import com.velocitypowered.api.event.connection.PostLoginEvent;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
|
||||
|
||||
public class VelocityDataManager extends DataManager<Player, PostLoginEvent, DisconnectEvent, PubSubMessageEvent> {
|
||||
|
||||
public VelocityDataManager(RedisBungeePlugin<Player> plugin) {
|
||||
super(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Subscribe
|
||||
public void onPostLogin(PostLoginEvent event) {
|
||||
invalidate(event.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Subscribe
|
||||
public void onPlayerDisconnect(DisconnectEvent event) {
|
||||
invalidate(event.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Subscribe
|
||||
public void onPubSubMessage(PubSubMessageEvent event) {
|
||||
handlePubSubMessage(event.getChannel(), event.getMessage());
|
||||
}
|
||||
}
|
@ -0,0 +1,327 @@
|
||||
package com.imaginarycode.minecraft.redisbungee.commands;
|
||||
|
||||
import com.imaginarycode.minecraft.redisbungee.RedisBungeeAPI;
|
||||
|
||||
|
||||
/**
|
||||
* This class contains subclasses that are used for the commands RedisBungee overrides or includes: /glist, /find and /lastseen.
|
||||
* <p>
|
||||
* All classes use the {@link RedisBungeeAPI}.
|
||||
*
|
||||
* @author tuxed
|
||||
* @since 0.2.3
|
||||
*/
|
||||
public class RedisBungeeCommands {
|
||||
/*
|
||||
private static final BaseComponent[] NO_PLAYER_SPECIFIED =
|
||||
new ComponentBuilder("You must specify a player name.").color(ChatColor.RED).create();
|
||||
private static final BaseComponent[] PLAYER_NOT_FOUND =
|
||||
new ComponentBuilder("No such player found.").color(ChatColor.RED).create();
|
||||
private static final BaseComponent[] NO_COMMAND_SPECIFIED =
|
||||
new ComponentBuilder("You must specify a command to be run.").color(ChatColor.RED).create();
|
||||
|
||||
private static String playerPlural(int num) {
|
||||
return num == 1 ? num + " player is" : num + " players are";
|
||||
}
|
||||
|
||||
public static class GlistCommand extends Command {
|
||||
private final RedisBungeeVelocityPlugin plugin;
|
||||
|
||||
public GlistCommand(RedisBungeeVelocityPlugin plugin) {
|
||||
super("glist", "bungeecord.command.list", "redisbungee", "rglist");
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(final CommandSender sender, final String[] args) {
|
||||
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
int count = plugin.getApi().getPlayerCount();
|
||||
BaseComponent[] playersOnline = new ComponentBuilder("").color(ChatColor.YELLOW)
|
||||
.append(playerPlural(count) + " currently online.").create();
|
||||
if (args.length > 0 && args[0].equals("showall")) {
|
||||
Multimap<String, UUID> serverToPlayers = plugin.getApi().getServerToPlayers();
|
||||
Multimap<String, String> human = HashMultimap.create();
|
||||
for (Map.Entry<String, UUID> entry : serverToPlayers.entries()) {
|
||||
human.put(entry.getKey(), plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false));
|
||||
}
|
||||
for (String server : new TreeSet<>(serverToPlayers.keySet())) {
|
||||
TextComponent serverName = new TextComponent();
|
||||
serverName.setColor(ChatColor.GREEN);
|
||||
serverName.setText("[" + server + "] ");
|
||||
TextComponent serverCount = new TextComponent();
|
||||
serverCount.setColor(ChatColor.YELLOW);
|
||||
serverCount.setText("(" + serverToPlayers.get(server).size() + "): ");
|
||||
TextComponent serverPlayers = new TextComponent();
|
||||
serverPlayers.setColor(ChatColor.WHITE);
|
||||
serverPlayers.setText(Joiner.on(", ").join(human.get(server)));
|
||||
sender.sendMessage(serverName, serverCount, serverPlayers);
|
||||
}
|
||||
sender.sendMessage(playersOnline);
|
||||
} else {
|
||||
sender.sendMessage(playersOnline);
|
||||
sender.sendMessage(new ComponentBuilder("To see all players online, use /glist showall.").color(ChatColor.YELLOW).create());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static class FindCommand extends Command {
|
||||
private final RedisBungeeVelocityPlugin plugin;
|
||||
|
||||
public FindCommand(RedisBungeeVelocityPlugin plugin) {
|
||||
super("find", "bungeecord.command.find", "rfind");
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(final CommandSender sender, final String[] args) {
|
||||
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (args.length > 0) {
|
||||
UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
|
||||
if (uuid == null) {
|
||||
sender.sendMessage(PLAYER_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
ServerInfo si = plugin.getProxy().getServerInfo(plugin.getApi().getServerFor(uuid));
|
||||
if (si != null) {
|
||||
TextComponent message = new TextComponent();
|
||||
message.setColor(ChatColor.BLUE);
|
||||
message.setText(args[0] + " is on " + si.getName() + ".");
|
||||
sender.sendMessage(message);
|
||||
} else {
|
||||
sender.sendMessage(PLAYER_NOT_FOUND);
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage(NO_PLAYER_SPECIFIED);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static class LastSeenCommand extends Command {
|
||||
private final RedisBungeeVelocityPlugin plugin;
|
||||
|
||||
public LastSeenCommand(RedisBungeeVelocityPlugin plugin) {
|
||||
super("lastseen", "redisbungee.command.lastseen", "rlastseen");
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(final CommandSender sender, final String[] args) {
|
||||
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (args.length > 0) {
|
||||
UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
|
||||
if (uuid == null) {
|
||||
sender.sendMessage(PLAYER_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
long secs = plugin.getApi().getLastOnline(uuid);
|
||||
TextComponent message = new TextComponent();
|
||||
if (secs == 0) {
|
||||
message.setColor(ChatColor.GREEN);
|
||||
message.setText(args[0] + " is currently online.");
|
||||
} else if (secs != -1) {
|
||||
message.setColor(ChatColor.BLUE);
|
||||
message.setText(args[0] + " was last online on " + new SimpleDateFormat().format(secs) + ".");
|
||||
} else {
|
||||
message.setColor(ChatColor.RED);
|
||||
message.setText(args[0] + " has never been online.");
|
||||
}
|
||||
sender.sendMessage(message);
|
||||
} else {
|
||||
sender.sendMessage(NO_PLAYER_SPECIFIED);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static class IpCommand extends Command {
|
||||
private final RedisBungeeVelocityPlugin plugin;
|
||||
|
||||
public IpCommand(RedisBungeeVelocityPlugin plugin) {
|
||||
super("ip", "redisbungee.command.ip", "playerip", "rip", "rplayerip");
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(final CommandSender sender, final String[] args) {
|
||||
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (args.length > 0) {
|
||||
UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
|
||||
if (uuid == null) {
|
||||
sender.sendMessage(PLAYER_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
InetAddress ia = plugin.getApi().getPlayerIp(uuid);
|
||||
if (ia != null) {
|
||||
TextComponent message = new TextComponent();
|
||||
message.setColor(ChatColor.GREEN);
|
||||
message.setText(args[0] + " is connected from " + ia.toString() + ".");
|
||||
sender.sendMessage(message);
|
||||
} else {
|
||||
sender.sendMessage(PLAYER_NOT_FOUND);
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage(NO_PLAYER_SPECIFIED);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static class PlayerProxyCommand extends Command {
|
||||
private final RedisBungeeVelocityPlugin plugin;
|
||||
|
||||
public PlayerProxyCommand(RedisBungeeVelocityPlugin plugin) {
|
||||
super("pproxy", "redisbungee.command.pproxy");
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(final CommandSender sender, final String[] args) {
|
||||
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (args.length > 0) {
|
||||
UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
|
||||
if (uuid == null) {
|
||||
sender.sendMessage(PLAYER_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
String proxy = plugin.getApi().getProxy(uuid);
|
||||
if (proxy != null) {
|
||||
TextComponent message = new TextComponent();
|
||||
message.setColor(ChatColor.GREEN);
|
||||
message.setText(args[0] + " is connected to " + proxy + ".");
|
||||
sender.sendMessage(message);
|
||||
} else {
|
||||
sender.sendMessage(PLAYER_NOT_FOUND);
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage(NO_PLAYER_SPECIFIED);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static class SendToAll extends Command {
|
||||
private final RedisBungeeVelocityPlugin plugin;
|
||||
|
||||
public SendToAll(RedisBungeeVelocityPlugin plugin) {
|
||||
super("sendtoall", "redisbungee.command.sendtoall", "rsendtoall");
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
if (args.length > 0) {
|
||||
String command = Joiner.on(" ").skipNulls().join(args);
|
||||
plugin.getApi().sendProxyCommand(command);
|
||||
TextComponent message = new TextComponent();
|
||||
message.setColor(ChatColor.GREEN);
|
||||
message.setText("Sent the command /" + command + " to all proxies.");
|
||||
sender.sendMessage(message);
|
||||
} else {
|
||||
sender.sendMessage(NO_COMMAND_SPECIFIED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class ServerId extends Command {
|
||||
private final RedisBungeeVelocityPlugin plugin;
|
||||
|
||||
public ServerId(RedisBungeeVelocityPlugin plugin) {
|
||||
super("serverid", "redisbungee.command.serverid", "rserverid");
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
TextComponent textComponent = new TextComponent();
|
||||
textComponent.setText("You are on " + plugin.getApi().getServerId() + ".");
|
||||
textComponent.setColor(ChatColor.YELLOW);
|
||||
sender.sendMessage(textComponent);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ServerIds extends Command {
|
||||
private final RedisBungeeVelocityPlugin plugin;
|
||||
public ServerIds(RedisBungeeVelocityPlugin plugin) {
|
||||
super("serverids", "redisbungee.command.serverids");
|
||||
this.plugin =plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] strings) {
|
||||
TextComponent textComponent = new TextComponent();
|
||||
textComponent.setText("All server IDs: " + Joiner.on(", ").join(plugin.getApi().getAllServers()));
|
||||
textComponent.setColor(ChatColor.YELLOW);
|
||||
sender.sendMessage(textComponent);
|
||||
}
|
||||
}
|
||||
|
||||
public static class PlistCommand extends Command {
|
||||
private final RedisBungeeVelocityPlugin plugin;
|
||||
|
||||
public PlistCommand(RedisBungeeVelocityPlugin plugin) {
|
||||
super("plist", "redisbungee.command.plist", "rplist");
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(final CommandSender sender, final String[] args) {
|
||||
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
String proxy = args.length >= 1 ? args[0] : plugin.getConfiguration().getServerId();
|
||||
if (!plugin.getServerIds().contains(proxy)) {
|
||||
sender.sendMessage(new ComponentBuilder(proxy + " is not a valid proxy. See /serverids for valid proxies.").color(ChatColor.RED).create());
|
||||
return;
|
||||
}
|
||||
Set<UUID> players = plugin.getApi().getPlayersOnProxy(proxy);
|
||||
BaseComponent[] playersOnline = new ComponentBuilder("").color(ChatColor.YELLOW)
|
||||
.append(playerPlural(players.size()) + " currently on proxy " + proxy + ".").create();
|
||||
if (args.length >= 2 && args[1].equals("showall")) {
|
||||
Multimap<String, UUID> serverToPlayers = plugin.getApi().getServerToPlayers();
|
||||
Multimap<String, String> human = HashMultimap.create();
|
||||
for (Map.Entry<String, UUID> entry : serverToPlayers.entries()) {
|
||||
if (players.contains(entry.getValue())) {
|
||||
human.put(entry.getKey(), plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false));
|
||||
}
|
||||
}
|
||||
for (String server : new TreeSet<>(human.keySet())) {
|
||||
TextComponent serverName = new TextComponent();
|
||||
serverName.setColor(ChatColor.RED);
|
||||
serverName.setText("[" + server + "] ");
|
||||
TextComponent serverCount = new TextComponent();
|
||||
serverCount.setColor(ChatColor.YELLOW);
|
||||
serverCount.setText("(" + human.get(server).size() + "): ");
|
||||
TextComponent serverPlayers = new TextComponent();
|
||||
serverPlayers.setColor(ChatColor.WHITE);
|
||||
serverPlayers.setText(Joiner.on(", ").join(human.get(server)));
|
||||
sender.sendMessage(serverName, serverCount, serverPlayers);
|
||||
}
|
||||
sender.sendMessage(playersOnline);
|
||||
} else {
|
||||
sender.sendMessage(playersOnline);
|
||||
sender.sendMessage(new ComponentBuilder("To see all players online, use /plist " + proxy + " showall.").color(ChatColor.YELLOW).create());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package com.imaginarycode.minecraft.redisbungee.events;
|
||||
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* This event is sent when a player connects to a new server. RedisBungee sends the event only when
|
||||
* the proxy the player has been connected to is different than the local proxy.
|
||||
* <p>
|
||||
* This event corresponds to {@link net.md_5.bungee.api.event.ServerConnectedEvent}, and is fired
|
||||
* asynchronously.
|
||||
*
|
||||
* @since 0.3.4
|
||||
*/
|
||||
public class PlayerChangedServerNetworkEvent {
|
||||
private final UUID uuid;
|
||||
private final String previousServer;
|
||||
private final String server;
|
||||
|
||||
public PlayerChangedServerNetworkEvent(UUID uuid, String previousServer, String server) {
|
||||
this.uuid = uuid;
|
||||
this.previousServer = previousServer;
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
public UUID getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public String getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
public String getPreviousServer() {
|
||||
return previousServer;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package com.imaginarycode.minecraft.redisbungee.events;
|
||||
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* This event is sent when a player joins the network. RedisBungee sends the event only when
|
||||
* the proxy the player has been connected to is different than the local proxy.
|
||||
* <p>
|
||||
* This event corresponds to {@link net.md_5.bungee.api.event.PostLoginEvent}, and is fired
|
||||
* asynchronously.
|
||||
*
|
||||
* @since 0.3.4
|
||||
*/
|
||||
public class PlayerJoinedNetworkEvent {
|
||||
private final UUID uuid;
|
||||
|
||||
public PlayerJoinedNetworkEvent(UUID uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public UUID getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package com.imaginarycode.minecraft.redisbungee.events;
|
||||
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* This event is sent when a player disconnects. RedisBungee sends the event only when
|
||||
* the proxy the player has been connected to is different than the local proxy.
|
||||
* <p>
|
||||
* This event corresponds to {@link net.md_5.bungee.api.event.PlayerDisconnectEvent}, and is fired
|
||||
* asynchronously.
|
||||
*
|
||||
* @since 0.3.4
|
||||
*/
|
||||
public class PlayerLeftNetworkEvent {
|
||||
private final UUID uuid;
|
||||
|
||||
public PlayerLeftNetworkEvent(UUID uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public UUID getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package com.imaginarycode.minecraft.redisbungee.events;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This event is posted when a PubSub message is received.
|
||||
* <p>
|
||||
* <strong>Warning</strong>: This event is fired in a separate thread!
|
||||
*
|
||||
* @since 0.2.6
|
||||
*/
|
||||
|
||||
public class PubSubMessageEvent {
|
||||
private final String channel;
|
||||
private final String message;
|
||||
|
||||
public PubSubMessageEvent(String channel, String message) {
|
||||
this.channel = channel;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user