2022-07-06 22:24:08 +00:00
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 ;
2022-07-07 00:16:09 +00:00
import com.google.common.reflect.TypeToken ;
2022-07-06 22:24:08 +00:00
import com.google.inject.Inject ;
2022-07-17 05:51:24 +00:00
import com.imaginarycode.minecraft.redisbungee.api.* ;
2022-07-22 11:12:32 +00:00
import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration ;
2022-07-17 05:51:24 +00:00
import com.imaginarycode.minecraft.redisbungee.api.summoners.ClusterJedisSummoner ;
import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisSummoner ;
import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner ;
2022-07-20 09:21:24 +00:00
import com.imaginarycode.minecraft.redisbungee.api.tasks.HeartbeatTask ;
2022-07-17 05:51:24 +00:00
import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask ;
import com.imaginarycode.minecraft.redisbungee.api.util.RedisUtil ;
2022-07-19 11:30:45 +00:00
import com.imaginarycode.minecraft.redisbungee.api.util.payload.PayloadUtils ;
2022-07-17 05:51:24 +00:00
import com.imaginarycode.minecraft.redisbungee.api.util.io.IOUtil ;
import com.imaginarycode.minecraft.redisbungee.api.util.lua.LuaManager ;
import com.imaginarycode.minecraft.redisbungee.api.util.uuid.NameFetcher ;
import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDFetcher ;
import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDTranslator ;
2022-07-15 06:52:53 +00:00
import com.imaginarycode.minecraft.redisbungee.commands.RedisBungeeCommands ;
2022-07-06 22:24:08 +00:00
import com.imaginarycode.minecraft.redisbungee.events.PlayerChangedServerNetworkEvent ;
import com.imaginarycode.minecraft.redisbungee.events.PlayerJoinedNetworkEvent ;
2022-07-20 08:32:04 +00:00
import com.imaginarycode.minecraft.redisbungee.events.PlayerLeftNetworkEvent ;
2022-07-06 22:24:08 +00:00
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent ;
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 ;
2022-07-07 00:16:09 +00:00
import com.velocitypowered.api.plugin.Plugin ;
2022-07-06 22:24:08 +00:00
import com.velocitypowered.api.plugin.annotation.DataDirectory ;
import com.velocitypowered.api.proxy.Player ;
import com.velocitypowered.api.proxy.ProxyServer ;
2022-07-15 06:52:53 +00:00
import com.velocitypowered.api.proxy.messages.ChannelIdentifier ;
2022-07-14 23:05:53 +00:00
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier ;
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier ;
2022-07-06 22:24:08 +00:00
import com.velocitypowered.api.scheduler.ScheduledTask ;
2022-07-07 00:16:09 +00:00
import ninja.leaping.configurate.ConfigurationNode ;
import ninja.leaping.configurate.objectmapping.ObjectMappingException ;
import ninja.leaping.configurate.yaml.YAMLConfigurationLoader ;
2022-07-17 05:51:24 +00:00
import org.apache.commons.pool2.impl.GenericObjectPoolConfig ;
2022-07-06 22:24:08 +00:00
import org.slf4j.Logger ;
2022-07-17 05:51:24 +00:00
import redis.clients.jedis.* ;
2022-07-06 22:24:08 +00:00
import redis.clients.jedis.exceptions.JedisConnectionException ;
2022-07-17 05:51:24 +00:00
import redis.clients.jedis.exceptions.JedisException ;
2022-07-08 02:47:50 +00:00
2022-07-07 00:16:09 +00:00
import static com.google.common.base.Preconditions.checkArgument ;
2022-07-06 22:24:08 +00:00
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 ;
2022-07-21 12:37:32 +00:00
@Plugin ( id = " redisbungee " , name = " RedisBungee " , version = PomData . VERSION , url = " https://github.com/ProxioDev/RedisBungee " , authors = { " astei " , " ProxioDev " } )
2022-07-06 22:24:08 +00:00
public class RedisBungeeVelocityPlugin implements RedisBungeePlugin < Player > {
private final ProxyServer server ;
private final Logger logger ;
2022-07-15 06:52:53 +00:00
private final Path dataFolder ;
2022-07-08 02:47:50 +00:00
private final RedisBungeeAPI api ;
private final PubSubListener psl ;
2022-07-17 05:51:24 +00:00
private Summoner < ? > jedisSummoner ;
private RedisBungeeMode redisBungeeMode ;
2022-07-08 02:47:50 +00:00
private final UUIDTranslator uuidTranslator ;
2022-07-06 22:24:08 +00:00
private RedisBungeeConfiguration configuration ;
2022-07-08 02:47:50 +00:00
private final VelocityDataManager dataManager ;
private final OkHttpClient httpClient ;
2022-07-17 11:38:00 +00:00
private volatile List < String > proxiesIds ;
2022-07-06 22:24:08 +00:00
private final AtomicInteger nagAboutServers = new AtomicInteger ( ) ;
private final AtomicInteger globalPlayerCount = new AtomicInteger ( ) ;
private ScheduledTask integrityCheck ;
private ScheduledTask heartbeatTask ;
2022-07-17 05:51:24 +00:00
private LuaManager . Script getRedisClusterTimeScript ;
2022-07-06 22:24:08 +00:00
private static final Object SERVER_TO_PLAYERS_KEY = new Object ( ) ;
2022-07-15 06:52:53 +00:00
public static final List < ChannelIdentifier > IDENTIFIERS = List . of (
2022-07-17 05:51:24 +00:00
MinecraftChannelIdentifier . create ( " legacy " , " redisbungee " ) ,
2022-07-22 19:52:15 +00:00
new LegacyChannelIdentifier ( " RedisBungee " ) ,
// This is needed for clients before 1.13
new LegacyChannelIdentifier ( " legacy:redisbungee " )
2022-07-15 06:52:53 +00:00
) ;
2022-07-06 22:24:08 +00:00
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 ;
2022-07-15 06:52:53 +00:00
this . dataFolder = dataDirectory ;
2022-07-08 02:47:50 +00:00
try {
2022-07-25 12:49:57 +00:00
loadConfig ( this , dataDirectory ) ;
2022-07-08 02:47:50 +00:00
} 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 ) ;
2022-07-17 05:51:24 +00:00
LuaManager luaManager = new LuaManager ( this ) ;
new RedisTask < Void > ( this ) {
@Override
public Void jedisTask ( Jedis jedis ) {
// This is more portable than INFO <section>
String info = jedis . info ( ) ;
for ( String s : info . split ( " \ r \ n " ) ) {
if ( s . startsWith ( " redis_version: " ) ) {
String version = s . split ( " : " ) [ 1 ] ;
getLogger ( ) . info ( " {} <- redis version " , version ) ;
if ( ! RedisUtil . isRedisVersionRight ( version ) ) {
getLogger ( ) . error ( " Your version of Redis ({}) is not at least version 6.0 RedisBungee requires a newer version of Redis. " , version ) ;
throw new RuntimeException ( " Unsupported Redis version detected " ) ;
}
long uuidCacheSize = jedis . 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. " ) ;
}
break ;
}
}
return null ;
}
@Override
public Void clusterJedisTask ( JedisCluster jedisCluster ) {
// This is more portable than INFO <section>
try {
getRedisClusterTimeScript = luaManager . createScript ( IOUtil . readInputStreamAsString ( getResourceAsStream ( " lua/get_cluster_time.lua " ) ) ) ;
} catch ( JedisException e ) {
throw new RuntimeException ( " possible not supported redis version " , e ) ;
}
String info = ( String ) luaManager . createScript ( IOUtil . readInputStreamAsString ( getResourceAsStream ( " lua/get_cluster_info.lua " ) ) ) . eval ( Collections . singletonList ( " 0 " ) , Collections . emptyList ( ) ) ;
for ( String s : info . split ( " \ r \ n " ) ) {
if ( s . startsWith ( " redis_version: " ) ) {
String version = s . split ( " : " ) [ 1 ] ;
getLogger ( ) . info ( " {} <- redis version " , version ) ;
if ( ! RedisUtil . isRedisVersionRight ( version ) ) {
getLogger ( ) . error ( " Your version of Redis ({}) is not at least version 6.0 RedisBungee requires a newer version of Redis. " , version ) ;
throw new RuntimeException ( " Unsupported Redis version detected " ) ;
}
long uuidCacheSize = jedisCluster . 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. " ) ;
}
break ;
}
}
return null ;
}
} . execute ( ) ;
getLogger ( ) . info ( " lua manager was loaded " ) ;
// check if this proxy is recovering from a crash and start heart the beat.
new RedisTask < Void > ( api ) {
@Override
public Void jedisTask ( Jedis jedis ) {
Path crashFile = getDataFolder ( ) . resolve ( " restarted_from_crash.txt " ) ;
if ( Files . exists ( crashFile ) ) {
try {
Files . delete ( crashFile ) ;
} catch ( IOException e ) {
throw new RuntimeException ( e ) ;
}
getLogger ( ) . info ( " crash file was deleted " ) ;
2022-07-17 11:38:00 +00:00
} else if ( jedis . hexists ( " heartbeats " , configuration . getProxyId ( ) ) ) {
2022-07-17 05:51:24 +00:00
try {
2022-07-17 11:38:00 +00:00
long value = Long . parseLong ( jedis . hget ( " heartbeats " , configuration . getProxyId ( ) ) ) ;
2022-07-17 05:51:24 +00:00
long redisTime = getRedisTime ( jedis . time ( ) ) ;
if ( redisTime < value + 20 ) {
getLogger ( ) . error ( " You have launched a possible impostor Velocity / 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 ) {
}
}
return null ;
}
@Override
public Void clusterJedisTask ( JedisCluster jedisCluster ) {
Path crashFile = getDataFolder ( ) . resolve ( " restarted_from_crash.txt " ) ;
if ( Files . exists ( crashFile ) ) {
try {
Files . delete ( crashFile ) ;
} catch ( IOException e ) {
throw new RuntimeException ( e ) ;
}
getLogger ( ) . info ( " crash file was deleted " ) ;
2022-07-17 11:38:00 +00:00
} else if ( jedisCluster . hexists ( " heartbeats " , configuration . getProxyId ( ) ) ) {
2022-07-17 05:51:24 +00:00
try {
2022-07-17 11:38:00 +00:00
long value = Long . parseLong ( jedisCluster . hget ( " heartbeats " , configuration . getProxyId ( ) ) ) ;
2022-07-17 05:51:24 +00:00
long redisTime = getRedisClusterTime ( ) ;
if ( redisTime < value + 20 ) {
getLogger ( ) . error ( " You have launched a possible impostor Velocity / 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 ) {
}
}
return null ;
}
} . execute ( ) ;
2022-07-08 02:47:50 +00:00
uuidTranslator = new UUIDTranslator ( this ) ;
dataManager = new VelocityDataManager ( this ) ;
psl = new PubSubListener ( this ) ;
this . httpClient = new OkHttpClient ( ) ;
Dispatcher dispatcher = new Dispatcher ( Executors . newFixedThreadPool ( 6 ) ) ;
this . httpClient . setDispatcher ( dispatcher ) ;
NameFetcher . setHttpClient ( httpClient ) ;
UUIDFetcher . setHttpClient ( httpClient ) ;
// keeping this lol
new RedisBungee ( api ) ;
2022-07-17 05:51:24 +00:00
2022-07-07 23:23:44 +00:00
2022-07-06 22:24:08 +00:00
}
@Override
public RedisBungeeConfiguration getConfiguration ( ) {
return this . configuration ;
}
@Override
public int getCount ( ) {
return this . globalPlayerCount . get ( ) ;
}
@Override
public int getCurrentCount ( ) {
2022-07-17 05:51:24 +00:00
return new RedisTask < Long > ( api ) {
@Override
public Long jedisTask ( Jedis jedis ) {
long total = 0 ;
long redisTime = getRedisTime ( jedis . time ( ) ) ;
Map < String , String > heartBeats = jedis . hgetAll ( " heartbeats " ) ;
for ( Map . Entry < String , String > stringStringEntry : heartBeats . entrySet ( ) ) {
String k = stringStringEntry . getKey ( ) ;
String v = stringStringEntry . getValue ( ) ;
long heartbeatTime = Long . parseLong ( v ) ;
if ( heartbeatTime + 30 > = redisTime ) {
total = total + jedis . scard ( " proxy: " + k + " :usersOnline " ) ;
}
}
return total ;
}
@Override
public Long clusterJedisTask ( JedisCluster jedisCluster ) {
long total = 0 ;
long redisTime = getRedisClusterTime ( ) ;
Map < String , String > heartBeats = jedisCluster . hgetAll ( " heartbeats " ) ;
for ( Map . Entry < String , String > stringStringEntry : heartBeats . entrySet ( ) ) {
String k = stringStringEntry . getKey ( ) ;
String v = stringStringEntry . getValue ( ) ;
long heartbeatTime = Long . parseLong ( v ) ;
if ( heartbeatTime + 30 > = redisTime ) {
total = total + jedisCluster . scard ( " proxy: " + k + " :usersOnline " ) ;
}
}
return total ;
}
} . execute ( ) . intValue ( ) ;
2022-07-06 22:24:08 +00:00
}
@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
2022-07-07 22:39:05 +00:00
public AbstractDataManager < Player , ? , ? , ? > getDataManager ( ) {
2022-07-06 22:24:08 +00:00
return this . dataManager ;
}
@Override
public Set < UUID > getPlayers ( ) {
2022-07-17 05:51:24 +00:00
return new RedisTask < Set < UUID > > ( api ) {
@Override
public Set < UUID > jedisTask ( Jedis jedis ) {
ImmutableSet . Builder < UUID > setBuilder = ImmutableSet . builder ( ) ;
try {
List < String > keys = new ArrayList < > ( ) ;
2022-07-17 11:38:00 +00:00
for ( String i : getProxiesIds ( ) ) {
2022-07-17 05:51:24 +00:00
keys . add ( " proxy: " + i + " :usersOnline " ) ;
}
if ( ! keys . isEmpty ( ) ) {
Set < String > users = jedis . sunion ( keys . toArray ( new String [ 0 ] ) ) ;
if ( users ! = null & & ! users . isEmpty ( ) ) {
for ( String user : users ) {
try {
setBuilder = setBuilder . add ( UUID . fromString ( user ) ) ;
} catch ( IllegalArgumentException ignored ) {
}
2022-07-06 22:24:08 +00:00
}
}
}
2022-07-17 05:51:24 +00:00
} 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 ) ;
2022-07-06 22:24:08 +00:00
}
2022-07-17 05:51:24 +00:00
return setBuilder . build ( ) ;
2022-07-06 22:24:08 +00:00
}
2022-07-17 05:51:24 +00:00
@Override
public Set < UUID > clusterJedisTask ( JedisCluster jedisCluster ) {
ImmutableSet . Builder < UUID > setBuilder = ImmutableSet . builder ( ) ;
try {
List < String > keys = new ArrayList < > ( ) ;
2022-07-17 11:38:00 +00:00
for ( String i : getProxiesIds ( ) ) {
2022-07-17 05:51:24 +00:00
keys . add ( " proxy: " + i + " :usersOnline " ) ;
}
if ( ! keys . isEmpty ( ) ) {
Set < String > users = jedisCluster . sunion ( keys . toArray ( new String [ 0 ] ) ) ;
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 ( ) ;
}
} . execute ( ) ;
2022-07-06 22:24:08 +00:00
}
2022-07-15 22:58:48 +00:00
@Override
2022-07-17 05:51:24 +00:00
public Summoner < ? > getSummoner ( ) {
2022-07-15 22:58:48 +00:00
return this . jedisSummoner ;
}
2022-07-06 22:24:08 +00:00
@Override
public RedisBungeeAPI getApi ( ) {
return this . api ;
}
@Override
public UUIDTranslator getUuidTranslator ( ) {
return this . uuidTranslator ;
}
@Override
public Multimap < String , UUID > serversToPlayers ( ) {
try {
2022-07-17 05:51:24 +00:00
return serverToPlayersCache . get ( SERVER_TO_PLAYERS_KEY , new RedisTask < Multimap < String , UUID > > ( api ) {
@Override
public Multimap < String , UUID > jedisTask ( Jedis jedis ) {
ImmutableMultimap . Builder < String , UUID > builder = ImmutableMultimap . builder ( ) ;
2022-07-17 11:38:00 +00:00
for ( String serverId : getProxiesIds ( ) ) {
2022-07-17 05:51:24 +00:00
Set < String > players = jedis . smembers ( " proxy: " + serverId + " :usersOnline " ) ;
for ( String player : players ) {
2022-07-19 03:19:32 +00:00
String playerServer = jedis . hget ( " player: " + player , " server " ) ;
if ( playerServer = = null ) {
continue ;
}
builder . put ( playerServer , UUID . fromString ( player ) ) ;
2022-07-17 05:51:24 +00:00
}
2022-07-06 22:24:08 +00:00
}
2022-07-17 05:51:24 +00:00
return builder . build ( ) ;
2022-07-06 22:24:08 +00:00
}
2022-07-08 02:47:50 +00:00
2022-07-17 05:51:24 +00:00
@Override
public Multimap < String , UUID > clusterJedisTask ( JedisCluster jedisCluster ) {
ImmutableMultimap . Builder < String , UUID > builder = ImmutableMultimap . builder ( ) ;
2022-07-17 11:38:00 +00:00
for ( String serverId : getProxiesIds ( ) ) {
2022-07-17 05:51:24 +00:00
Set < String > players = jedisCluster . smembers ( " proxy: " + serverId + " :usersOnline " ) ;
for ( String player : players ) {
2022-07-19 03:19:32 +00:00
String playerServer = jedisCluster . hget ( " player: " + player , " server " ) ;
if ( playerServer = = null ) {
continue ;
}
builder . put ( playerServer , UUID . fromString ( player ) ) ;
2022-07-17 05:51:24 +00:00
}
}
return builder . build ( ) ;
}
2022-07-06 22:24:08 +00:00
} ) ;
} catch ( ExecutionException e ) {
throw new RuntimeException ( e ) ;
}
}
@Override
public Set < UUID > getPlayersOnProxy ( String proxyId ) {
2022-07-17 11:38:00 +00:00
checkArgument ( getProxiesIds ( ) . contains ( proxyId ) , proxyId + " is not a valid proxy ID " ) ;
2022-07-17 05:51:24 +00:00
return new RedisTask < Set < UUID > > ( api ) {
@Override
public Set < UUID > jedisTask ( Jedis jedis ) {
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 ( ) ;
2022-07-06 22:24:08 +00:00
}
2022-07-17 05:51:24 +00:00
@Override
public Set < UUID > clusterJedisTask ( JedisCluster jedisCluster ) {
Set < String > users = jedisCluster . smembers ( " proxy: " + proxyId + " :usersOnline " ) ;
ImmutableSet . Builder < UUID > builder = ImmutableSet . builder ( ) ;
for ( String user : users ) {
builder . add ( UUID . fromString ( user ) ) ;
}
return builder . build ( ) ;
}
} . execute ( ) ;
2022-07-06 22:24:08 +00:00
}
@Override
public void sendProxyCommand ( String serverId , String command ) {
2022-07-17 11:38:00 +00:00
checkArgument ( getProxiesIds ( ) . contains ( serverId ) | | serverId . equals ( " allservers " ) , " proxyId is invalid " ) ;
2022-07-06 22:24:08 +00:00
sendChannelMessage ( " redisbungee- " + serverId , command ) ;
}
@Override
2022-07-17 11:38:00 +00:00
public List < String > getProxiesIds ( ) {
return proxiesIds ;
2022-07-06 22:24:08 +00:00
}
2022-07-17 05:51:24 +00:00
2022-07-06 22:24:08 +00:00
@Override
2022-07-17 11:38:00 +00:00
public List < String > getCurrentProxiesIds ( boolean nag , boolean lagged ) {
2022-07-17 05:51:24 +00:00
return new RedisTask < List < String > > ( api ) {
@Override
public List < String > jedisTask ( Jedis jedis ) {
try {
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 ( " {} is {} seconds behind! (Time not synchronized or server down?) and was removed from heartbeat. " , entry . getKey ( ) , ( time - stamp ) ) ;
jedis . hdel ( " heartbeats " , entry . getKey ( ) ) ;
}
} catch ( NumberFormatException ignored ) {
}
}
return servers . build ( ) ;
} catch ( JedisConnectionException e ) {
getLogger ( ) . error ( " Unable to fetch server IDs " , e ) ;
2022-07-17 11:38:00 +00:00
return Collections . singletonList ( configuration . getProxyId ( ) ) ;
2022-07-06 22:24:08 +00:00
}
}
2022-07-17 05:51:24 +00:00
@Override
public List < String > clusterJedisTask ( JedisCluster jedisCluster ) {
2022-07-06 22:24:08 +00:00
try {
2022-07-17 05:51:24 +00:00
long time = getRedisClusterTime ( ) ;
int nagTime = 0 ;
if ( nag ) {
nagTime = nagAboutServers . decrementAndGet ( ) ;
if ( nagTime < = 0 ) {
nagAboutServers . set ( 10 ) ;
}
2022-07-06 22:24:08 +00:00
}
2022-07-17 05:51:24 +00:00
ImmutableList . Builder < String > servers = ImmutableList . builder ( ) ;
Map < String , String > heartbeats = jedisCluster . 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 ( " {} is {} seconds behind! (Time not synchronized or server down?) and was removed from heartbeat. " , entry . getKey ( ) , ( time - stamp ) ) ;
jedisCluster . hdel ( " heartbeats " , entry . getKey ( ) ) ;
}
} catch ( NumberFormatException ignored ) {
}
}
return servers . build ( ) ;
} catch ( JedisConnectionException e ) {
getLogger ( ) . error ( " Unable to fetch server IDs " , e ) ;
2022-07-17 11:38:00 +00:00
return Collections . singletonList ( configuration . getProxyId ( ) ) ;
2022-07-06 22:24:08 +00:00
}
}
2022-07-17 05:51:24 +00:00
} . execute ( ) ;
2022-07-06 22:24:08 +00:00
}
@Override
public PubSubListener getPubSubListener ( ) {
return this . psl ;
}
@Override
public void sendChannelMessage ( String channel , String message ) {
2022-07-17 05:51:24 +00:00
new RedisTask < Void > ( api ) {
@Override
public Void jedisTask ( Jedis jedis ) {
try {
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 ) ;
}
return null ;
}
@Override
public Void clusterJedisTask ( JedisCluster jedisCluster ) {
try {
jedisCluster . 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 ) ;
}
return null ;
}
} . execute ( ) ;
2022-07-06 22:24:08 +00:00
}
@Override
public void executeAsync ( Runnable runnable ) {
this . getProxy ( ) . getScheduler ( ) . buildTask ( this , runnable ) . schedule ( ) ;
}
@Override
public void executeAsyncAfter ( Runnable runnable , TimeUnit timeUnit , int time ) {
2022-07-15 06:52:53 +00:00
this . getProxy ( ) . getScheduler ( ) . buildTask ( this , runnable ) . delay ( time , timeUnit ) . schedule ( ) ;
2022-07-06 22:24:08 +00:00
}
@Override
public void callEvent ( Object event ) {
2022-07-15 06:52:53 +00:00
this . getProxy ( ) . getEventManager ( ) . fireAndForget ( event ) ;
2022-07-06 22:24:08 +00:00
}
@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 ) {
2022-07-17 11:38:00 +00:00
checkArgument ( getProxiesIds ( ) . contains ( this . configuration . getProxyId ( ) ) | | this . configuration . getProxyId ( ) . equals ( " allservers " ) , " proxyId is invalid " ) ;
sendChannelMessage ( " redisbungee- " + this . configuration . getProxyId ( ) , cmd ) ;
2022-07-06 22:24:08 +00:00
}
@Override
public long getRedisTime ( List < String > timeRes ) {
return Long . parseLong ( timeRes . get ( 0 ) ) ;
}
@Override
2022-07-07 23:23:44 +00:00
public void initialize ( ) {
2022-07-20 09:21:24 +00:00
updateProxyIds ( ) ;
2022-07-17 05:51:24 +00:00
// start heartbeat task
2022-07-20 09:21:24 +00:00
heartbeatTask = getProxy ( ) . getScheduler ( ) . buildTask ( this , new HeartbeatTask ( this , this . globalPlayerCount ) ) . repeat ( HeartbeatTask . INTERVAL , HeartbeatTask . REPEAT_INTERVAL_TIME_UNIT ) . schedule ( ) ;
2022-07-08 02:47:50 +00:00
2022-07-14 23:05:53 +00:00
getProxy ( ) . getEventManager ( ) . register ( this , new RedisBungeeVelocityListener ( this , configuration . getExemptAddresses ( ) ) ) ;
2022-07-08 02:47:50 +00:00
getProxy ( ) . getEventManager ( ) . register ( this , dataManager ) ;
getProxy ( ) . getScheduler ( ) . buildTask ( this , psl ) . schedule ( ) ;
2022-07-17 05:51:24 +00:00
RedisTask < Void > integrityCheckRedisTask = new RedisTask < Void > ( api ) {
@Override
public Void jedisTask ( Jedis jedis ) {
try {
Set < String > players = getLocalPlayersAsUuidStrings ( ) ;
2022-07-17 11:38:00 +00:00
Set < String > playersInRedis = jedis . smembers ( " proxy: " + configuration . getProxyId ( ) + " :usersOnline " ) ;
List < String > lagged = getCurrentProxiesIds ( false , true ) ;
2022-07-17 05:51:24 +00:00
// Clean up lagged players.
for ( String s : lagged ) {
Set < String > laggedPlayers = jedis . smembers ( " proxy: " + s + " :usersOnline " ) ;
jedis . del ( " proxy: " + s + " :usersOnline " ) ;
if ( ! laggedPlayers . isEmpty ( ) ) {
getLogger ( ) . info ( " Cleaning up lagged proxy {} ({} players)... " , s , laggedPlayers . size ( ) ) ;
for ( String laggedPlayer : laggedPlayers ) {
2022-07-21 05:36:10 +00:00
GenericPlayerUtils . cleanUpPlayer ( laggedPlayer , jedis , true ) ;
2022-07-17 05:51:24 +00:00
}
2022-07-06 22:24:08 +00:00
}
}
2022-07-17 05:51:24 +00:00
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 ;
2022-07-17 11:38:00 +00:00
for ( String proxyId : getProxiesIds ( ) ) {
if ( proxyId . equals ( configuration . getProxyId ( ) ) ) continue ;
2022-07-17 05:51:24 +00:00
if ( jedis . sismember ( " proxy: " + proxyId + " :usersOnline " , member ) ) {
// Just clean up the set.
found = true ;
break ;
}
}
if ( ! found ) {
2022-07-21 05:36:10 +00:00
GenericPlayerUtils . cleanUpPlayer ( member , jedis , false ) ;
2022-07-17 05:51:24 +00:00
getLogger ( ) . warn ( " Player found in set that was not found locally and globally: {} " , member ) ;
} else {
2022-07-17 11:38:00 +00:00
jedis . srem ( " proxy: " + configuration . getProxyId ( ) + " :usersOnline " , member ) ;
2022-07-17 05:51:24 +00:00
getLogger ( ) . warn ( " Player found in set that was not found locally, but is on another proxy: {} " , member ) ;
2022-07-08 02:47:50 +00:00
}
2022-07-06 22:24:08 +00:00
}
2022-07-17 05:51:24 +00:00
Pipeline pipeline = jedis . pipelined ( ) ;
for ( String player : absentInRedis ) {
// Player not online according to Redis but not BungeeCord.
getLogger ( ) . warn ( " Player {} is on the proxy but not in Redis. " , player ) ;
Player playerProxied = getProxy ( ) . getPlayer ( UUID . fromString ( player ) ) . orElse ( null ) ;
if ( playerProxied = = null )
continue ; // We'll deal with it later.
2022-07-23 06:31:12 +00:00
VelocityPlayerUtils . createPlayer ( playerProxied , pipeline , false ) ;
2022-07-06 22:24:08 +00:00
}
2022-07-17 05:51:24 +00:00
pipeline . sync ( ) ;
} catch ( Throwable e ) {
getLogger ( ) . error ( " Unable to fix up stored player data " , e ) ;
2022-07-06 22:24:08 +00:00
}
2022-07-17 05:51:24 +00:00
return null ;
}
@Override
public Void clusterJedisTask ( JedisCluster jedisCluster ) {
try {
Set < String > players = getLocalPlayersAsUuidStrings ( ) ;
2022-07-17 11:38:00 +00:00
Set < String > playersInRedis = jedisCluster . smembers ( " proxy: " + configuration . getProxyId ( ) + " :usersOnline " ) ;
List < String > lagged = getCurrentProxiesIds ( false , true ) ;
2022-07-17 05:51:24 +00:00
// Clean up lagged players.
for ( String s : lagged ) {
Set < String > laggedPlayers = jedisCluster . smembers ( " proxy: " + s + " :usersOnline " ) ;
jedisCluster . del ( " proxy: " + s + " :usersOnline " ) ;
if ( ! laggedPlayers . isEmpty ( ) ) {
getLogger ( ) . info ( " Cleaning up lagged proxy {} ({} players)... " , s , laggedPlayers . size ( ) ) ;
for ( String laggedPlayer : laggedPlayers ) {
2022-07-21 05:36:10 +00:00
GenericPlayerUtils . cleanUpPlayer ( laggedPlayer , jedisCluster , true ) ;
2022-07-17 05:51:24 +00:00
}
}
}
2022-07-06 22:24:08 +00:00
2022-07-17 05:51:24 +00:00
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 ;
2022-07-17 11:38:00 +00:00
for ( String proxyId : getProxiesIds ( ) ) {
if ( proxyId . equals ( configuration . getProxyId ( ) ) ) continue ;
2022-07-17 05:51:24 +00:00
if ( jedisCluster . sismember ( " proxy: " + proxyId + " :usersOnline " , member ) ) {
// Just clean up the set.
found = true ;
break ;
}
}
if ( ! found ) {
2022-07-21 05:36:10 +00:00
GenericPlayerUtils . cleanUpPlayer ( member , jedisCluster , false ) ;
2022-07-17 05:51:24 +00:00
getLogger ( ) . warn ( " Player found in set that was not found locally and globally: {} " , member ) ;
} else {
2022-07-17 11:38:00 +00:00
jedisCluster . srem ( " proxy: " + configuration . getProxyId ( ) + " :usersOnline " , member ) ;
2022-07-17 05:51:24 +00:00
getLogger ( ) . warn ( " Player found in set that was not found locally, but is on another proxy: {} " , member ) ;
}
}
2022-07-06 22:24:08 +00:00
2022-07-17 05:51:24 +00:00
for ( String player : absentInRedis ) {
// Player not online according to Redis but not BungeeCord.
getLogger ( ) . warn ( " Player {} is on the proxy but not in Redis. " , player ) ;
2022-07-06 22:24:08 +00:00
2022-07-17 05:51:24 +00:00
Player playerProxied = getProxy ( ) . getPlayer ( UUID . fromString ( player ) ) . orElse ( null ) ;
if ( playerProxied = = null )
continue ; // We'll deal with it later.
2022-07-06 22:24:08 +00:00
2022-07-23 06:31:12 +00:00
VelocityPlayerUtils . createPlayer ( playerProxied , jedisCluster , false ) ;
2022-07-17 05:51:24 +00:00
}
} catch ( Throwable e ) {
getLogger ( ) . error ( " Unable to fix up stored player data " , e ) ;
2022-07-08 02:47:50 +00:00
}
2022-07-17 05:51:24 +00:00
return null ;
2022-07-08 02:47:50 +00:00
}
2022-07-17 05:51:24 +00:00
} ;
2022-07-19 03:21:17 +00:00
integrityCheck = getProxy ( ) . getScheduler ( ) . buildTask ( this , integrityCheckRedisTask : : execute ) . repeat ( 30 , TimeUnit . SECONDS ) . schedule ( ) ;
2022-07-06 22:24:08 +00:00
2022-07-15 06:52:53 +00:00
// register plugin messages
IDENTIFIERS . forEach ( getProxy ( ) . getChannelRegistrar ( ) : : register ) ;
2022-07-06 22:24:08 +00:00
2022-07-22 12:10:06 +00:00
// register legacy commands
if ( configuration . doRegisterLegacyCommands ( ) ) {
// Override Velocity commands
if ( configuration . doOverrideBungeeCommands ( ) ) {
getProxy ( ) . getCommandManager ( ) . register ( " glist " , new RedisBungeeCommands . GlistCommand ( this ) , " redisbungee " , " rglist " ) ;
}
getProxy ( ) . getCommandManager ( ) . register ( " sendtoall " , new RedisBungeeCommands . SendToAll ( this ) , " rsendtoall " ) ;
getProxy ( ) . getCommandManager ( ) . register ( " serverid " , new RedisBungeeCommands . ServerId ( this ) , " rserverid " ) ;
getProxy ( ) . getCommandManager ( ) . register ( " serverids " , new RedisBungeeCommands . ServerIds ( this ) ) ;
getProxy ( ) . getCommandManager ( ) . register ( " pproxy " , new RedisBungeeCommands . PlayerProxyCommand ( this ) ) ;
getProxy ( ) . getCommandManager ( ) . register ( " plist " , new RedisBungeeCommands . PlistCommand ( this ) , " rplist " ) ;
getProxy ( ) . getCommandManager ( ) . register ( " lastseen " , new RedisBungeeCommands . LastSeenCommand ( this ) , " rlastseen " ) ;
getProxy ( ) . getCommandManager ( ) . register ( " ip " , new RedisBungeeCommands . IpCommand ( this ) , " playerip " , " rip " , " rplayerip " ) ;
getProxy ( ) . getCommandManager ( ) . register ( " find " , new RedisBungeeCommands . FindCommand ( this ) , " rfind " ) ;
2022-07-15 06:52:53 +00:00
}
2022-07-06 22:24:08 +00:00
}
@Override
public void stop ( ) {
2022-07-17 05:51:24 +00:00
// Poison the PubSub listener
if ( psl ! = null ) {
2022-07-06 22:24:08 +00:00
psl . poison ( ) ;
2022-07-17 05:51:24 +00:00
}
if ( integrityCheck ! = null ) {
2022-07-06 22:24:08 +00:00
integrityCheck . cancel ( ) ;
2022-07-17 05:51:24 +00:00
}
if ( heartbeatTask ! = null ) {
2022-07-06 22:24:08 +00:00
heartbeatTask . cancel ( ) ;
2022-07-17 05:51:24 +00:00
}
new RedisTask < Void > ( api ) {
@Override
public Void jedisTask ( Jedis jedis ) {
2022-07-17 11:38:00 +00:00
jedis . hdel ( " heartbeats " , configuration . getProxyId ( ) ) ;
if ( jedis . scard ( " proxy: " + configuration . getProxyId ( ) + " :usersOnline " ) > 0 ) {
Set < String > players = jedis . smembers ( " proxy: " + configuration . getProxyId ( ) + " :usersOnline " ) ;
2022-07-06 22:24:08 +00:00
for ( String member : players )
2022-07-21 05:36:10 +00:00
GenericPlayerUtils . cleanUpPlayer ( member , jedis , true ) ;
2022-07-06 22:24:08 +00:00
}
2022-07-17 05:51:24 +00:00
return null ;
2022-07-06 22:24:08 +00:00
}
2022-07-17 05:51:24 +00:00
@Override
public Void clusterJedisTask ( JedisCluster jedisCluster ) {
2022-07-17 11:38:00 +00:00
jedisCluster . hdel ( " heartbeats " , configuration . getProxyId ( ) ) ;
if ( jedisCluster . scard ( " proxy: " + configuration . getProxyId ( ) + " :usersOnline " ) > 0 ) {
Set < String > players = jedisCluster . smembers ( " proxy: " + configuration . getProxyId ( ) + " :usersOnline " ) ;
2022-07-17 05:51:24 +00:00
for ( String member : players )
2022-07-21 05:36:10 +00:00
GenericPlayerUtils . cleanUpPlayer ( member , jedisCluster , true ) ;
2022-07-17 05:51:24 +00:00
}
return null ;
2022-07-06 22:24:08 +00:00
}
2022-07-17 05:51:24 +00:00
} . execute ( ) ;
try {
this . jedisSummoner . close ( ) ;
} catch ( IOException e ) {
throw new RuntimeException ( e ) ;
2022-07-06 22:24:08 +00:00
}
2022-07-17 05:51:24 +00:00
2022-07-06 22:24:08 +00:00
this . httpClient . getDispatcher ( ) . getExecutorService ( ) . shutdown ( ) ;
try {
this . httpClient . getDispatcher ( ) . getExecutorService ( ) . awaitTermination ( 20 , TimeUnit . SECONDS ) ;
} catch ( InterruptedException e ) {
throw new RuntimeException ( e ) ;
}
}
@Override
2022-07-25 12:49:57 +00:00
public void onConfigLoad ( RedisBungeeConfiguration configuration , Summoner < ? > summoner , RedisBungeeMode mode ) {
this . jedisSummoner = summoner ;
this . configuration = configuration ;
this . redisBungeeMode = mode ;
2022-07-06 22:24:08 +00:00
}
2022-07-19 11:30:45 +00:00
@Override
public void kickPlayer ( UUID playerUniqueId , String message ) {
// first handle on origin proxy if player not found publish the payload
if ( ! dataManager . handleKick ( playerUniqueId , message ) ) {
new RedisTask < Void > ( api ) {
@Override
public Void jedisTask ( Jedis jedis ) {
2022-07-20 08:32:04 +00:00
PayloadUtils . kickPlayerPayload ( playerUniqueId , message , jedis ) ;
2022-07-19 11:30:45 +00:00
return null ;
}
@Override
public Void clusterJedisTask ( JedisCluster jedisCluster ) {
2022-07-20 08:32:04 +00:00
PayloadUtils . kickPlayerPayload ( playerUniqueId , message , jedisCluster ) ;
2022-07-19 11:30:45 +00:00
return null ;
}
} . execute ( ) ;
}
}
@Override
public void kickPlayer ( String playerName , String message ) {
// fetch the uuid
2022-07-22 11:12:32 +00:00
UUID playerUUID = this . uuidTranslator . getTranslatedUuid ( playerName , true ) ;
2022-07-19 11:30:45 +00:00
kickPlayer ( playerUUID , message ) ;
}
2022-07-17 05:51:24 +00:00
@Override
public RedisBungeeMode getRedisBungeeMode ( ) {
return this . redisBungeeMode ;
}
@Override
public Long getRedisClusterTime ( ) {
return getRedisTime ( ( List < String > ) this . getRedisClusterTimeScript . eval ( Collections . singletonList ( " 0 " ) , Collections . emptyList ( ) ) ) ;
}
2022-07-20 09:21:24 +00:00
@Override
public void updateProxyIds ( ) {
this . proxiesIds = this . getCurrentProxiesIds ( true , false ) ;
}
2022-07-06 22:24:08 +00:00
@Subscribe
public void proxyInit ( ProxyInitializeEvent event ) {
2022-07-07 23:23:44 +00:00
initialize ( ) ;
2022-07-06 22:24:08 +00:00
}
@Subscribe
public void proxyShutdownEvent ( ProxyShutdownEvent event ) {
stop ( ) ;
}
@Override
2022-07-20 08:32:04 +00:00
public Object createPlayerChangedNetworkEvent ( UUID uuid , String previousServer , String server ) {
return new PlayerChangedServerNetworkEvent ( uuid , previousServer , server ) ;
2022-07-06 22:24:08 +00:00
}
@Override
2022-07-20 08:32:04 +00:00
public Object createPlayerJoinedNetworkEvent ( UUID uuid ) {
return new PlayerJoinedNetworkEvent ( uuid ) ;
2022-07-06 22:24:08 +00:00
}
@Override
2022-07-20 08:32:04 +00:00
public Object createPlayerLeftNetworkEvent ( UUID uuid ) {
return new PlayerLeftNetworkEvent ( uuid ) ;
2022-07-06 22:24:08 +00:00
}
@Override
2022-07-20 08:32:04 +00:00
public Object createPubSubEvent ( String channel , String message ) {
return new PubSubMessageEvent ( channel , message ) ;
2022-07-06 22:24:08 +00:00
}
public ProxyServer getProxy ( ) {
return server ;
}
public Logger getLogger ( ) {
return logger ;
}
2022-07-15 06:52:53 +00:00
public Path getDataFolder ( ) {
2022-07-06 22:24:08 +00:00
return this . dataFolder ;
}
2022-07-22 11:12:32 +00:00
public InputStream getResourceAsStream ( String name ) {
return this . getClass ( ) . getClassLoader ( ) . getResourceAsStream ( name ) ;
2022-07-06 22:24:08 +00:00
}
}