2
0
mirror of https://github.com/proxiodev/RedisBungee.git synced 2026-04-25 16:00:27 +00:00
## NOTES
data system shouldn't effect anybody, unless you do any direct query to
Redis query data, you should adapt the changes, by viewing classes
`ProxyDataManager` and `PlayerDataManager`

# Changes 
* RedisBungee is compiled with `java 17` now, Due java 11 support is
ending at end of September
* config version is now `2` which will reset your config if older
version
* Adventure API is included inside RedisBungee API
* new Language infrastructure for RedisBungee built-in messages #85
*commands not included yet*
* New data system which replaces Redis PubSub with Redis Streams *see
below*
* Ability to connect player to last server they where on using an config
option #84
* new environment variable `REDISBUNGEE_PROXY_ID` which can be set
before launch
* new environment variable `REDISBUNGEE_NETWORK_ID` which can be set
before launch
* RedisBungee requires redis version 6.2 or above  #88 
* Better command system
https://github.com/ProxioDev/RedisBungee/issues/93

## New data system
Due limitation of Redis PubSub in Cluster environment, Internals of
RedisBungee were changed to support Redis Streams
- Network Ids
  - networks ids used to group network proxies
    - example having 'test' network and 'main' network
- Networks in the same redis server / cluster share the same UUID cache
- Heartbeat system:
- RedisBungee old heartbeat system used hastset on redisbungee to store
the current unix time of the proxy to check what every proxy died or
not, now instead we publish the heartbeat using unix time, and online
count to proxy which proxy store it in their memory, which allow the
`get number of online players` to be faster than pooling whole list in
old data system.

- PubSub
- since redisbungee was initially designed with pubsub in mind,
registration no longer required now for event to fire, see the api
changes below.

## Commands System
* rewritten using [acf lib](https://github.com/aikar/commands) to be
platform independent
* new command `/rb` or `/redisbungee` with sub commands `help`, `info`,
'clean', 'show'.

* 'rb'
  * '/rb' and '/rb info'

![image](https://github.com/ProxioDev/RedisBungee/assets/34905970/70796ab0-b5fd-4578-8c93-c976e517df95)

  * '/rb show'
  

![image](https://github.com/ProxioDev/RedisBungee/assets/34905970/56332c37-701f-43e0-946b-6894b845fab3)

* configuration to disable or override each command from legacy to new
introduced one `/rb`
```yaml

# For redis bungee legacy commands
# either can be run using '/rbl glist' for example
# or if 'install' is set to true '/glist' can be used.
# 'install' also overrides the proxy installed commands
#
# In legacy commands each command got it own permissions since they had it own permission pre new command system,
# so it's also applied to subcommands in '/rbl'.
commands:
  # Permission redisbungee.legacy.use
  redisbungee-legacy:
    enabled: false
    subcommands:
        # Permission redisbungee.command.glist
        glist:
          enabled: false
          install: false
        # Permission redisbungee.command.find
        find:
          enabled: false
          install: false
        # Permission redisbungee.command.lastseen
        lastseen:
          enabled: false
          install: false
        # Permission redisbungee.command.ip
        ip:
          enabled: false
          install: false
        # Permission redisbungee.command.pproxy
        pproxy:
          enabled: false
          install: false
        # Permission redisbungee.command.sendtoall
        sendtoall:
          enabled: false
          install: false
        # Permission redisbungee.command.serverid
        serverid:
          enabled: false
          install: false
        # Permission redisbungee.command.serverids
        serverids:
          enabled: false
          install: false
       # Permission redisbungee.command.plist
        plist:
          enabled: false
          install: false
  # Permission redisbungee.command.use
  redisbungee:
    enabled: true
```

## API changes
- Kick api Deprecated: 
  - `kickPlayer(String playerName, String message) `
  - `kickPlayer(UUID playerUUID, String message) `

- newer where added using adventure api:
  - `kickPlayer(String playerName, Component message) `
  - `kickPlayer(UUID playerUUID, Component message) `

-  PubSub registration api Deprecated:
```java
/**
     * Register (a) PubSub channel(s), so that you may handle PubSubMessageEvent for it.
     *
     * @param channels the channels to register
     * @since 0.3
     * @deprecated No longer required
     */
    @Deprecated
    public final void registerPubSubChannels(String... channels) {
    }

    /**
     * Unregister (a) PubSub channel(s).
     *
     * @param channels the channels to unregister
     * @since 0.3
     * @deprecated No longer required
     */
    @Deprecated
    public final void unregisterPubSubChannels(String... channels) {
    }

```
# Contributors

* `summoncraft.us` for running this branch in production
* @SrBedrock for providing [Brazilian
Portuguese](https://en.wikipedia.org/wiki/Brazilian_Portuguese)
translation #87
 
# issues
closes #84 
closes #88 
closes #92 
closes #81
closes #93

---------

Signed-off-by: mohammed jasem alaajel <xrambad@gmail.com>
Co-authored-by: ThiagoROX <51332006+SrBedrock@users.noreply.github.com>
This commit is contained in:
2024-04-28 15:29:53 +04:00
committed by GitHub
parent 5c4de82714
commit 1593c2d628
88 changed files with 3715 additions and 3181 deletions

View File

@@ -6,10 +6,6 @@ plugins {
}
repositories {
mavenCentral()
maven { url = uri("https://repo.papermc.io/repository/maven-public/") }
}
dependencies {
api(project(":RedisBungee-API")) {
@@ -17,9 +13,18 @@ dependencies {
exclude("com.google.guava", "guava")
exclude("com.google.code.gson", "gson")
exclude("org.spongepowered", "configurate-yaml")
// exclude also adventure api
exclude("net.kyori", "adventure-api")
exclude("net.kyori", "adventure-text-serializer-gson")
exclude("net.kyori", "adventure-text-serializer-legacy")
exclude("net.kyori", "adventure-text-serializer-plain")
exclude("net.kyori", "adventure-text-minimessage")
}
compileOnly("com.velocitypowered:velocity-api:3.3.0-SNAPSHOT")
annotationProcessor("com.velocitypowered:velocity-api:3.3.0-SNAPSHOT")
compileOnly(libs.platform.velocity)
annotationProcessor(libs.platform.velocity)
implementation(project(":RedisBungee-Commands"))
implementation(libs.acf.velocity)
}
description = "RedisBungee Velocity implementation"
@@ -39,10 +44,12 @@ tasks {
"https://jd.papermc.io/velocity/3.0.0/", // velocity api
)
val apiDocs = File(rootProject.projectDir, "RedisBungee-API/build/docs/javadoc")
options.linksOffline("https://ci.limework.net/RedisBungee/RedisBungee-API/build/docs/javadoc", apiDocs.path)
options.linksOffline("https://ci.limework.net/RedisBungee/RedisBungee-API/build/docs/javadoc", apiDocs.path)
}
runVelocity {
velocityVersion("3.3.0-SNAPSHOT")
environment["REDISBUNGEE_PROXY_ID"] = "velocity-1"
environment["REDISBUNGEE_NETWORK_ID"] = "dev"
}
compileJava {
options.encoding = Charsets.UTF_8.name()
@@ -61,6 +68,9 @@ tasks {
relocate("com.squareup.okhttp", "com.imaginarycode.minecraft.redisbungee.internal.okhttp")
relocate("okio", "com.imaginarycode.minecraft.redisbungee.internal.okio")
relocate("org.json", "com.imaginarycode.minecraft.redisbungee.internal.json")
relocate("com.github.benmanes.caffeine", "com.imaginarycode.minecraft.redisbungee.internal.caffeine")
// acf shade
relocate("co.aikar.commands", "com.imaginarycode.minecraft.redisbungee.internal.acf.commands")
}
}

View File

@@ -23,7 +23,7 @@ import java.util.UUID;
* or somehow you got the Plugin instance by you can call the api using {@link RedisBungeePlugin#getAbstractRedisBungeeApi()}.
*
* @author tuxed
* @since 0.2.3 | updated 0.8.0
* @since 0.2.3
*/
public class RedisBungeeAPI extends AbstractRedisBungeeAPI {

View File

@@ -0,0 +1,173 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
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.api.RedisBungeePlugin;
import com.velocitypowered.api.event.PostOrder;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.event.player.PlayerChooseInitialServerEvent;
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.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerPing;
import net.kyori.adventure.text.Component;
import java.util.*;
import java.util.stream.Collectors;
import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.MultiMapSerialization.serializeMultimap;
import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.MultiMapSerialization.serializeMultiset;
public class RedisBungeeListener {
private final RedisBungeePlugin<Player> plugin;
public RedisBungeeListener(RedisBungeePlugin<Player> plugin) {
this.plugin = plugin;
}
@Subscribe(order = PostOrder.LAST) // some plugins changes it online players so we need to be executed as last
public void onPing(ProxyPingEvent event) {
if (!plugin.configuration().handleMotd()) return;
if (plugin.configuration().getExemptAddresses().contains(event.getConnection().getRemoteAddress().getAddress())) return;
ServerPing.Builder ping = event.getPing().asBuilder();
ping.onlinePlayers(plugin.proxyDataManager().totalNetworkPlayers());
event.setPing(ping.build());
}
@Subscribe
public void onPluginMessage(PluginMessageEvent event) {
if (!(event.getSource() instanceof ServerConnection) || !RedisBungeeVelocityPlugin.IDENTIFIERS.contains(event.getIdentifier())) {
return;
}
event.setResult(PluginMessageEvent.ForwardResult.handled());
plugin.executeAsync(() -> {
ByteArrayDataInput in = event.dataAsDataStream();
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.proxyDataManager().networkPlayers();
} else {
out.writeUTF(type);
try {
original = plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type);
} catch (IllegalArgumentException ignored) {
}
}
Set<String> players = original.stream()
.map(uuid -> plugin.getUuidTranslator().getNameFromUuid(uuid, false))
.collect(Collectors.toSet());
out.writeUTF(Joiner.on(',').join(players));
}
case "PlayerCount" -> {
out.writeUTF("PlayerCount");
type = in.readUTF();
if (type.equals("ALL")) {
out.writeUTF("ALL");
out.writeInt(plugin.proxyDataManager().totalNetworkPlayers());
} else {
out.writeUTF(type);
try {
out.writeInt(plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type).size());
} catch (IllegalArgumentException e) {
out.writeInt(0);
}
}
}
case "LastOnline" -> {
String user = in.readUTF();
out.writeUTF("LastOnline");
out.writeUTF(user);
out.writeLong(plugin.getAbstractRedisBungeeApi().getLastOnline(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(user, true))));
}
case "ServerPlayers" -> {
String type1 = in.readUTF();
out.writeUTF("ServerPlayers");
Multimap<String, UUID> multimap = plugin.getAbstractRedisBungeeApi().getServerToPlayers();
boolean includesUsers;
switch (type1) {
case "COUNT" -> includesUsers = false;
case "PLAYERS" -> includesUsers = true;
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);
}
}
case "Proxy" -> {
out.writeUTF("Proxy");
out.writeUTF(plugin.configuration().getProxyId());
}
case "PlayerProxy" -> {
String username = in.readUTF();
out.writeUTF("PlayerProxy");
out.writeUTF(username);
out.writeUTF(plugin.getAbstractRedisBungeeApi().getProxy(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(username, true))));
}
default -> {
return;
}
}
((ServerConnection) event.getSource()).sendPluginMessage(event.getIdentifier(), out.toByteArray());
});
}
@Subscribe
public void onPlayerChooseInitialServerEvent(PlayerChooseInitialServerEvent event) {
if (plugin.configuration().handleReconnectToLastServer()) {
Player player = event.getPlayer();
String lastServer = plugin.playerDataManager().getLastServerFor(player.getUniqueId());
if (lastServer == null) return;
player.sendMessage(plugin.langConfiguration().messages().serverConnecting(player.getPlayerSettings().getLocale(), lastServer));
Optional<RegisteredServer> optionalRegisteredServer = ((RedisBungeeVelocityPlugin) plugin).getProxy().getServer(lastServer);
if (optionalRegisteredServer.isEmpty()) {
player.sendMessage(plugin.langConfiguration().messages().serverNotFound(player.getPlayerSettings().getLocale(), lastServer));
return;
}
RegisteredServer server = optionalRegisteredServer.get();
event.setInitialServer(server);
}
}
}

View File

@@ -1,263 +0,0 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
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.api.AbstractRedisBungeeListener;
import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration;
import com.imaginarycode.minecraft.redisbungee.api.util.player.PlayerUtils;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask;
import com.imaginarycode.minecraft.redisbungee.api.util.payload.PayloadUtils;
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
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.connection.PluginMessageEvent.ForwardResult;
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 net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import redis.clients.jedis.UnifiedJedis;
import java.net.InetAddress;
import java.util.*;
import java.util.stream.Collectors;
import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.Serializations.serializeMultimap;
import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.Serializations.serializeMultiset;
public class RedisBungeeVelocityListener extends AbstractRedisBungeeListener<LoginEvent, PostLoginEvent, DisconnectEvent, ServerConnectedEvent, ProxyPingEvent, PluginMessageEvent, PubSubMessageEvent> {
// Some messages are using legacy characters
private final LegacyComponentSerializer serializer = LegacyComponentSerializer.legacySection();
public RedisBungeeVelocityListener(RedisBungeePlugin<?> plugin, List<InetAddress> exemptAddresses) {
super(plugin, exemptAddresses);
}
@Subscribe(order = PostOrder.LAST)
public void onLogin(LoginEvent event, Continuation continuation) {
plugin.executeAsync(new RedisTask<Void>(plugin) {
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
try {
if (!event.getResult().isAllowed()) {
return null;
}
if (plugin.getConfiguration().restoreOldKickBehavior()) {
for (String s : plugin.getProxiesIds()) {
if (unifiedJedis.sismember("proxy:" + s + ":usersOnline", event.getPlayer().getUniqueId().toString())) {
event.setResult(ResultedEvent.ComponentResult.denied(serializer.deserialize(plugin.getConfiguration().getMessages().get(RedisBungeeConfiguration.MessageType.ALREADY_LOGGED_IN))));
return null;
}
}
} else if (api.isPlayerOnline(event.getPlayer().getUniqueId())) {
PlayerUtils.setKickedOtherLocation(event.getPlayer().getUniqueId().toString(), unifiedJedis);
api.kickPlayer(event.getPlayer().getUniqueId(), plugin.getConfiguration().getMessages().get(RedisBungeeConfiguration.MessageType.LOGGED_IN_OTHER_LOCATION));
}
return null;
} finally {
continuation.resume();
}
}
});
}
@Override
@Subscribe
public void onPostLogin(PostLoginEvent event) {
plugin.executeAsync(new RedisTask<Void>(plugin) {
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
plugin.getUuidTranslator().persistInfo(event.getPlayer().getUsername(), event.getPlayer().getUniqueId(), unifiedJedis);
VelocityPlayerUtils.createVelocityPlayer(event.getPlayer(), unifiedJedis, true);
return null;
}
});
}
@Override
@Subscribe
public void onPlayerDisconnect(DisconnectEvent event) {
plugin.executeAsync(new RedisTask<Void>(plugin) {
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
PlayerUtils.cleanUpPlayer(event.getPlayer().getUniqueId().toString(), unifiedJedis, true);
return null;
}
});
}
@Override
@Subscribe
public void onServerChange(ServerConnectedEvent event) {
final String currentServer = event.getServer().getServerInfo().getName();
final String oldServer = event.getPreviousServer().map(serverConnection -> serverConnection.getServerInfo().getName()).orElse(null);
plugin.executeAsync(new RedisTask<Void>(plugin) {
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
unifiedJedis.hset("player:" + event.getPlayer().getUniqueId().toString(), "server", currentServer);
PayloadUtils.playerServerChangePayload(event.getPlayer().getUniqueId(), unifiedJedis, currentServer, oldServer);
return null;
}
});
}
@Override
@Subscribe(order = PostOrder.LAST) // some plugins changes it online players so we need to be executed as last
public void onPing(ProxyPingEvent event) {
if (exemptAddresses.contains(event.getConnection().getRemoteAddress().getAddress())) {
return;
}
ServerPing.Builder ping = event.getPing().asBuilder();
ping.onlinePlayers(plugin.getCount());
event.setPing(ping.build());
}
@Override
@Subscribe
public void onPluginMessage(PluginMessageEvent event) {
if (!(event.getSource() instanceof ServerConnection) || !RedisBungeeVelocityPlugin.IDENTIFIERS.contains(event.getIdentifier())) {
return;
}
event.setResult(ForwardResult.handled());
plugin.executeAsync(() -> {
ByteArrayDataInput in = event.dataAsDataStream();
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 {
out.writeUTF(type);
try {
original = plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type);
} catch (IllegalArgumentException ignored) {
}
}
Set<String> players = original.stream()
.map(uuid -> plugin.getUuidTranslator().getNameFromUuid(uuid, false))
.collect(Collectors.toSet());
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.getAbstractRedisBungeeApi().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.getAbstractRedisBungeeApi().getLastOnline(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(user, true))));
break;
case "ServerPlayers":
String type1 = in.readUTF();
out.writeUTF("ServerPlayers");
Multimap<String, UUID> multimap = plugin.getAbstractRedisBungeeApi().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().getProxyId());
break;
case "PlayerProxy":
String username = in.readUTF();
out.writeUTF("PlayerProxy");
out.writeUTF(username);
out.writeUTF(plugin.getAbstractRedisBungeeApi().getProxy(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(username, true))));
break;
default:
return;
}
((ServerConnection) event.getSource()).sendPluginMessage(event.getIdentifier(), out.toByteArray());
});
}
@Override
@Subscribe
public void onPubSubMessage(PubSubMessageEvent event) {
if (event.getChannel().equals("redisbungee-allservers") || event.getChannel().equals("redisbungee-" + plugin.getAbstractRedisBungeeApi().getProxyId())) {
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);
}
}
}

View File

@@ -10,24 +10,27 @@
package com.imaginarycode.minecraft.redisbungee;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import co.aikar.commands.VelocityCommandManager;
import com.google.inject.Inject;
import com.imaginarycode.minecraft.redisbungee.api.*;
import com.imaginarycode.minecraft.redisbungee.api.config.ConfigLoader;
import com.imaginarycode.minecraft.redisbungee.api.PlayerDataManager;
import com.imaginarycode.minecraft.redisbungee.api.ProxyDataManager;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.commands.CommandLoader;
import com.imaginarycode.minecraft.redisbungee.commands.utils.CommandPlatformHelper;
import com.imaginarycode.minecraft.redisbungee.api.config.LangConfiguration;
import com.imaginarycode.minecraft.redisbungee.api.config.loaders.ConfigLoader;
import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration;
import com.imaginarycode.minecraft.redisbungee.api.config.loaders.LangConfigLoader;
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerChangedServerNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerJoinedNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerLeftNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.api.events.IPubSubMessageEvent;
import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner;
import com.imaginarycode.minecraft.redisbungee.api.tasks.*;
import com.imaginarycode.minecraft.redisbungee.api.util.InitialUtils;
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;
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.PlayerLeftNetworkEvent;
@@ -46,55 +49,62 @@ import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
import com.velocitypowered.api.scheduler.ScheduledTask;
import net.kyori.adventure.text.Component;
import org.slf4j.Logger;
import redis.clients.jedis.*;
import redis.clients.jedis.exceptions.JedisConnectionException;
import java.io.*;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.sql.Date;
import java.time.Duration;
import java.time.Instant;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@Plugin(id = "redisbungee", name = "RedisBungee", version = Constants.VERSION, url = "https://github.com/ProxioDev/RedisBungee", authors = {"astei", "ProxioDev"})
public class RedisBungeeVelocityPlugin implements RedisBungeePlugin<Player>, ConfigLoader {
public class RedisBungeeVelocityPlugin implements RedisBungeePlugin<Player>, ConfigLoader, LangConfigLoader {
private final ProxyServer server;
private final Logger logger;
private final Path dataFolder;
private final AbstractRedisBungeeAPI api;
private final PubSubListener psl;
private Summoner<?> jedisSummoner;
private RedisBungeeMode redisBungeeMode;
private final UUIDTranslator uuidTranslator;
private RedisBungeeConfiguration configuration;
private final VelocityDataManager dataManager;
private LangConfiguration langConfiguration;
private final OkHttpClient httpClient;
private volatile List<String> proxiesIds;
private final AtomicInteger globalPlayerCount = new AtomicInteger();
private ScheduledTask integrityCheck;
private final ProxyDataManager proxyDataManager;
private final VelocityPlayerDataManager playerDataManager;
private ScheduledTask cleanUpTask;
private ScheduledTask heartbeatTask;
private static final Object SERVER_TO_PLAYERS_KEY = new Object();
public static final List<ChannelIdentifier> IDENTIFIERS = List.of(
MinecraftChannelIdentifier.create("legacy", "redisbungee"),
new LegacyChannelIdentifier("RedisBungee"),
// This is needed for clients before 1.13
new LegacyChannelIdentifier("legacy:redisbungee")
);
private final Cache<Object, Multimap<String, UUID>> serverToPlayersCache = CacheBuilder.newBuilder()
.expireAfterWrite(5, TimeUnit.SECONDS)
.build();
private VelocityCommandManager commandManager;
@Inject
public RedisBungeeVelocityPlugin(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory) {
this.server = server;
this.logger = logger;
this.dataFolder = dataDirectory;
logInfo("Version: {}", Constants.VERSION);
try {
loadConfig(this, dataDirectory);
loadLangConfig(this, dataDirectory);
} catch (IOException e) {
throw new RuntimeException("Unable to load/save config", e);
} catch (JedisConnectionException e) {
@@ -102,11 +112,21 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin<Player>, Con
}
this.api = new RedisBungeeAPI(this);
InitialUtils.checkRedisVersion(this);
// check if this proxy is recovering from a crash and start heart the beat.
InitialUtils.checkIfRecovering(this, getDataFolder());
this.proxyDataManager = new ProxyDataManager(this) {
@Override
public Set<UUID> getLocalOnlineUUIDs() {
HashSet<UUID> players = new HashSet<>();
server.getAllPlayers().forEach(player -> players.add(player.getUniqueId()));
return players;
}
@Override
protected void handlePlatformCommandExecution(String command) {
server.getCommandManager().executeAsync(RedisBungeeCommandSource.getSingleton(), command);
}
};
this.playerDataManager = new VelocityPlayerDataManager(this);
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);
@@ -115,31 +135,6 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin<Player>, Con
}
@Override
public RedisBungeeConfiguration getConfiguration() {
return this.configuration;
}
@Override
public int getCount() {
return this.globalPlayerCount.get();
}
@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 AbstractDataManager<Player, ?, ?, ?> getDataManager() {
return this.dataManager;
}
@Override
public Summoner<?> getSummoner() {
return this.jedisSummoner;
@@ -150,29 +145,21 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin<Player>, Con
return this.api;
}
@Override
public ProxyDataManager proxyDataManager() {
return this.proxyDataManager;
}
@Override
public PlayerDataManager<Player, ?, ?, ?, ?, ?, ?> playerDataManager() {
return this.playerDataManager;
}
@Override
public UUIDTranslator getUuidTranslator() {
return this.uuidTranslator;
}
@Override
public Multimap<String, UUID> serverToPlayersCache() {
try {
return this.serverToPlayersCache.get(SERVER_TO_PLAYERS_KEY, this::serversToPlayers);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
@Override
public List<String> getProxiesIds() {
return proxiesIds;
}
@Override
public PubSubListener getPubSubListener() {
return this.psl;
}
@Override
public void executeAsync(Runnable runnable) {
@@ -199,16 +186,41 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin<Player>, Con
this.getLogger().info(msg);
}
@Override
public void logInfo(String format, Object... object) {
logger.info(format, object);
}
@Override
public void logWarn(String msg) {
this.getLogger().warn(msg);
}
@Override
public void logWarn(String format, Object... object) {
logger.warn(format, object);
}
@Override
public void logFatal(String msg) {
this.getLogger().error(msg);
}
@Override
public void logFatal(String format, Throwable throwable) {
logger.error(format, throwable);
}
@Override
public RedisBungeeConfiguration configuration() {
return this.configuration;
}
@Override
public LangConfiguration langConfiguration() {
return this.langConfiguration;
}
@Override
public Player getPlayer(UUID uuid) {
return this.getProxy().getPlayer(uuid).orElse(null);
@@ -229,6 +241,15 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin<Player>, Con
return this.getProxy().getPlayer(player).map(Player::getUsername).orElse(null);
}
@Override
public boolean handlePlatformKick(UUID uuid, Component message) {
Player player = getPlayer(uuid);
if (player == null) return false;
player.disconnect(message);
return true;
}
@Override
public String getPlayerServerName(Player player) {
return player.getCurrentServer().map(serverConnection -> serverConnection.getServerInfo().getName()).orElse(null);
@@ -247,44 +268,25 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin<Player>, Con
@Override
public void initialize() {
logInfo("Initializing RedisBungee.....");
updateProxiesIds();
// start heartbeat task
heartbeatTask = getProxy().getScheduler().buildTask(this, new HeartbeatTask(this, this.globalPlayerCount)).repeat(HeartbeatTask.INTERVAL, HeartbeatTask.REPEAT_INTERVAL_TIME_UNIT).schedule();
// heartbeat and clean up
this.heartbeatTask = server.getScheduler().buildTask(this, this.proxyDataManager::publishHeartbeat).repeat(Duration.ofSeconds(1)).schedule();
this.cleanUpTask = server.getScheduler().buildTask(this, this.proxyDataManager::correctionTask).repeat(Duration.ofSeconds(60)).schedule();
getProxy().getEventManager().register(this, new RedisBungeeVelocityListener(this, configuration.getExemptAddresses()));
getProxy().getEventManager().register(this, dataManager);
getProxy().getScheduler().buildTask(this, psl).schedule();
IntegrityCheckTask integrityCheckTask = new IntegrityCheckTask(this) {
@Override
public void handlePlatformPlayer(String player, UnifiedJedis unifiedJedis) {
Player playerProxied = getProxy().getPlayer(UUID.fromString(player)).orElse(null);
if (playerProxied == null)
return; // We'll deal with it later.
VelocityPlayerUtils.createVelocityPlayer(playerProxied, unifiedJedis, false);
}
};
integrityCheck = getProxy().getScheduler().buildTask(this, integrityCheckTask::execute).repeat(30, TimeUnit.SECONDS).schedule();
server.getEventManager().register(this, this.playerDataManager);
server.getEventManager().register(this, new RedisBungeeListener(this));
// subscribe
server.getScheduler().buildTask(this, this.proxyDataManager).schedule();
// register plugin messages
IDENTIFIERS.forEach(getProxy().getChannelRegistrar()::register);
// 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");
}
// load commands
CommandPlatformHelper.init(new VelocityCommandPlatformHelper());
this.commandManager = new VelocityCommandManager(this.getProxy(), this);
CommandLoader.initCommands(this.commandManager, this);
logInfo("RedisBungee initialized successfully ");
}
@@ -292,19 +294,18 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin<Player>, Con
public void stop() {
logInfo("Turning off redis connections.....");
// Poison the PubSub listener
if (psl != null) {
psl.poison();
}
if (integrityCheck != null) {
integrityCheck.cancel();
if (cleanUpTask != null) {
cleanUpTask.cancel();
}
if (heartbeatTask != null) {
heartbeatTask.cancel();
}
ShutdownUtils.shutdownCleanup(this);
try {
this.proxyDataManager.close();
this.jedisSummoner.close();
} catch (IOException e) {
} catch (Exception e) {
throw new RuntimeException(e);
}
@@ -315,6 +316,7 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin<Player>, Con
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (commandManager != null) commandManager.unregisterCommands();
logInfo("RedisBungee shutdown complete");
}
@@ -325,16 +327,16 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin<Player>, Con
this.redisBungeeMode = mode;
}
@Override
public void onLangConfigLoad(LangConfiguration langConfiguration) {
this.langConfiguration = langConfiguration;
}
@Override
public RedisBungeeMode getRedisBungeeMode() {
return this.redisBungeeMode;
}
@Override
public void updateProxiesIds() {
this.proxiesIds = this.getCurrentProxiesIds(false);
}
@Subscribe(order = PostOrder.FIRST)
public void onProxyInitializeEvent(ProxyInitializeEvent event) {

View File

@@ -0,0 +1,26 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee;
import co.aikar.commands.CommandIssuer;
import co.aikar.commands.VelocityCommandIssuer;
import com.imaginarycode.minecraft.redisbungee.commands.utils.CommandPlatformHelper;
import net.kyori.adventure.text.Component;
public class VelocityCommandPlatformHelper extends CommandPlatformHelper {
@Override
public void sendMessage(CommandIssuer issuer, Component component) {
VelocityCommandIssuer vIssuer = (VelocityCommandIssuer) issuer;
vIssuer.getIssuer().sendMessage(component);
}
}

View File

@@ -1,61 +0,0 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee;
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
import com.imaginarycode.minecraft.redisbungee.api.AbstractDataManager;
import com.imaginarycode.minecraft.redisbungee.api.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;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import java.util.UUID;
public class VelocityDataManager extends AbstractDataManager<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());
}
private final LegacyComponentSerializer serializer = LegacyComponentSerializer.legacySection();
@Override
public boolean handleKick(UUID target, String message) {
Player player = plugin.getPlayer(target);
if (player == null) {
return false;
}
player.disconnect(serializer.deserialize(message));
return true;
}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee;
import com.imaginarycode.minecraft.redisbungee.api.PlayerDataManager;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration;
import com.imaginarycode.minecraft.redisbungee.events.PlayerChangedServerNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.events.PlayerLeftNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
import com.velocitypowered.api.event.Continuation;
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.PostLoginEvent;
import com.velocitypowered.api.event.player.ServerConnectedEvent;
import com.velocitypowered.api.proxy.Player;
import net.kyori.adventure.text.Component;
import java.util.concurrent.TimeUnit;
public class VelocityPlayerDataManager extends PlayerDataManager<Player, PostLoginEvent, DisconnectEvent, PubSubMessageEvent, PlayerChangedServerNetworkEvent, PlayerLeftNetworkEvent, ServerConnectedEvent> {
public VelocityPlayerDataManager(RedisBungeePlugin<Player> plugin) {
super(plugin);
}
@Override
@Subscribe
public void onPlayerChangedServerNetworkEvent(PlayerChangedServerNetworkEvent event) {
handleNetworkPlayerServerChange(event);
}
@Override
@Subscribe
public void onNetworkPlayerQuit(PlayerLeftNetworkEvent event) {
handleNetworkPlayerQuit(event);
}
@Override
@Subscribe
public void onPubSubMessageEvent(PubSubMessageEvent event) {
handlePubSubMessageEvent(event);
}
@Override
@Subscribe
public void onServerConnectedEvent(ServerConnectedEvent event) {
final String currentServer = event.getServer().getServerInfo().getName();
final String oldServer;
if (event.getPreviousServer().isPresent()) {
oldServer = event.getPreviousServer().get().getServerInfo().getName();
} else {
oldServer = null;
}
super.playerChangedServer(event.getPlayer().getUniqueId(), oldServer, currentServer);
}
@Subscribe
public void onLoginEvent(LoginEvent event, Continuation continuation) {
// check if online
if (getLastOnline(event.getPlayer().getUniqueId()) == 0) {
if (plugin.configuration().kickWhenOnline()) {
kickPlayer(event.getPlayer().getUniqueId(), plugin.langConfiguration().messages().loggedInFromOtherLocation());
// wait 3 seconds before releasing the event
plugin.executeAsyncAfter(continuation::resume, TimeUnit.SECONDS, 3);
} else {
event.setResult(ResultedEvent.ComponentResult.denied(plugin.langConfiguration().messages().alreadyLoggedIn()));
continuation.resume();
}
} else {
continuation.resume();
}
}
@Override
@Subscribe
public void onLoginEvent(PostLoginEvent event) {
addPlayer(event.getPlayer().getUniqueId(), event.getPlayer().getRemoteAddress().getAddress());
}
@Override
@Subscribe
public void onDisconnectEvent(DisconnectEvent event) {
if (event.getLoginStatus() == DisconnectEvent.LoginStatus.SUCCESSFUL_LOGIN || event.getLoginStatus() == DisconnectEvent.LoginStatus.PRE_SERVER_JOIN) {
removePlayer(event.getPlayer().getUniqueId());
}
}
}

View File

@@ -1,31 +0,0 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee;
import com.imaginarycode.minecraft.redisbungee.api.util.player.PlayerUtils;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection;
import redis.clients.jedis.UnifiedJedis;
import java.util.Optional;
public class VelocityPlayerUtils {
protected static void createVelocityPlayer(Player player, UnifiedJedis unifiedJedis, boolean fireEvent) {
Optional<ServerConnection> optionalServerConnection = player.getCurrentServer();
String serverName = null;
if (optionalServerConnection.isPresent()) {
serverName = optionalServerConnection.get().getServerInfo().getName();
}
PlayerUtils.createPlayer(player.getUniqueId(), unifiedJedis, serverName, player.getRemoteAddress().getAddress(), fireEvent);
}
}

View File

@@ -1,362 +0,0 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.commands;
import java.net.InetAddress;
import java.text.SimpleDateFormat;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
import com.google.common.base.Joiner;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI;
import com.imaginarycode.minecraft.redisbungee.RedisBungeeVelocityPlugin;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.command.SimpleCommand;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor;
/**
* This class contains subclasses that are used for the commands RedisBungee overrides or includes: /glist, /find and /lastseen.
* <p>
* All classes use the {@link AbstractRedisBungeeAPI}.
*
* @author tuxed
* @since 0.2.3
*/
public class RedisBungeeCommands {
private static final Component NO_PLAYER_SPECIFIED =
Component.text("You must specify a player name.", NamedTextColor.RED);
private static final Component PLAYER_NOT_FOUND =
Component.text("No such player found.", NamedTextColor.RED);
private static final Component NO_COMMAND_SPECIFIED =
Component.text("You must specify a command to be run.", NamedTextColor.RED);
private static String playerPlural(int num) {
return num == 1 ? num + " player is" : num + " players are";
}
public static class GlistCommand implements SimpleCommand {
private final RedisBungeeVelocityPlugin plugin;
public GlistCommand(RedisBungeeVelocityPlugin plugin) {
this.plugin = plugin;
}
@Override
public void execute(final Invocation invocation) {
plugin.getProxy().getScheduler().buildTask(plugin, () -> {
int count = plugin.getAbstractRedisBungeeApi().getPlayerCount();
Component playersOnline = Component.text(playerPlural(count) + " currently online.", NamedTextColor.YELLOW);
CommandSource sender = invocation.source();
if (invocation.arguments().length > 0 && invocation.arguments()[0].equals("showall")) {
Multimap<String, UUID> serverToPlayers = plugin.getAbstractRedisBungeeApi().getServerToPlayers();
Multimap<String, String> human = HashMultimap.create();
serverToPlayers.forEach((key, value) -> {
// if for any reason UUID translation fails just return the uuid as name, to make command finish executing.
String playerName = plugin.getUuidTranslator().getNameFromUuid(value, false);
human.put(key, playerName != null ? playerName : value.toString());
});
for (String server : new TreeSet<>(serverToPlayers.keySet())) {
Component serverName = Component.text("[" + server + "] ", NamedTextColor.GREEN);
Component serverCount = Component.text("(" + serverToPlayers.get(server).size() + "): ", NamedTextColor.YELLOW);
Component serverPlayers = Component.text(Joiner.on(", ").join(human.get(server)), NamedTextColor.WHITE);
sender.sendMessage(Component.textOfChildren(serverName, serverCount, serverPlayers));
}
sender.sendMessage(playersOnline);
} else {
sender.sendMessage(playersOnline);
sender.sendMessage(Component.text("To see all players online, use /glist showall.", NamedTextColor.YELLOW));
}
}).schedule();
}
@Override
public boolean hasPermission(Invocation invocation) {
return invocation.source().hasPermission("velocity.command.server");
}
}
public static class FindCommand implements SimpleCommand {
private final RedisBungeeVelocityPlugin plugin;
public FindCommand(RedisBungeeVelocityPlugin plugin) {
this.plugin = plugin;
}
@Override
public void execute(final Invocation invocation) {
plugin.getProxy().getScheduler().buildTask(plugin, () -> {
String[] args = invocation.arguments();
CommandSource sender = invocation.source();
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().getServer(plugin.getAbstractRedisBungeeApi().getServerNameFor(uuid)).map(RegisteredServer::getServerInfo).orElse(null);
if (si != null) {
Component message = Component.text(args[0] + " is on " + si.getName() + ".", NamedTextColor.BLUE);
sender.sendMessage(message);
} else {
sender.sendMessage(PLAYER_NOT_FOUND);
}
} else {
sender.sendMessage(NO_PLAYER_SPECIFIED);
}
}).schedule();
}
@Override
public boolean hasPermission(Invocation invocation) {
return invocation.source().hasPermission("redisbungee.command.find");
}
}
public static class LastSeenCommand implements SimpleCommand {
private final RedisBungeeVelocityPlugin plugin;
public LastSeenCommand(RedisBungeeVelocityPlugin plugin) {
this.plugin = plugin;
}
@Override
public void execute(final Invocation invocation) {
plugin.getProxy().getScheduler().buildTask(plugin, () -> {
String[] args = invocation.arguments();
CommandSource sender = invocation.source();
if (args.length > 0) {
UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
if (uuid == null) {
sender.sendMessage(PLAYER_NOT_FOUND);
return;
}
long secs = plugin.getAbstractRedisBungeeApi().getLastOnline(uuid);
TextComponent.Builder message = Component.text();
if (secs == 0) {
message.color(NamedTextColor.GREEN);
message.content(args[0] + " is currently online.");
} else if (secs != -1) {
message.color(NamedTextColor.BLUE);
message.content(args[0] + " was last online on " + new SimpleDateFormat().format(secs) + ".");
} else {
message.color(NamedTextColor.RED);
message.content(args[0] + " has never been online.");
}
sender.sendMessage(message.build());
} else {
sender.sendMessage(NO_PLAYER_SPECIFIED);
}
}).schedule();
}
@Override
public boolean hasPermission(Invocation invocation) {
return invocation.source().hasPermission("redisbungee.command.lastseen");
}
}
public static class IpCommand implements SimpleCommand {
private final RedisBungeeVelocityPlugin plugin;
public IpCommand(RedisBungeeVelocityPlugin plugin) {
this.plugin = plugin;
}
@Override
public void execute(final Invocation invocation) {
CommandSource sender = invocation.source();
String[] args = invocation.arguments();
plugin.getProxy().getScheduler().buildTask(plugin, () -> {
if (args.length > 0) {
UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
if (uuid == null) {
sender.sendMessage(PLAYER_NOT_FOUND);
return;
}
InetAddress ia = plugin.getAbstractRedisBungeeApi().getPlayerIp(uuid);
if (ia != null) {
TextComponent message = Component.text(args[0] + " is connected from " + ia.toString() + ".", NamedTextColor.GREEN);
sender.sendMessage(message);
} else {
sender.sendMessage(PLAYER_NOT_FOUND);
}
} else {
sender.sendMessage(NO_PLAYER_SPECIFIED);
}
}).schedule();
}
@Override
public boolean hasPermission(Invocation invocation) {
return invocation.source().hasPermission("redisbungee.command.ip");
}
}
public static class PlayerProxyCommand implements SimpleCommand {
private final RedisBungeeVelocityPlugin plugin;
public PlayerProxyCommand(RedisBungeeVelocityPlugin plugin) {
this.plugin = plugin;
}
@Override
public void execute(final Invocation invocation) {
CommandSource sender = invocation.source();
String[] args = invocation.arguments();
plugin.getProxy().getScheduler().buildTask(plugin, () -> {
if (args.length > 0) {
UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
if (uuid == null) {
sender.sendMessage(PLAYER_NOT_FOUND);
return;
}
String proxy = plugin.getAbstractRedisBungeeApi().getProxy(uuid);
if (proxy != null) {
TextComponent message = Component.text(args[0] + " is connected to " + proxy + ".", NamedTextColor.GREEN);
sender.sendMessage(message);
} else {
sender.sendMessage(PLAYER_NOT_FOUND);
}
} else {
sender.sendMessage(NO_PLAYER_SPECIFIED);
}
}).schedule();
}
@Override
public boolean hasPermission(Invocation invocation) {
return invocation.source().hasPermission("redisbungee.command.pproxy");
}
}
public static class SendToAll implements SimpleCommand {
private final RedisBungeeVelocityPlugin plugin;
public SendToAll(RedisBungeeVelocityPlugin plugin) {
//super("sendtoall", "redisbungee.command.sendtoall", "rsendtoall");
this.plugin = plugin;
}
@Override
public void execute(final Invocation invocation) {
String[] args = invocation.arguments();
CommandSource sender = invocation.source();
if (args.length > 0) {
String command = Joiner.on(" ").skipNulls().join(args);
plugin.getAbstractRedisBungeeApi().sendProxyCommand(command);
TextComponent message = Component.text("Sent the command /" + command + " to all proxies.", NamedTextColor.GREEN);
sender.sendMessage(message);
} else {
sender.sendMessage(NO_COMMAND_SPECIFIED);
}
}
@Override
public boolean hasPermission(Invocation invocation) {
return invocation.source().hasPermission("redisbungee.command.sendtoall");
}
}
public static class ServerId implements SimpleCommand {
private final RedisBungeeVelocityPlugin plugin;
public ServerId(RedisBungeeVelocityPlugin plugin) {
this.plugin = plugin;
}
@Override
public void execute(Invocation invocation) {
invocation.source().sendMessage(Component.text("You are on " + plugin.getAbstractRedisBungeeApi().getProxyId() + ".", NamedTextColor.YELLOW));
}
@Override
public boolean hasPermission(Invocation invocation) {
return invocation.source().hasPermission("redisbungee.command.serverid");
}
}
public static class ServerIds implements SimpleCommand {
private final RedisBungeeVelocityPlugin plugin;
public ServerIds(RedisBungeeVelocityPlugin plugin) {
this.plugin = plugin;
}
@Override
public void execute(Invocation invocation) {
invocation.source().sendMessage(
Component.text("All server IDs: " + Joiner.on(", ").join(plugin.getAbstractRedisBungeeApi().getAllProxies()), NamedTextColor.YELLOW));
}
@Override
public boolean hasPermission(Invocation invocation) {
return invocation.source().hasPermission("redisbungee.command.serverids");
}
}
public static class PlistCommand implements SimpleCommand {
private final RedisBungeeVelocityPlugin plugin;
public PlistCommand(RedisBungeeVelocityPlugin plugin) {
this.plugin = plugin;
}
@Override
public void execute(Invocation invocation) {
CommandSource sender = invocation.source();
String[] args = invocation.arguments();
plugin.getProxy().getScheduler().buildTask(plugin, () -> {
String proxy = args.length >= 1 ? args[0] : plugin.getConfiguration().getProxyId();
if (!plugin.getProxiesIds().contains(proxy)) {
sender.sendMessage(Component.text(proxy + " is not a valid proxy. See /serverids for valid proxies.", NamedTextColor.RED));
return;
}
Set<UUID> players = plugin.getAbstractRedisBungeeApi().getPlayersOnProxy(proxy);
Component playersOnline = Component.text(playerPlural(players.size()) + " currently on proxy " + proxy + ".", NamedTextColor.YELLOW);
if (args.length >= 2 && args[1].equals("showall")) {
Multimap<String, UUID> serverToPlayers = plugin.getAbstractRedisBungeeApi().getServerToPlayers();
Multimap<String, String> human = HashMultimap.create();
serverToPlayers.forEach((key, value) -> {
if (players.contains(value)) {
human.put(key, plugin.getUuidTranslator().getNameFromUuid(value, false));
}
});
for (String server : new TreeSet<>(human.keySet())) {
TextComponent serverName = Component.text("[" + server + "] ", NamedTextColor.RED);
TextComponent serverCount = Component.text("(" + human.get(server).size() + "): ", NamedTextColor.YELLOW);
TextComponent serverPlayers = Component.text(Joiner.on(", ").join(human.get(server)), NamedTextColor.WHITE);
sender.sendMessage(Component.textOfChildren(serverName, serverCount, serverPlayers));
}
sender.sendMessage(playersOnline);
} else {
sender.sendMessage(playersOnline);
sender.sendMessage(Component.text("To see all players online, use /plist " + proxy + " showall.", NamedTextColor.YELLOW));
}
}).schedule();
}
@Override
public boolean hasPermission(Invocation invocation) {
return invocation.source().hasPermission("redisbungee.command.plist");
}
}
}