mirror of
https://github.com/BGHDDevelopment/PlayerBalancer.git
synced 2024-10-05 10:57:32 +02:00
feat: initial commit for velocity support
This commit is contained in:
parent
0847b0febd
commit
16293403f0
107
balancer-velocity/pom.xml
Normal file
107
balancer-velocity/pom.xml
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>com.jaimemartz</groupId>
|
||||||
|
<version>2.3.3</version>
|
||||||
|
<artifactId>playerbalancer-parent</artifactId>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>playerbalancer-velocity</artifactId>
|
||||||
|
|
||||||
|
<name>PlayerBalancer Velocity</name>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<finalName>PlayerBalancer-Velocity-${project.version}</finalName>
|
||||||
|
<relocations>
|
||||||
|
<relocation>
|
||||||
|
<pattern>org.bstats</pattern>
|
||||||
|
<shadedPattern>com.jaimemartz.playerbalancer.metrics</shadedPattern>
|
||||||
|
</relocation>
|
||||||
|
<relocation>
|
||||||
|
<pattern>ninja.leaping.configurate</pattern>
|
||||||
|
<shadedPattern>com.jaimemartz.playerbalancer.libs.ninja.leaping.configurate</shadedPattern>
|
||||||
|
</relocation>
|
||||||
|
<relocation>
|
||||||
|
<pattern>com.typesafe.config</pattern>
|
||||||
|
<shadedPattern>com.jaimemartz.playerbalancer.libs.com.typesafe.config</shadedPattern>
|
||||||
|
</relocation>
|
||||||
|
</relocations>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>shade</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>papermc</id>
|
||||||
|
<url>https://repo.papermc.io/repository/maven-public/</url>
|
||||||
|
</repository>
|
||||||
|
<repository>
|
||||||
|
<id>CodeMC</id>
|
||||||
|
<url>https://repo.codemc.org/repository/maven-public</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.velocitypowered</groupId>
|
||||||
|
<artifactId>velocity-api</artifactId>
|
||||||
|
<version>3.2.0-SNAPSHOT</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.guava</groupId>
|
||||||
|
<artifactId>guava</artifactId>
|
||||||
|
<version>25.1-jre</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.inject</groupId>
|
||||||
|
<artifactId>guice</artifactId>
|
||||||
|
<version>6.0.0</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.imaginarycode.minecraft</groupId>
|
||||||
|
<artifactId>RedisBungee</artifactId>
|
||||||
|
<version>0.6.5-SNAPSHOT</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.spongepowered</groupId>
|
||||||
|
<artifactId>configurate-hocon</artifactId>
|
||||||
|
<version>3.7.3</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<!-- Already shaded in velocity -->
|
||||||
|
<groupId>com.google.guava</groupId>
|
||||||
|
<artifactId>guava</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bstats</groupId>
|
||||||
|
<artifactId>bstats-velocity</artifactId>
|
||||||
|
<version>3.0.2</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
@ -0,0 +1,355 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity;
|
||||||
|
|
||||||
|
import com.google.common.reflect.TypeToken;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.commands.FallbackCommand;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.commands.MainCommand;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.commands.ManageCommand;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.connection.ServerAssignRegistry;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.helper.NetworkManager;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.helper.PasteHelper;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.helper.PlayerLocker;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.listeners.PlayerDisconnectListener;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.listeners.PluginMessageListener;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.listeners.ProxyReloadListener;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.listeners.ServerConnectListener;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.listeners.ServerKickListener;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.ping.StatusManager;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.section.SectionManager;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.settings.SettingsHolder;
|
||||||
|
import com.velocitypowered.api.command.CommandManager;
|
||||||
|
import com.velocitypowered.api.command.CommandMeta;
|
||||||
|
import com.velocitypowered.api.command.SimpleCommand;
|
||||||
|
import com.velocitypowered.api.event.Subscribe;
|
||||||
|
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
|
||||||
|
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
|
||||||
|
import com.velocitypowered.api.plugin.Dependency;
|
||||||
|
import com.velocitypowered.api.plugin.Plugin;
|
||||||
|
import com.velocitypowered.api.plugin.PluginContainer;
|
||||||
|
import com.velocitypowered.api.plugin.annotation.DataDirectory;
|
||||||
|
import com.velocitypowered.api.proxy.ProxyServer;
|
||||||
|
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
|
||||||
|
import lombok.Getter;
|
||||||
|
import ninja.leaping.configurate.commented.CommentedConfigurationNode;
|
||||||
|
import ninja.leaping.configurate.hocon.HoconConfigurationLoader;
|
||||||
|
import ninja.leaping.configurate.loader.ConfigurationLoader;
|
||||||
|
import org.bstats.charts.SingleLineChart;
|
||||||
|
import org.bstats.velocity.Metrics;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Plugin(
|
||||||
|
id = "playerbalancer",
|
||||||
|
name = "PlayerBalancer Velocity",
|
||||||
|
version = "2.3.3",
|
||||||
|
description = "PlayerBalancer is a plugin for setting up a network with multiple lobbies of different types.",
|
||||||
|
authors = {"jaime29010", "BGHDDevelopment", "HappyAreaBean"},
|
||||||
|
dependencies = {
|
||||||
|
@Dependency(id = "redisbungee", optional = true)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@Getter
|
||||||
|
public class PlayerBalancer {
|
||||||
|
private boolean failed = false;
|
||||||
|
private StatusManager statusManager;
|
||||||
|
private SettingsHolder settings;
|
||||||
|
private SectionManager sectionManager;
|
||||||
|
private NetworkManager networkManager;
|
||||||
|
private ConfigurationLoader<CommentedConfigurationNode> loader;
|
||||||
|
|
||||||
|
private FallbackCommand fallbackCommand;
|
||||||
|
private SimpleCommand mainCommand, manageCommand;
|
||||||
|
private CommandMeta mainCommandMeta, manageCommandMeta, fallbackCommandMeta;
|
||||||
|
private Object connectListener, kickListener, reloadListener, pluginMessageListener;
|
||||||
|
|
||||||
|
public static final LegacyChannelIdentifier PB_CHANNEL = new LegacyChannelIdentifier("playerbalancer:main");
|
||||||
|
|
||||||
|
private final ProxyServer proxyServer;
|
||||||
|
private final Logger logger;
|
||||||
|
private final Metrics.Factory metricsFactory;
|
||||||
|
private final PluginContainer container;
|
||||||
|
private final Path dataDirectory;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public PlayerBalancer(ProxyServer proxyServer, Logger logger, Metrics.Factory metricsFactory, PluginContainer container, @DataDirectory Path dataDirectory) {
|
||||||
|
this.proxyServer = proxyServer;
|
||||||
|
this.logger = logger;
|
||||||
|
this.metricsFactory = metricsFactory;
|
||||||
|
this.container = container;
|
||||||
|
this.dataDirectory = dataDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onProxyInitialization(ProxyInitializeEvent event) {
|
||||||
|
Metrics metrics = metricsFactory.make(this, 1636);
|
||||||
|
metrics.addCustomChart(new SingleLineChart("configured_sections", () -> {
|
||||||
|
if (sectionManager != null) {
|
||||||
|
return sectionManager.getSections().size();
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
updateCheck();
|
||||||
|
|
||||||
|
this.execStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateCheck() {
|
||||||
|
try {
|
||||||
|
Optional<String> pluginVersion = container.getDescription().getVersion();
|
||||||
|
if (!pluginVersion.isPresent()) return;
|
||||||
|
String urlString = "https://updatecheck.bghddevelopment.com";
|
||||||
|
URL url = new URL(urlString);
|
||||||
|
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||||
|
connection.setRequestMethod("GET");
|
||||||
|
connection.setRequestProperty("User-Agent", "Mozilla/5.0");
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
||||||
|
String input;
|
||||||
|
StringBuilder response = new StringBuilder();
|
||||||
|
while ((input = reader.readLine()) != null) {
|
||||||
|
response.append(input);
|
||||||
|
}
|
||||||
|
reader.close();
|
||||||
|
JsonObject object = new JsonParser().parse(response.toString()).getAsJsonObject();
|
||||||
|
|
||||||
|
if (object.has("plugins")) {
|
||||||
|
JsonObject plugins = object.get("plugins").getAsJsonObject();
|
||||||
|
JsonObject info = plugins.get("PlayerBalancer").getAsJsonObject();
|
||||||
|
String version = info.get("version").getAsString();
|
||||||
|
if (version.equals(pluginVersion.get())) {
|
||||||
|
getLogger().info(("PlayerBalancer is on the latest version."));
|
||||||
|
} else {
|
||||||
|
getLogger().warn("");
|
||||||
|
getLogger().warn("");
|
||||||
|
getLogger().warn("Your PlayerBalancer version is out of date!");
|
||||||
|
getLogger().warn("We recommend updating ASAP!");
|
||||||
|
getLogger().warn("");
|
||||||
|
getLogger().warn("Your Version: " + pluginVersion.get());
|
||||||
|
getLogger().warn("Newest Version: " + version);
|
||||||
|
getLogger().warn("");
|
||||||
|
getLogger().warn("");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.error("Wrong response from update API, contact plugin developer!");
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
logger.error("Failed to get updater check. (" + ex.getMessage() + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onProxyShutdown(ProxyShutdownEvent event) {
|
||||||
|
this.execStop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void execStart() {
|
||||||
|
if (!dataDirectory.toFile().exists())
|
||||||
|
dataDirectory.toFile().mkdir();
|
||||||
|
|
||||||
|
File file = new File(dataDirectory.toFile(), "plugin.conf");
|
||||||
|
|
||||||
|
if (!file.exists()) {
|
||||||
|
try (InputStream in = getClass().getResourceAsStream("velocity.conf")) {
|
||||||
|
Files.copy(in, file.toPath());
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Unable to copy velocity.conf", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loader == null) {
|
||||||
|
loader = HoconConfigurationLoader.builder().setFile(file).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
CommandManager commandManager = proxyServer.getCommandManager();
|
||||||
|
mainCommand = new MainCommand(this);
|
||||||
|
mainCommandMeta = commandManager.metaBuilder("playerbalancer")
|
||||||
|
.aliases("balancer")
|
||||||
|
.plugin(this)
|
||||||
|
.build();
|
||||||
|
commandManager.register(mainCommandMeta, mainCommand);
|
||||||
|
|
||||||
|
CommentedConfigurationNode node = loader.load();
|
||||||
|
settings = node.getValue(TypeToken.of(SettingsHolder.class));
|
||||||
|
|
||||||
|
if (settings.getGeneralProps().isEnabled()) {
|
||||||
|
if (settings.getGeneralProps().isAutoReload()) {
|
||||||
|
reloadListener = new ProxyReloadListener(this);
|
||||||
|
proxyServer.getEventManager().register(this, reloadListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
networkManager = new NetworkManager(this);
|
||||||
|
|
||||||
|
sectionManager = new SectionManager(this);
|
||||||
|
sectionManager.load();
|
||||||
|
|
||||||
|
statusManager = new StatusManager(this);
|
||||||
|
|
||||||
|
if (settings.getFeaturesProps().getServerCheckerProps().isEnabled()) {
|
||||||
|
statusManager.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.getFeaturesProps().getFallbackCommandProps().isEnabled()) {
|
||||||
|
fallbackCommand = new FallbackCommand(this);
|
||||||
|
fallbackCommandMeta = commandManager
|
||||||
|
.metaBuilder(settings.getFeaturesProps().getFallbackCommandProps().getCommand().getName())
|
||||||
|
.aliases(settings.getFeaturesProps().getFallbackCommandProps().getCommand().getAliasesArray())
|
||||||
|
.plugin(this)
|
||||||
|
.build();
|
||||||
|
commandManager.register(fallbackCommandMeta, fallbackCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
connectListener = new ServerConnectListener(this);
|
||||||
|
proxyServer.getEventManager().register(this, connectListener);
|
||||||
|
|
||||||
|
if (settings.getGeneralProps().isPluginMessaging()) {
|
||||||
|
proxyServer.getChannelRegistrar().register(PB_CHANNEL);
|
||||||
|
|
||||||
|
proxyServer.getEventManager().register(this, statusManager);
|
||||||
|
|
||||||
|
pluginMessageListener = new PluginMessageListener(this);
|
||||||
|
proxyServer.getEventManager().register(this, pluginMessageListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
manageCommand = new ManageCommand(this);
|
||||||
|
manageCommandMeta = commandManager
|
||||||
|
.metaBuilder("section")
|
||||||
|
.plugin(this)
|
||||||
|
.build();
|
||||||
|
commandManager.register(manageCommandMeta, manageCommand);
|
||||||
|
|
||||||
|
proxyServer.getEventManager().register(this, new PlayerDisconnectListener(this));
|
||||||
|
|
||||||
|
if (settings.getFeaturesProps().getKickHandlerProps().isEnabled()) {
|
||||||
|
kickListener = new ServerKickListener(this);
|
||||||
|
proxyServer.getEventManager().register(this, kickListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
PasteHelper.reset();
|
||||||
|
getLogger().info("The plugin has finished loading without any problems");
|
||||||
|
} else {
|
||||||
|
getLogger().warn("-----------------------------------------------------");
|
||||||
|
getLogger().warn("WARNING: This plugin is disabled, do not forget to set enabled on the config to true");
|
||||||
|
getLogger().warn("Nothing is going to work until you do that, you can reload me by using the /balancer command");
|
||||||
|
getLogger().warn("-----------------------------------------------------");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
this.failed = true;
|
||||||
|
getLogger().error("The plugin could not continue loading due to an unexpected exception", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void execStop() {
|
||||||
|
if (mainCommand != null) {
|
||||||
|
proxyServer.getCommandManager().unregister(mainCommandMeta);
|
||||||
|
mainCommand = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.getGeneralProps().isEnabled()) {
|
||||||
|
// Do not try to do anything if the plugin has not loaded correctly
|
||||||
|
if (failed) return;
|
||||||
|
|
||||||
|
if (settings.getGeneralProps().isAutoReload()) {
|
||||||
|
if (reloadListener != null) {
|
||||||
|
proxyServer.getEventManager().unregisterListener(this, reloadListener);
|
||||||
|
reloadListener = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.getFeaturesProps().getServerCheckerProps().isEnabled()) {
|
||||||
|
if (statusManager != null) {
|
||||||
|
statusManager.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.getFeaturesProps().getFallbackCommandProps().isEnabled()) {
|
||||||
|
if (fallbackCommand != null) {
|
||||||
|
proxyServer.getCommandManager().unregister(fallbackCommandMeta);
|
||||||
|
fallbackCommand = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.getFeaturesProps().getKickHandlerProps().isEnabled()) {
|
||||||
|
if (kickListener != null) {
|
||||||
|
proxyServer.getEventManager().unregisterListener(this, kickListener);
|
||||||
|
kickListener = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connectListener != null) {
|
||||||
|
proxyServer.getEventManager().unregisterListener(this, connectListener);
|
||||||
|
connectListener = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.getGeneralProps().isPluginMessaging()) {
|
||||||
|
if (pluginMessageListener != null) {
|
||||||
|
proxyServer.getChannelRegistrar().unregister(PB_CHANNEL);
|
||||||
|
proxyServer.getEventManager().unregisterListener(this, pluginMessageListener);
|
||||||
|
pluginMessageListener = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (manageCommand != null) {
|
||||||
|
proxyServer.getCommandManager().unregister(manageCommandMeta);
|
||||||
|
manageCommand = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sectionManager != null) {
|
||||||
|
sectionManager.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerAssignRegistry.getTable().clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerLocker.flush();
|
||||||
|
failed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean reloadPlugin() {
|
||||||
|
getLogger().info("Reloading the plugin...");
|
||||||
|
long starting = System.currentTimeMillis();
|
||||||
|
|
||||||
|
this.execStop();
|
||||||
|
this.execStart();
|
||||||
|
|
||||||
|
if (!failed) {
|
||||||
|
long ending = System.currentTimeMillis() - starting;
|
||||||
|
getLogger().info(String.format("The plugin has been reloaded, took %sms", ending));
|
||||||
|
}
|
||||||
|
|
||||||
|
return !failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SettingsHolder getSettings() {
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SectionManager getSectionManager() {
|
||||||
|
return sectionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StatusManager getStatusManager() {
|
||||||
|
return statusManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FallbackCommand getFallbackCommand() {
|
||||||
|
return fallbackCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NetworkManager getNetworkManager() {
|
||||||
|
return networkManager;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,125 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.commands;
|
||||||
|
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.PlayerBalancer;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.connection.ConnectionIntent;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.section.ServerSection;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.settings.props.MessagesProps;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.settings.props.features.FallbackCommandProps;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.utils.MessageUtils;
|
||||||
|
import com.velocitypowered.api.command.CommandSource;
|
||||||
|
import com.velocitypowered.api.command.SimpleCommand;
|
||||||
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
import com.velocitypowered.api.proxy.ServerConnection;
|
||||||
|
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static com.jaimemartz.playerbalancer.velocity.utils.MessageUtils.safeNull;
|
||||||
|
|
||||||
|
public class FallbackCommand implements SimpleCommand {
|
||||||
|
private final PlayerBalancer plugin;
|
||||||
|
private final MessagesProps messages;
|
||||||
|
protected final FallbackCommandProps props;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for `fallback-command`
|
||||||
|
*/
|
||||||
|
public FallbackCommand(PlayerBalancer plugin) {
|
||||||
|
this.messages = plugin.getSettings().getMessagesProps();
|
||||||
|
this.props = plugin.getSettings().getFeaturesProps().getFallbackCommandProps();
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Invocation invocation) {
|
||||||
|
CommandSource source = invocation.source();
|
||||||
|
String[] args = invocation.arguments();
|
||||||
|
|
||||||
|
if (source instanceof Player) {
|
||||||
|
Player player = (Player) source;
|
||||||
|
ServerSection target = getSection(player);
|
||||||
|
|
||||||
|
if (target != null) {
|
||||||
|
if (args.length == 1) {
|
||||||
|
try {
|
||||||
|
int number = Integer.parseInt(args[0]);
|
||||||
|
if (number <= 0) {
|
||||||
|
MessageUtils.send(player, messages.getInvalidInputMessage());
|
||||||
|
} else if (number > target.getServers().size()) {
|
||||||
|
MessageUtils.send(player, messages.getInvalidInputMessage());
|
||||||
|
} else {
|
||||||
|
ServerInfo server = Iterables.get(target.getServers(), number - 1).getServerInfo();
|
||||||
|
ConnectionIntent.direct(plugin, player, server, null);
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
MessageUtils.send(player, messages.getInvalidInputMessage());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (props.isPreventSameSection()) {
|
||||||
|
Optional<ServerConnection> current = player.getCurrentServer();
|
||||||
|
if (current.isPresent()) {
|
||||||
|
if (target.getServers().contains(current.get().getServer())) {
|
||||||
|
MessageUtils.send(player, plugin.getSettings().getMessagesProps().getSameSectionMessage(),
|
||||||
|
(str) -> str.replace("{server}", current.get().getServerInfo().getName())
|
||||||
|
.replace("{section}", target.getName())
|
||||||
|
.replace("{alias}", safeNull(target.getProps().getAlias()))
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ConnectionIntent.simple(plugin, player, target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
source.sendMessage(Component.text("This command can only be executed by a player", NamedTextColor.RED));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerSection getSection(Player player) {
|
||||||
|
ServerSection current = plugin.getSectionManager().getByPlayer(player);
|
||||||
|
|
||||||
|
if (current != null) {
|
||||||
|
if (props.getExcludedSections().contains(current.getName())) {
|
||||||
|
MessageUtils.send(player, messages.getUnavailableServerMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerSection target = current.getParent();
|
||||||
|
|
||||||
|
String bindName = props.getRules().get(current.getName());
|
||||||
|
if (bindName != null) {
|
||||||
|
ServerSection bind = plugin.getSectionManager().getByName(bindName);
|
||||||
|
if (bind != null) {
|
||||||
|
target = bind;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target == null) {
|
||||||
|
MessageUtils.send(player, messages.getUnavailableServerMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.isRestrictive()) {
|
||||||
|
if (current.getPosition() >= 0 && target.getPosition() < 0) {
|
||||||
|
MessageUtils.send(player, messages.getUnavailableServerMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return target;
|
||||||
|
} else {
|
||||||
|
if (plugin.getSettings().getFeaturesProps().getBalancerProps().isDefaultPrincipal()) {
|
||||||
|
return plugin.getSectionManager().getPrincipal();
|
||||||
|
} else {
|
||||||
|
MessageUtils.send(player, messages.getUnavailableServerMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,142 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.commands;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.PlayerBalancer;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.helper.PasteHelper;
|
||||||
|
import com.velocitypowered.api.command.CommandSource;
|
||||||
|
import com.velocitypowered.api.command.SimpleCommand;
|
||||||
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.event.ClickEvent;
|
||||||
|
import net.kyori.adventure.text.event.HoverEvent;
|
||||||
|
import net.kyori.adventure.text.format.TextDecoration;
|
||||||
|
|
||||||
|
import static net.kyori.adventure.text.Component.text;
|
||||||
|
import static net.kyori.adventure.text.format.NamedTextColor.AQUA;
|
||||||
|
import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
|
||||||
|
import static net.kyori.adventure.text.format.NamedTextColor.GREEN;
|
||||||
|
import static net.kyori.adventure.text.format.NamedTextColor.RED;
|
||||||
|
|
||||||
|
public class MainCommand implements SimpleCommand {
|
||||||
|
private final PlayerBalancer plugin;
|
||||||
|
|
||||||
|
public MainCommand(PlayerBalancer plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Invocation invocation) {
|
||||||
|
CommandSource sender = invocation.source();
|
||||||
|
String[] args = invocation.arguments();
|
||||||
|
|
||||||
|
if (args.length != 0) {
|
||||||
|
switch (args[0].toLowerCase()) {
|
||||||
|
case "paste": {
|
||||||
|
if (sender.hasPermission("playerbalancer.admin")) {
|
||||||
|
if (args.length == 2) {
|
||||||
|
switch (args[1].toLowerCase()) {
|
||||||
|
case "all": {
|
||||||
|
PasteHelper.PLUGIN.send(plugin, sender);
|
||||||
|
PasteHelper.VELOCITY.send(plugin, sender);
|
||||||
|
PasteHelper.LOGS.send(plugin, sender);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "plugin": {
|
||||||
|
PasteHelper.PLUGIN.send(plugin, sender);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "velocity": {
|
||||||
|
PasteHelper.VELOCITY.send(plugin, sender);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "logs": {
|
||||||
|
PasteHelper.LOGS.send(plugin, sender);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
sender.sendMessage(text("This is not a valid argument for this command! Execute /balancer paste for help", RED));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (sender instanceof Player) {
|
||||||
|
sender.sendMessage(text("Available paste types:", AQUA));
|
||||||
|
|
||||||
|
sender.sendMessage(text("Click one:", AQUA)
|
||||||
|
.append(text(" [")
|
||||||
|
.color(GRAY)
|
||||||
|
.append(text("All")
|
||||||
|
.color(RED)
|
||||||
|
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.RUN_COMMAND, "/balancer paste all"))
|
||||||
|
.hoverEvent(HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT, text("Click to paste all", RED)))
|
||||||
|
)
|
||||||
|
.append(Component.text("]", GRAY)))
|
||||||
|
.append(text(" [")
|
||||||
|
.color(GRAY)
|
||||||
|
.append(text("Plugin")
|
||||||
|
.color(RED)
|
||||||
|
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.RUN_COMMAND, "/balancer paste plugin"))
|
||||||
|
.hoverEvent(HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT, text("Click to paste plugin config", RED)))
|
||||||
|
)
|
||||||
|
.append(Component.text("]", GRAY))
|
||||||
|
)
|
||||||
|
.append(text(" [")
|
||||||
|
.color(GRAY)
|
||||||
|
.append(text("Velocity")
|
||||||
|
.color(RED)
|
||||||
|
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.RUN_COMMAND, "/balancer paste velocity"))
|
||||||
|
.hoverEvent(HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT, text("Click to paste Velocity config", RED)))
|
||||||
|
)
|
||||||
|
.append(Component.text("]", GRAY)))
|
||||||
|
.append(text(" [")
|
||||||
|
.color(GRAY)
|
||||||
|
.append(text("Logs")
|
||||||
|
.color(RED)
|
||||||
|
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.RUN_COMMAND, "/balancer paste logs"))
|
||||||
|
.hoverEvent(HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT, text("Click to paste logs", RED)))
|
||||||
|
)
|
||||||
|
.append(Component.text("]", GRAY)))
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
sender.sendMessage(text("Usage: /balancer paste [all|plugin|velocity|logs]", RED));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sender.sendMessage(text("You do not have permission to execute this command!"));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "reload": {
|
||||||
|
if (sender.hasPermission("playerbalancer.admin")) {
|
||||||
|
sender.sendMessage(text("Reloading the configuration, this may take a while...", GREEN));
|
||||||
|
if (plugin.reloadPlugin()) {
|
||||||
|
sender.sendMessage(text("The plugin has been successfully reloaded", GREEN));
|
||||||
|
} else {
|
||||||
|
sender.sendMessage(text("Something went badly while reloading the plugin", RED));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sender.sendMessage(text("You do not have permission to execute this command!", RED));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
sender.sendMessage(text("This is not a valid argument for this command! Execute /balancer for help", RED));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sender.sendMessage(text(Strings.repeat("-", 53), GRAY, TextDecoration.STRIKETHROUGH));
|
||||||
|
sender.sendMessage(text("PlayerBalancer " + plugin.getContainer().getDescription().getVersion().orElse("-.-.-"), GRAY));
|
||||||
|
sender.sendMessage(text("Available commands:", GRAY));
|
||||||
|
sender.sendMessage(text("/balancer", AQUA).append(text(" - ", GRAY)).append(text("Shows you this message", RED)));
|
||||||
|
sender.sendMessage(text("/balancer paste [all|plugin|velocity|logs]", AQUA).append(text(" - ", GRAY)).append(text("Creates a paste with the important files", RED)));
|
||||||
|
sender.sendMessage(text("/balancer reload", AQUA).append(text(" - ", GRAY)).append(text("Reloads the plugin completely", RED)));
|
||||||
|
sender.sendMessage(text(Strings.repeat("-", 53), GRAY, TextDecoration.STRIKETHROUGH));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,273 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.commands;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.PlayerBalancer;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.connection.ConnectionIntent;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.ping.ServerStatus;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.section.SectionManager;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.section.ServerSection;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.utils.MessageUtils;
|
||||||
|
import com.velocitypowered.api.command.CommandSource;
|
||||||
|
import com.velocitypowered.api.command.SimpleCommand;
|
||||||
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
import net.kyori.adventure.text.event.ClickEvent;
|
||||||
|
import net.kyori.adventure.text.event.HoverEvent;
|
||||||
|
import net.kyori.adventure.text.format.TextDecoration;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static net.kyori.adventure.text.Component.newline;
|
||||||
|
import static net.kyori.adventure.text.Component.text;
|
||||||
|
import static net.kyori.adventure.text.Component.textOfChildren;
|
||||||
|
import static net.kyori.adventure.text.format.NamedTextColor.AQUA;
|
||||||
|
import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
|
||||||
|
import static net.kyori.adventure.text.format.NamedTextColor.GREEN;
|
||||||
|
import static net.kyori.adventure.text.format.NamedTextColor.RED;
|
||||||
|
import static net.kyori.adventure.text.format.NamedTextColor.WHITE;
|
||||||
|
|
||||||
|
public class ManageCommand implements SimpleCommand {
|
||||||
|
private final PlayerBalancer plugin;
|
||||||
|
|
||||||
|
public ManageCommand(PlayerBalancer plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasPermission(Invocation invocation) {
|
||||||
|
return invocation.source().hasPermission("playerbalancer.admin");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Invocation invocation) {
|
||||||
|
CommandSource sender = invocation.source();
|
||||||
|
String[] args = invocation.arguments();
|
||||||
|
|
||||||
|
if (args.length != 0) {
|
||||||
|
switch (args[0].toLowerCase()) {
|
||||||
|
case "connect": {
|
||||||
|
if (args.length >= 2) {
|
||||||
|
String input = args[1];
|
||||||
|
ServerSection section = plugin.getSectionManager().getByName(input);
|
||||||
|
if (section != null) {
|
||||||
|
if (args.length == 3) {
|
||||||
|
Optional<Player> player = plugin.getProxyServer().getPlayer(args[2]);
|
||||||
|
if (player.isPresent()) {
|
||||||
|
ConnectionIntent.simple(plugin, player.get(), section);
|
||||||
|
} else {
|
||||||
|
sender.sendMessage(text("There is no player with that name connected to this proxy", RED));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (sender instanceof Player) {
|
||||||
|
ConnectionIntent.simple(plugin, (Player) sender, section);
|
||||||
|
} else {
|
||||||
|
sender.sendMessage(text("This command variant can only be executed by a player", RED));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
MessageUtils.send(sender, plugin.getSettings().getMessagesProps().getUnknownSectionMessage());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sender.sendMessage(text("Usage: /section connect <section> [player]", RED));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "info": {
|
||||||
|
if (args.length == 2) {
|
||||||
|
String input = args[1];
|
||||||
|
SectionManager manager = plugin.getSectionManager();
|
||||||
|
ServerSection section = manager.getByName(input);
|
||||||
|
|
||||||
|
if (section != null) {
|
||||||
|
sender.sendMessage(text(Strings.repeat("-", 53), GRAY, TextDecoration.STRIKETHROUGH));
|
||||||
|
|
||||||
|
sender.sendMessage(text("Information of section: ", GRAY)
|
||||||
|
.append(text(section.getName(), RED))
|
||||||
|
);
|
||||||
|
|
||||||
|
sender.sendMessage(text("Principal: ", GRAY)
|
||||||
|
.append(
|
||||||
|
text(manager.isPrincipal(section) ? "yes" : "no")
|
||||||
|
.color(manager.isPrincipal(section) ? GREEN : RED)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
sender.sendMessage(text("Dummy: ", GRAY)
|
||||||
|
.append(
|
||||||
|
text(manager.isDummy(section) ? "yes" : "no")
|
||||||
|
.color(manager.isDummy(section) ? GREEN : RED)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
sender.sendMessage(text("Reiterative: ", GRAY)
|
||||||
|
.append(
|
||||||
|
text(manager.isReiterative(section) ? "yes" : "no")
|
||||||
|
.color(manager.isReiterative(section) ? GREEN : RED))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (section.getParent() != null) {
|
||||||
|
sender.sendMessage(text("Parent: ", GRAY)
|
||||||
|
.append(text(section.getParent().getName(), AQUA))
|
||||||
|
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.RUN_COMMAND, String.format("/section info %s", section.getParent().getName())))
|
||||||
|
.hoverEvent(HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT, text("Click me for info of " + section.getParent().getName(), RED)))
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
sender.sendMessage(text("Parent: ", GRAY)
|
||||||
|
.append(text("None", AQUA))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (section.getProps().getAlias() != null) {
|
||||||
|
sender.sendMessage(text("Alias: ", GRAY)
|
||||||
|
.append(text("\"", AQUA))
|
||||||
|
.append(text(section.getProps().getAlias(), RED))
|
||||||
|
.append(text("\"", AQUA))
|
||||||
|
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
sender.sendMessage(text("Alias: ", GRAY)
|
||||||
|
.append(text("None", AQUA))
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
sender.sendMessage(text("Position: ", GRAY)
|
||||||
|
.append(text(String.valueOf(section.getPosition()), AQUA))
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
sender.sendMessage(text("Provider: ", GRAY)
|
||||||
|
.append(text(section.getImplicitProvider().name(), AQUA))
|
||||||
|
.append(text(String.format(" (%s)", section.isInherited() ? "Implicit" : "Explicit"), GRAY))
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
if (section.getServer() != null) {
|
||||||
|
sender.sendMessage(text("Section Server: ", GRAY)
|
||||||
|
.append(text(section.getServer().getServerInfo().getName(), AQUA))
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
sender.sendMessage(text("Section Server: ", GRAY)
|
||||||
|
.append(text("None", AQUA))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (section.getCommand() != null) {
|
||||||
|
sender.sendMessage(text("Section Command: ", GRAY)
|
||||||
|
.append(text(section.getCommand().getCommandProps().getName(), AQUA))
|
||||||
|
.hoverEvent(HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT,
|
||||||
|
textOfChildren(
|
||||||
|
text("Name: ", GRAY),
|
||||||
|
text(section.getCommand().getCommandProps().getName()).color(AQUA),
|
||||||
|
|
||||||
|
newline(),
|
||||||
|
|
||||||
|
text("Permission: ", GRAY),
|
||||||
|
text("\"", AQUA),
|
||||||
|
text(section.getCommand().getCommandProps().getPermission(), RED),
|
||||||
|
text("\"", AQUA),
|
||||||
|
|
||||||
|
newline(),
|
||||||
|
|
||||||
|
text("Aliases: ", GRAY),
|
||||||
|
text(Arrays.toString(section.getCommand().getCommandProps().getAliasesArray()), AQUA)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
sender.sendMessage(text("Section Command: ", GRAY)
|
||||||
|
.append(text("None", AQUA))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!section.getServers().isEmpty()) {
|
||||||
|
sender.sendMessage(text("Section Servers: ", GRAY));
|
||||||
|
|
||||||
|
section.getServers().forEach(server -> {
|
||||||
|
ServerStatus status = plugin.getStatusManager().getStatus(server.getServerInfo());
|
||||||
|
boolean accessible = plugin.getStatusManager().isAccessible(server.getServerInfo());
|
||||||
|
sender.sendMessage(
|
||||||
|
text("• Server: ")
|
||||||
|
.append(text(server.getServerInfo().getName()))
|
||||||
|
.hoverEvent(HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT,
|
||||||
|
textOfChildren(
|
||||||
|
text("Online: ", GRAY),
|
||||||
|
text(status.isOnline() ? "yes" : "no", status.isOnline() ? GREEN : RED),
|
||||||
|
|
||||||
|
newline(),
|
||||||
|
|
||||||
|
text("Accessible: ", GRAY),
|
||||||
|
text(accessible ? "yes" : "no", accessible ? GREEN : RED),
|
||||||
|
|
||||||
|
newline(),
|
||||||
|
|
||||||
|
text("Description: ", GRAY),
|
||||||
|
text("\"", AQUA),
|
||||||
|
status.getDescription().color(WHITE),
|
||||||
|
text("\"", AQUA),
|
||||||
|
|
||||||
|
newline(),
|
||||||
|
|
||||||
|
text("Address: ", GRAY),
|
||||||
|
text(server.getServerInfo().getAddress().toString(), AQUA)
|
||||||
|
)
|
||||||
|
))
|
||||||
|
.append(text((String.format(" (%d/%d) ",
|
||||||
|
status.getPlayers(),
|
||||||
|
status.getMaximum()))))
|
||||||
|
.color(status.isOnline() ? GREEN : RED)
|
||||||
|
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
sender.sendMessage(text("Section Servers: ", GRAY)
|
||||||
|
.append(text("None"))
|
||||||
|
.color(AQUA)
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
sender.sendMessage(text(Strings.repeat("-", 53), GRAY, TextDecoration.STRIKETHROUGH));
|
||||||
|
} else {
|
||||||
|
MessageUtils.send(sender, plugin.getSettings().getMessagesProps().getUnknownSectionMessage());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sender.sendMessage(text("Usage: /section info <section>", RED));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "list": {
|
||||||
|
if (!plugin.getSectionManager().getSections().isEmpty()) {
|
||||||
|
sender.sendMessage(text("These are the registered sections: ", GRAY));
|
||||||
|
|
||||||
|
plugin.getSectionManager().getSections().forEach((name, section) -> {
|
||||||
|
sender.sendMessage(text("• Section: ", GRAY)
|
||||||
|
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.RUN_COMMAND, String.format("/section info %s", name)))
|
||||||
|
.hoverEvent(HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT, text("Click me for info of section " + name, RED)))
|
||||||
|
.append(text(name, AQUA))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
sender.sendMessage(text("There are no sections to list", GRAY));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
sender.sendMessage(text("This is not a valid argument for this command! Execute /section for help", RED));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sender.sendMessage(text(Strings.repeat("-", 53), GRAY, TextDecoration.STRIKETHROUGH));
|
||||||
|
sender.sendMessage(text("Available commands:", GRAY));
|
||||||
|
sender.sendMessage(text("/section", AQUA).append(text(" - ", GRAY)).append(text("Shows you this message", RED)));
|
||||||
|
sender.sendMessage(text("/section list", AQUA).append(text(" - ", GRAY)).append(text("Tells you which sections are configured in the plugin", RED)));
|
||||||
|
sender.sendMessage(text("/section info <section>", AQUA).append(text(" - ", GRAY)).append(text("Tells you info about the specified section", RED)));
|
||||||
|
sender.sendMessage(text("/section connect <section> [player]", AQUA).append(text(" - ", GRAY)).append(text("Connects you or the specified player to that section", RED)));
|
||||||
|
sender.sendMessage(text(Strings.repeat("-", 53), GRAY, TextDecoration.STRIKETHROUGH));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,145 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.connection;
|
||||||
|
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.PlayerBalancer;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.helper.PlayerLocker;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.section.ServerSection;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.utils.MessageUtils;
|
||||||
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||||
|
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import static com.jaimemartz.playerbalancer.velocity.utils.MessageUtils.safeNull;
|
||||||
|
|
||||||
|
public abstract class ConnectionIntent {
|
||||||
|
private final PlayerBalancer plugin;
|
||||||
|
private final Player player;
|
||||||
|
private final ServerSection section;
|
||||||
|
private final List<ServerInfo> exclusions;
|
||||||
|
|
||||||
|
public ConnectionIntent(PlayerBalancer plugin, Player player, ServerSection section) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.player = player;
|
||||||
|
this.section = tryRoute(player, section);
|
||||||
|
this.exclusions = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ServerInfo> getExclusions() {
|
||||||
|
return exclusions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void execute() {
|
||||||
|
MessageUtils.send(player, plugin.getSettings().getMessagesProps().getConnectingMessage(),
|
||||||
|
(str) -> str.replace("{section}", section.getName())
|
||||||
|
.replace("{alias}", safeNull(section.getProps().getAlias()))
|
||||||
|
);
|
||||||
|
|
||||||
|
// Ensure a new copy of the section servers
|
||||||
|
List<RegisteredServer> servers = new ArrayList<>(section.getServers());
|
||||||
|
|
||||||
|
// Prevents connections to the same server
|
||||||
|
player.getCurrentServer().ifPresent(current -> servers.remove(current.getServer()));
|
||||||
|
|
||||||
|
if (section.getImplicitProvider() != ProviderType.NONE) {
|
||||||
|
ServerInfo target = this.fetchServer(player, section, section.getImplicitProvider(), servers);
|
||||||
|
if (target != null) {
|
||||||
|
this.connect(target, (response) -> {
|
||||||
|
if (response) { // only if the connect has been executed correctly
|
||||||
|
MessageUtils.send(player, plugin.getSettings().getMessagesProps().getConnectedMessage(),
|
||||||
|
(str) -> str.replace("{server}", target.getName())
|
||||||
|
.replace("{section}", section.getName())
|
||||||
|
.replace("{alias}", safeNull(section.getProps().getAlias()))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
MessageUtils.send(player, plugin.getSettings().getMessagesProps().getFailureMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ServerInfo fetchServer(Player player, ServerSection section, ProviderType provider, List<RegisteredServer> servers) {
|
||||||
|
if (plugin.getSectionManager().isReiterative(section)) {
|
||||||
|
if (ServerAssignRegistry.hasAssignedServer(player, section)) {
|
||||||
|
ServerInfo target = ServerAssignRegistry.getAssignedServer(player, section);
|
||||||
|
if (plugin.getStatusManager().isAccessible(target)) {
|
||||||
|
return target;
|
||||||
|
} else {
|
||||||
|
ServerAssignRegistry.revokeTarget(player, section);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int intents = plugin.getSettings().getFeaturesProps().getServerCheckerProps().getAttempts();
|
||||||
|
for (int intent = 1; intent <= intents; intent++) {
|
||||||
|
if (servers.size() == 0) return null;
|
||||||
|
|
||||||
|
RegisteredServer target = provider.requestTarget(plugin, section, servers, player);
|
||||||
|
if (target == null) continue;
|
||||||
|
|
||||||
|
if (plugin.getStatusManager().isAccessible(target.getServerInfo())) {
|
||||||
|
return target.getServerInfo();
|
||||||
|
} else {
|
||||||
|
servers.remove(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ServerSection tryRoute(Player player, ServerSection section) {
|
||||||
|
if (plugin.getSettings().getFeaturesProps().getPermissionRouterProps().isEnabled()) {
|
||||||
|
Map<String, String> routes = plugin.getSettings().getFeaturesProps().getPermissionRouterProps().getRules().get(section.getName());
|
||||||
|
ServerSection current = plugin.getSectionManager().getByPlayer(player);
|
||||||
|
|
||||||
|
if (routes != null) {
|
||||||
|
for (Map.Entry<String, String> route : routes.entrySet()) {
|
||||||
|
if (player.hasPermission(route.getKey())) {
|
||||||
|
ServerSection bind = plugin.getSectionManager().getByName(route.getValue());
|
||||||
|
|
||||||
|
if (bind != null) {
|
||||||
|
if (current == bind) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bind;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return section;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void connect(ServerInfo server, Consumer<Boolean> callback);
|
||||||
|
|
||||||
|
public static void simple(PlayerBalancer plugin, Player player, ServerSection section) {
|
||||||
|
new ConnectionIntent(plugin, player, section) {
|
||||||
|
@Override
|
||||||
|
public void connect(ServerInfo server, Consumer<Boolean> callback) {
|
||||||
|
ConnectionIntent.direct(plugin, player, server, callback);
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void direct(PlayerBalancer plugin, Player player, ServerInfo server, Consumer<Boolean> callback) {
|
||||||
|
PlayerLocker.lock(player);
|
||||||
|
plugin.getProxyServer().getServer(server.getName()).ifPresent((rServer) -> {
|
||||||
|
player.createConnectionRequest(rServer).connect()
|
||||||
|
.whenComplete((result, throwable) -> {
|
||||||
|
plugin.getProxyServer().getScheduler().buildTask(plugin, () -> PlayerLocker.unlock(player))
|
||||||
|
.delay(5, TimeUnit.SECONDS).schedule();
|
||||||
|
|
||||||
|
callback.accept(result.isSuccessful());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,100 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.connection;
|
||||||
|
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.PlayerBalancer;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.connection.provider.AbstractProvider;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.connection.provider.types.NullProvider;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.connection.provider.types.progressive.ProgressiveFillerProvider;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.connection.provider.types.progressive.ProgressiveLowestProvider;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.connection.provider.types.progressive.ProgressiveProvider;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.connection.provider.types.random.RandomFillerProvider;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.connection.provider.types.random.RandomLowestProvider;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.connection.provider.types.random.RandomProvider;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.section.ServerSection;
|
||||||
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public enum ProviderType {
|
||||||
|
NONE {
|
||||||
|
NullProvider provider = new NullProvider();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RegisteredServer requestTarget(PlayerBalancer plugin, ServerSection section, List<RegisteredServer> servers, Player player) {
|
||||||
|
return provider.requestTarget(plugin, section, servers, player);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
RANDOM {
|
||||||
|
RandomProvider provider = new RandomProvider();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RegisteredServer requestTarget(PlayerBalancer plugin, ServerSection section, List<RegisteredServer> servers, Player player) {
|
||||||
|
return provider.requestTarget(plugin, section, servers, player);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
RANDOM_LOWEST {
|
||||||
|
RandomLowestProvider provider = new RandomLowestProvider();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RegisteredServer requestTarget(PlayerBalancer plugin, ServerSection section, List<RegisteredServer> servers, Player player) {
|
||||||
|
return provider.requestTarget(plugin, section, servers, player);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
RANDOM_FILLER {
|
||||||
|
RandomFillerProvider provider = new RandomFillerProvider();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RegisteredServer requestTarget(PlayerBalancer plugin, ServerSection section, List<RegisteredServer> servers, Player player) {
|
||||||
|
return provider.requestTarget(plugin, section, servers, player);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
PROGRESSIVE {
|
||||||
|
ProgressiveProvider provider = new ProgressiveProvider();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RegisteredServer requestTarget(PlayerBalancer plugin, ServerSection section, List<RegisteredServer> servers, Player player) {
|
||||||
|
return provider.requestTarget(plugin, section, servers, player);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
PROGRESSIVE_LOWEST {
|
||||||
|
ProgressiveLowestProvider provider = new ProgressiveLowestProvider();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RegisteredServer requestTarget(PlayerBalancer plugin, ServerSection section, List<RegisteredServer> servers, Player player) {
|
||||||
|
return provider.requestTarget(plugin, section, servers, player);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
PROGRESSIVE_FILLER {
|
||||||
|
ProgressiveFillerProvider provider = new ProgressiveFillerProvider();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RegisteredServer requestTarget(PlayerBalancer plugin, ServerSection section, List<RegisteredServer> servers, Player player) {
|
||||||
|
return provider.requestTarget(plugin, section, servers, player);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
EXTERNAL {
|
||||||
|
@Override
|
||||||
|
public RegisteredServer requestTarget(PlayerBalancer plugin, ServerSection section, List<RegisteredServer> servers, Player player) {
|
||||||
|
AbstractProvider provider = section.getExternalProvider();
|
||||||
|
if (provider == null) {
|
||||||
|
plugin.getLogger().warn("Target requested to the EXTERNAL provider with the section not having a provider instance, falling back to RANDOM...");
|
||||||
|
return RANDOM.requestTarget(plugin, section, servers, player);
|
||||||
|
}
|
||||||
|
return provider.requestTarget(plugin, section, servers, player);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public abstract RegisteredServer requestTarget(
|
||||||
|
PlayerBalancer plugin,
|
||||||
|
ServerSection section,
|
||||||
|
List<RegisteredServer> servers,
|
||||||
|
Player player
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.connection;
|
||||||
|
|
||||||
|
import com.google.common.collect.HashBasedTable;
|
||||||
|
import com.google.common.collect.Table;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.section.ServerSection;
|
||||||
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class ServerAssignRegistry {
|
||||||
|
private static final Table<Player, ServerSection, ServerInfo> table = HashBasedTable.create();
|
||||||
|
|
||||||
|
public static void assignTarget(Player player, ServerSection section, ServerInfo server) {
|
||||||
|
synchronized (table) {
|
||||||
|
table.put(player, section, server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void revokeTarget(Player player, ServerSection section) {
|
||||||
|
synchronized (table) {
|
||||||
|
table.remove(player, section);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ServerInfo getAssignedServer(Player player, ServerSection section) {
|
||||||
|
synchronized (table) {
|
||||||
|
return table.get(player, section);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<ServerSection, ServerInfo> getAssignments(Player player) {
|
||||||
|
synchronized (table) {
|
||||||
|
return table.row(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clearAsssignedServers(Player player) {
|
||||||
|
synchronized (table) {
|
||||||
|
table.row(player).clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hasAssignedServer(Player player, ServerSection section) {
|
||||||
|
synchronized (table) {
|
||||||
|
return table.contains(player, section);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Table<Player, ServerSection, ServerInfo> getTable() {
|
||||||
|
synchronized (table) {
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.connection.provider;
|
||||||
|
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.PlayerBalancer;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.section.ServerSection;
|
||||||
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public abstract class AbstractProvider {
|
||||||
|
public abstract RegisteredServer requestTarget(
|
||||||
|
PlayerBalancer plugin,
|
||||||
|
ServerSection section,
|
||||||
|
List<RegisteredServer> servers,
|
||||||
|
Player player
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.connection.provider.types;
|
||||||
|
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.PlayerBalancer;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.connection.provider.AbstractProvider;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.section.ServerSection;
|
||||||
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class NullProvider extends AbstractProvider {
|
||||||
|
@Override
|
||||||
|
public RegisteredServer requestTarget(PlayerBalancer plugin, ServerSection section, List<RegisteredServer> servers, Player player) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.connection.provider.types.progressive;
|
||||||
|
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.PlayerBalancer;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.connection.provider.AbstractProvider;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.ping.ServerStatus;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.section.ServerSection;
|
||||||
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ProgressiveFillerProvider extends AbstractProvider {
|
||||||
|
@Override
|
||||||
|
public RegisteredServer requestTarget(PlayerBalancer plugin, ServerSection section, List<RegisteredServer> servers, Player player) {
|
||||||
|
int max = Integer.MIN_VALUE;
|
||||||
|
RegisteredServer target = null;
|
||||||
|
|
||||||
|
for (RegisteredServer server : servers) {
|
||||||
|
ServerStatus status = plugin.getStatusManager().getStatus(server.getServerInfo());
|
||||||
|
int count = plugin.getNetworkManager().getPlayers(server.getServerInfo());
|
||||||
|
|
||||||
|
if (count > max && count <= status.getMaximum()) {
|
||||||
|
max = count;
|
||||||
|
target = server;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.connection.provider.types.progressive;
|
||||||
|
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.PlayerBalancer;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.connection.provider.AbstractProvider;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.section.ServerSection;
|
||||||
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ProgressiveLowestProvider extends AbstractProvider {
|
||||||
|
@Override
|
||||||
|
public RegisteredServer requestTarget(PlayerBalancer plugin, ServerSection section, List<RegisteredServer> servers, Player player) {
|
||||||
|
int min = Integer.MAX_VALUE;
|
||||||
|
RegisteredServer target = null;
|
||||||
|
|
||||||
|
for (RegisteredServer server : servers) {
|
||||||
|
int count = plugin.getNetworkManager().getPlayers(server.getServerInfo());
|
||||||
|
|
||||||
|
if (count < min) {
|
||||||
|
min = count;
|
||||||
|
target = server;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.connection.provider.types.progressive;
|
||||||
|
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.PlayerBalancer;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.connection.provider.AbstractProvider;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.ping.ServerStatus;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.section.ServerSection;
|
||||||
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ProgressiveProvider extends AbstractProvider {
|
||||||
|
@Override
|
||||||
|
public RegisteredServer requestTarget(PlayerBalancer plugin, ServerSection section, List<RegisteredServer> servers, Player player) {
|
||||||
|
for (RegisteredServer server : servers) {
|
||||||
|
ServerStatus status = plugin.getStatusManager().getStatus(server.getServerInfo());
|
||||||
|
if (plugin.getNetworkManager().getPlayers(server.getServerInfo()) < status.getMaximum()) {
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.connection.provider.types.random;
|
||||||
|
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.PlayerBalancer;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.connection.provider.AbstractProvider;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.section.ServerSection;
|
||||||
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static com.jaimemartz.playerbalancer.velocity.utils.RandomUtils.random;
|
||||||
|
|
||||||
|
public class RandomFillerProvider extends AbstractProvider {
|
||||||
|
@Override
|
||||||
|
public RegisteredServer requestTarget(PlayerBalancer plugin, ServerSection section, List<RegisteredServer> servers, Player player) {
|
||||||
|
List<RegisteredServer> results = new ArrayList<>();
|
||||||
|
int max = Integer.MIN_VALUE;
|
||||||
|
|
||||||
|
for (RegisteredServer server : servers) {
|
||||||
|
int count = plugin.getNetworkManager().getPlayers(server.getServerInfo());
|
||||||
|
|
||||||
|
if (count >= max) {
|
||||||
|
if (count > max) {
|
||||||
|
max = count;
|
||||||
|
results.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
results.add(server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return random(results);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.connection.provider.types.random;
|
||||||
|
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.PlayerBalancer;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.connection.provider.AbstractProvider;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.section.ServerSection;
|
||||||
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static com.jaimemartz.playerbalancer.velocity.utils.RandomUtils.random;
|
||||||
|
|
||||||
|
public class RandomLowestProvider extends AbstractProvider {
|
||||||
|
@Override
|
||||||
|
public RegisteredServer requestTarget(PlayerBalancer plugin, ServerSection section, List<RegisteredServer> servers, Player player) {
|
||||||
|
List<RegisteredServer> results = new ArrayList<>();
|
||||||
|
int min = Integer.MAX_VALUE;
|
||||||
|
|
||||||
|
for (RegisteredServer server : servers) {
|
||||||
|
int count = plugin.getNetworkManager().getPlayers(server.getServerInfo());
|
||||||
|
|
||||||
|
if (count <= min) {
|
||||||
|
if (count < min) {
|
||||||
|
min = count;
|
||||||
|
results.clear();
|
||||||
|
}
|
||||||
|
results.add(server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return random(results);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.connection.provider.types.random;
|
||||||
|
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.PlayerBalancer;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.connection.provider.AbstractProvider;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.section.ServerSection;
|
||||||
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static com.jaimemartz.playerbalancer.velocity.utils.RandomUtils.random;
|
||||||
|
|
||||||
|
public class RandomProvider extends AbstractProvider {
|
||||||
|
@Override
|
||||||
|
public RegisteredServer requestTarget(PlayerBalancer plugin, ServerSection section, List<RegisteredServer> servers, Player player) {
|
||||||
|
return random(servers);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.helper;
|
||||||
|
|
||||||
|
import com.imaginarycode.minecraft.redisbungee.RedisBungeeAPI;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.PlayerBalancer;
|
||||||
|
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||||
|
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class NetworkManager {
|
||||||
|
|
||||||
|
private final PlayerBalancer plugin;
|
||||||
|
|
||||||
|
public NetworkManager(PlayerBalancer plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPlayers(ServerInfo server) {
|
||||||
|
if (plugin.getSettings().getGeneralProps().isRedisBungee()) {
|
||||||
|
try {
|
||||||
|
return RedisBungeeAPI.getRedisBungeeApi().getPlayersOnServer(server.getName()).size();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<RegisteredServer> serverConnection = plugin.getProxyServer().getServer(server.getName());
|
||||||
|
|
||||||
|
return serverConnection.map(registeredServer -> registeredServer.getPlayersConnected().size()).orElse(0);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,135 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.helper;
|
||||||
|
|
||||||
|
import com.google.common.io.CharStreams;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.PlayerBalancer;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.utils.GuestPaste.PasteException;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.utils.HastebinPaste;
|
||||||
|
import com.velocitypowered.api.command.CommandSource;
|
||||||
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
import net.kyori.adventure.text.event.ClickEvent;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
import static net.kyori.adventure.text.Component.text;
|
||||||
|
import static net.kyori.adventure.text.format.NamedTextColor.GREEN;
|
||||||
|
import static net.kyori.adventure.text.format.NamedTextColor.RED;
|
||||||
|
|
||||||
|
public enum PasteHelper {
|
||||||
|
PLUGIN((sender, address) -> {
|
||||||
|
if (sender instanceof Player) {
|
||||||
|
sender.sendMessage(text("Click me for the PlayerBalancer configuration", GREEN)
|
||||||
|
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.OPEN_URL, address.toString()))
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
sender.sendMessage(text("PlayerBalancer configuration link: " + address.toString()));
|
||||||
|
}
|
||||||
|
}, true) {
|
||||||
|
@Override
|
||||||
|
public URL paste(PlayerBalancer plugin) throws Exception {
|
||||||
|
File file = new File(plugin.getDataDirectory().toFile(), "plugin.conf");
|
||||||
|
try (FileInputStream stream = new FileInputStream(file)) {
|
||||||
|
try (InputStreamReader reader = new InputStreamReader(stream, StandardCharsets.UTF_8)) {
|
||||||
|
String content = CharStreams.toString(reader);
|
||||||
|
HastebinPaste paste = new HastebinPaste(HASTEBIN_HOST, content);
|
||||||
|
return paste.paste();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
VELOCITY((sender, address) -> {
|
||||||
|
if (sender instanceof Player) {
|
||||||
|
sender.sendMessage(text("Click me for the Velocity configuration", GREEN)
|
||||||
|
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.OPEN_URL, address.toString()))
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
sender.sendMessage(text("Velocity configuration link: " + address.toString()));
|
||||||
|
}
|
||||||
|
}, true) {
|
||||||
|
@Override
|
||||||
|
public URL paste(PlayerBalancer plugin) throws Exception {
|
||||||
|
File file = new File("velocity.toml");
|
||||||
|
try (FileInputStream stream = new FileInputStream(file)) {
|
||||||
|
try (InputStreamReader reader = new InputStreamReader(stream, StandardCharsets.UTF_8)) {
|
||||||
|
String content = CharStreams.toString(reader);
|
||||||
|
content = content.replaceAll("[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}", "?.?.?.?");
|
||||||
|
HastebinPaste paste = new HastebinPaste(HASTEBIN_HOST, content);
|
||||||
|
return paste.paste();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
LOGS((sender, address) -> {
|
||||||
|
if (sender instanceof Player) {
|
||||||
|
sender.sendMessage(text("Click me for the server logs", GREEN)
|
||||||
|
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.OPEN_URL, address.toString()))
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
sender.sendMessage(text("Server logs link: " + address.toString()));
|
||||||
|
}
|
||||||
|
}, false) {
|
||||||
|
@Override
|
||||||
|
public URL paste(PlayerBalancer plugin) throws Exception {
|
||||||
|
File file = new File("logs/latest.log");
|
||||||
|
try (FileInputStream stream = new FileInputStream(file)) {
|
||||||
|
try (InputStreamReader reader = new InputStreamReader(stream, StandardCharsets.UTF_8)) {
|
||||||
|
String content = CharStreams.toString(reader);
|
||||||
|
HastebinPaste paste = new HastebinPaste(HASTEBIN_HOST, content);
|
||||||
|
return paste.paste();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final String HASTEBIN_HOST = "https://haste.zneix.eu/";
|
||||||
|
|
||||||
|
private URL lastPasteUrl;
|
||||||
|
|
||||||
|
private final BiConsumer<CommandSource, URL> consumer;
|
||||||
|
private final boolean cache;
|
||||||
|
|
||||||
|
PasteHelper(BiConsumer<CommandSource, URL> consumer, boolean cache) {
|
||||||
|
this.consumer = consumer;
|
||||||
|
this.cache = cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send(PlayerBalancer plugin, CommandSource sender) {
|
||||||
|
if (lastPasteUrl == null || !cache) {
|
||||||
|
try {
|
||||||
|
lastPasteUrl = paste(plugin);
|
||||||
|
} catch (PasteException e) {
|
||||||
|
sender.sendMessage(text("An exception occurred while trying to send the paste: " + e.getMessage(), RED));
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
sender.sendMessage(text("An internal error occurred while attempting to perform this command", RED));
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sender.sendMessage(text("This is a cached link, reload the plugin for it to refresh!", RED));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastPasteUrl != null) {
|
||||||
|
consumer.accept(sender, lastPasteUrl);
|
||||||
|
} else {
|
||||||
|
sender.sendMessage(text("Could not create the paste, try again...", RED));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public URL getLastPasteURL() {
|
||||||
|
return lastPasteUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract URL paste(PlayerBalancer plugin) throws Exception;
|
||||||
|
|
||||||
|
public static void reset() {
|
||||||
|
for (PasteHelper helper : values()) {
|
||||||
|
helper.lastPasteUrl = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.helper;
|
||||||
|
|
||||||
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class PlayerLocker {
|
||||||
|
private static final Set<UUID> storage = Collections.synchronizedSet(new HashSet<UUID>());
|
||||||
|
|
||||||
|
public static boolean lock(Player player) {
|
||||||
|
if (storage.contains(player.getUniqueId())) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
storage.add(player.getUniqueId());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean unlock(Player player) {
|
||||||
|
if (storage.contains(player.getUniqueId())) {
|
||||||
|
storage.remove(player.getUniqueId());
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isLocked(Player player) {
|
||||||
|
return storage.contains(player.getUniqueId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void flush() {
|
||||||
|
storage.clear();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.listeners;
|
||||||
|
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.PlayerBalancer;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.connection.ServerAssignRegistry;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.helper.PlayerLocker;
|
||||||
|
import com.velocitypowered.api.event.Subscribe;
|
||||||
|
import com.velocitypowered.api.event.connection.DisconnectEvent;
|
||||||
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
|
||||||
|
public class PlayerDisconnectListener {
|
||||||
|
private final PlayerBalancer plugin;
|
||||||
|
|
||||||
|
public PlayerDisconnectListener(PlayerBalancer plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onDisconnect(DisconnectEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
PlayerLocker.unlock(player);
|
||||||
|
|
||||||
|
ServerAssignRegistry.clearAsssignedServers(player);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,269 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.listeners;
|
||||||
|
|
||||||
|
import com.google.common.io.ByteArrayDataInput;
|
||||||
|
import com.google.common.io.ByteStreams;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.JsonSerializer;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.PlayerBalancer;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.connection.ConnectionIntent;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.helper.PlayerLocker;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.ping.ServerStatus;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.section.ServerSection;
|
||||||
|
import com.velocitypowered.api.event.Subscribe;
|
||||||
|
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
||||||
|
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.ServerInfo;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class PluginMessageListener {
|
||||||
|
private final PlayerBalancer plugin;
|
||||||
|
private final Gson gson;
|
||||||
|
|
||||||
|
public PluginMessageListener(PlayerBalancer plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
GsonBuilder builder = new GsonBuilder();
|
||||||
|
|
||||||
|
// Only serialize the name of ServerInfo
|
||||||
|
builder.registerTypeAdapter(ServerInfo.class, (JsonSerializer<ServerInfo>) (server, type, context) ->
|
||||||
|
context.serialize(server.getName())
|
||||||
|
);
|
||||||
|
|
||||||
|
builder.serializeNulls();
|
||||||
|
gson = builder.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onPluginMessage(PluginMessageEvent event) {
|
||||||
|
if (event.getIdentifier().equals(PlayerBalancer.PB_CHANNEL) && event.getSource() instanceof ServerConnection) {
|
||||||
|
ByteArrayDataInput in = ByteStreams.newDataInput(event.getData());
|
||||||
|
String request = in.readUTF();
|
||||||
|
ServerConnection serverConnection = ((ServerConnection) event.getSource());
|
||||||
|
ServerInfo sender = serverConnection.getServerInfo();
|
||||||
|
|
||||||
|
switch (request) {
|
||||||
|
case "Connect": {
|
||||||
|
if (event.getTarget() instanceof Player) {
|
||||||
|
Player player = (Player) event.getTarget();
|
||||||
|
ServerSection section = plugin.getSectionManager().getByName(in.readUTF());
|
||||||
|
|
||||||
|
if (section == null)
|
||||||
|
break;
|
||||||
|
|
||||||
|
ConnectionIntent.simple(plugin, player, section);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "ConnectOther": {
|
||||||
|
Optional<Player> player = plugin.getProxyServer().getPlayer(in.readUTF());
|
||||||
|
|
||||||
|
if (!player.isPresent())
|
||||||
|
break;
|
||||||
|
|
||||||
|
ServerSection section = plugin.getSectionManager().getByName(in.readUTF());
|
||||||
|
|
||||||
|
if (section == null)
|
||||||
|
break;
|
||||||
|
|
||||||
|
ConnectionIntent.simple(plugin, player.get(), section);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "GetSectionByName": {
|
||||||
|
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream out = new DataOutputStream(stream);
|
||||||
|
|
||||||
|
ServerSection section = plugin.getSectionManager().getByName(in.readUTF());
|
||||||
|
|
||||||
|
if (section == null)
|
||||||
|
break;
|
||||||
|
|
||||||
|
try {
|
||||||
|
String output = gson.toJson(section);
|
||||||
|
out.writeUTF("GetSectionByName");
|
||||||
|
out.writeUTF(output);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
serverConnection.sendPluginMessage(PlayerBalancer.PB_CHANNEL, stream.toByteArray());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "GetSectionByServer": {
|
||||||
|
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream out = new DataOutputStream(stream);
|
||||||
|
|
||||||
|
Optional<RegisteredServer> server = plugin.getProxyServer().getServer(in.readUTF());
|
||||||
|
|
||||||
|
if (!server.isPresent())
|
||||||
|
break;
|
||||||
|
|
||||||
|
ServerSection section = plugin.getSectionManager().getByServer(server.get());
|
||||||
|
|
||||||
|
if (section == null)
|
||||||
|
break;
|
||||||
|
|
||||||
|
try {
|
||||||
|
String output = gson.toJson(section);
|
||||||
|
out.writeUTF("GetSectionByServer");
|
||||||
|
out.writeUTF(output);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
serverConnection.sendPluginMessage(PlayerBalancer.PB_CHANNEL, stream.toByteArray());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "GetSectionOfPlayer": {
|
||||||
|
if (event.getTarget() instanceof Player) {
|
||||||
|
Player player = (Player) event.getTarget();
|
||||||
|
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream out = new DataOutputStream(stream);
|
||||||
|
|
||||||
|
ServerSection section = plugin.getSectionManager().getByPlayer(player);
|
||||||
|
|
||||||
|
if (section == null)
|
||||||
|
break;
|
||||||
|
|
||||||
|
try {
|
||||||
|
String output = gson.toJson(section);
|
||||||
|
out.writeUTF("GetSectionOfPlayer");
|
||||||
|
out.writeUTF(output);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
serverConnection.sendPluginMessage(PlayerBalancer.PB_CHANNEL, stream.toByteArray());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "GetSectionPlayerCount": {
|
||||||
|
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream out = new DataOutputStream(stream);
|
||||||
|
|
||||||
|
ServerSection section = plugin.getSectionManager().getByName(in.readUTF());
|
||||||
|
|
||||||
|
if (section == null)
|
||||||
|
break;
|
||||||
|
|
||||||
|
try {
|
||||||
|
out.writeUTF("GetSectionPlayerCount");
|
||||||
|
out.writeInt(section.getServers().stream().reduce(
|
||||||
|
0,
|
||||||
|
(integer, serverInfo) -> integer + serverInfo.getPlayersConnected().size(),
|
||||||
|
Integer::sum));
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
serverConnection.sendPluginMessage(PlayerBalancer.PB_CHANNEL, stream.toByteArray());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "GetServerStatus": {
|
||||||
|
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream out = new DataOutputStream(stream);
|
||||||
|
|
||||||
|
Optional<RegisteredServer> server = plugin.getProxyServer().getServer(in.readUTF());
|
||||||
|
if (!server.isPresent())
|
||||||
|
break;
|
||||||
|
|
||||||
|
ServerStatus status = plugin.getStatusManager().getStatus(server.get().getServerInfo());
|
||||||
|
|
||||||
|
try {
|
||||||
|
String output = gson.toJson(status);
|
||||||
|
out.writeUTF("GetServerStatus");
|
||||||
|
out.writeUTF(output);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
serverConnection.sendPluginMessage(PlayerBalancer.PB_CHANNEL, stream.toByteArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
case "ClearPlayerBypass": {
|
||||||
|
if (event.getTarget() instanceof Player) {
|
||||||
|
Player player = (Player) event.getTarget();
|
||||||
|
PlayerLocker.unlock(player);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "SetPlayerBypass": {
|
||||||
|
if (event.getTarget() instanceof Player) {
|
||||||
|
Player player = (Player) event.getTarget();
|
||||||
|
PlayerLocker.lock(player);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "BypassConnect": {
|
||||||
|
if (event.getTarget() instanceof Player) {
|
||||||
|
Player player = (Player) event.getTarget();
|
||||||
|
|
||||||
|
Optional<RegisteredServer> server = plugin.getProxyServer().getServer(in.readUTF());
|
||||||
|
if (!server.isPresent())
|
||||||
|
break;
|
||||||
|
|
||||||
|
ConnectionIntent.direct(
|
||||||
|
plugin,
|
||||||
|
player,
|
||||||
|
server.get().getServerInfo(),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "FallbackPlayer": {
|
||||||
|
if (event.getTarget() instanceof Player) {
|
||||||
|
Player player = (Player) event.getTarget();
|
||||||
|
ServerSection target = plugin.getFallbackCommand().getSection(player);
|
||||||
|
|
||||||
|
if (target == null)
|
||||||
|
break;
|
||||||
|
|
||||||
|
ConnectionIntent.simple(
|
||||||
|
plugin,
|
||||||
|
player,
|
||||||
|
target
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "FallbackOtherPlayer": {
|
||||||
|
Optional<Player> player = plugin.getProxyServer().getPlayer(in.readUTF());
|
||||||
|
|
||||||
|
if (!player.isPresent())
|
||||||
|
break;
|
||||||
|
|
||||||
|
ServerSection target = plugin.getFallbackCommand().getSection(player.get());
|
||||||
|
|
||||||
|
if (target == null)
|
||||||
|
break;
|
||||||
|
|
||||||
|
ConnectionIntent.simple(
|
||||||
|
plugin,
|
||||||
|
player.get(),
|
||||||
|
target
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.listeners;
|
||||||
|
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.PlayerBalancer;
|
||||||
|
import com.velocitypowered.api.event.Subscribe;
|
||||||
|
import com.velocitypowered.api.event.proxy.ProxyReloadEvent;
|
||||||
|
|
||||||
|
public class ProxyReloadListener {
|
||||||
|
private final PlayerBalancer plugin;
|
||||||
|
|
||||||
|
public ProxyReloadListener(PlayerBalancer plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onReload(ProxyReloadEvent event) {
|
||||||
|
plugin.getLogger().info("Velocity has been reloaded, reloading the plugin...");
|
||||||
|
plugin.reloadPlugin();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.listeners;
|
||||||
|
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.PlayerBalancer;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.connection.ConnectionIntent;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.connection.ServerAssignRegistry;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.helper.PlayerLocker;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.section.ServerSection;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.utils.MessageUtils;
|
||||||
|
import com.velocitypowered.api.event.Subscribe;
|
||||||
|
import com.velocitypowered.api.event.player.ServerPreConnectEvent;
|
||||||
|
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.ServerInfo;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public class ServerConnectListener {
|
||||||
|
private final PlayerBalancer plugin;
|
||||||
|
|
||||||
|
public ServerConnectListener(PlayerBalancer plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onConnect(ServerPreConnectEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
RegisteredServer target = event.getOriginalServer();
|
||||||
|
|
||||||
|
if (PlayerLocker.isLocked(player))
|
||||||
|
return;
|
||||||
|
|
||||||
|
ServerSection section = getSection(player, target);
|
||||||
|
|
||||||
|
if (section == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
new ConnectionIntent(plugin, player, section) {
|
||||||
|
@Override
|
||||||
|
public void connect(ServerInfo server, Consumer<Boolean> callback) {
|
||||||
|
if (plugin.getSectionManager().isReiterative(section)) {
|
||||||
|
ServerAssignRegistry.assignTarget(player, section, server);
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin.getProxyServer().getServer(server.getName()).ifPresent(registeredServer -> {
|
||||||
|
event.setResult(ServerPreConnectEvent.ServerResult.allowed(registeredServer));
|
||||||
|
callback.accept(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ServerSection getSection(Player player, RegisteredServer target) {
|
||||||
|
ServerSection section = plugin.getSectionManager().getByServer(target);
|
||||||
|
|
||||||
|
if (section != null) {
|
||||||
|
// Checks only for servers (not the section server)
|
||||||
|
if (!target.equals(section.getServer())) {
|
||||||
|
if (plugin.getSectionManager().isDummy(section)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player.hasPermission("playerbalancer.bypass")) {
|
||||||
|
MessageUtils.send(player, plugin.getSettings().getMessagesProps().getBypassMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerConnection serverConnection = player.getCurrentServer().orElse(null);
|
||||||
|
if (serverConnection != null && section.getServers().contains(serverConnection.getServer())) {
|
||||||
|
if (plugin.getSectionManager().isReiterative(section)) {
|
||||||
|
ServerAssignRegistry.assignTarget(player, section, target.getServerInfo());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return section;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,148 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.listeners;
|
||||||
|
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.PlayerBalancer;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.connection.ConnectionIntent;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.helper.PlayerLocker;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.section.ServerSection;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.settings.props.MessagesProps;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.settings.props.features.KickHandlerProps;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.utils.MessageUtils;
|
||||||
|
import com.velocitypowered.api.event.Subscribe;
|
||||||
|
import com.velocitypowered.api.event.player.KickedFromServerEvent;
|
||||||
|
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.ServerInfo;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public class ServerKickListener {
|
||||||
|
private final KickHandlerProps props;
|
||||||
|
private final MessagesProps messages;
|
||||||
|
private final PlayerBalancer plugin;
|
||||||
|
|
||||||
|
public ServerKickListener(PlayerBalancer plugin) {
|
||||||
|
this.props = plugin.getSettings().getFeaturesProps().getKickHandlerProps();
|
||||||
|
this.messages = plugin.getSettings().getMessagesProps();
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onKick(KickedFromServerEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
RegisteredServer from = event.getServer();
|
||||||
|
|
||||||
|
boolean matches = false;
|
||||||
|
String reason = PlainTextComponentSerializer.plainText().serialize(event.getServerKickReason().orElse(Component.empty()));
|
||||||
|
|
||||||
|
for (String string : props.getReasons()) {
|
||||||
|
if (reason.matches(string)) {
|
||||||
|
matches = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.isInverted()) {
|
||||||
|
matches = !matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.isDebug()) {
|
||||||
|
plugin.getLogger().info(String.format("The player %s got kicked from %s, reason: \"%s\". Matched reasons: %s",
|
||||||
|
player.getUsername(),
|
||||||
|
from.getServerInfo().getName(),
|
||||||
|
reason,
|
||||||
|
matches
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!matches)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ServerSection section = getSection(player, from);
|
||||||
|
|
||||||
|
if (section == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ConnectionIntent intent = new ConnectionIntent(plugin, player, section) {
|
||||||
|
@Override
|
||||||
|
public void connect(ServerInfo server, Consumer<Boolean> callback) {
|
||||||
|
PlayerLocker.lock(player);
|
||||||
|
Optional<RegisteredServer> registeredServer = plugin.getProxyServer().getServer(server.getName());
|
||||||
|
if (registeredServer.isPresent()) {
|
||||||
|
event.setResult(KickedFromServerEvent.RedirectPlayer.create(registeredServer.get()));
|
||||||
|
MessageUtils.send(player, messages.getKickMessage(), (str) -> str
|
||||||
|
.replace("{reason}", reason)
|
||||||
|
.replace("{from}", from.getServerInfo().getName())
|
||||||
|
.replace("{to}", server.getName()));
|
||||||
|
callback.accept(true);
|
||||||
|
}
|
||||||
|
plugin.getProxyServer().getScheduler().buildTask(plugin, () -> {
|
||||||
|
PlayerLocker.unlock(player);
|
||||||
|
}).delay(5, TimeUnit.SECONDS).schedule();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
intent.getExclusions().add(from.getServerInfo());
|
||||||
|
intent.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ServerSection getSection(Player player, RegisteredServer from) {
|
||||||
|
Optional<ServerConnection> serverConnection = player.getCurrentServer();
|
||||||
|
if (!serverConnection.isPresent()) {
|
||||||
|
if (props.isForcePrincipal()) {
|
||||||
|
return plugin.getSectionManager().getPrincipal();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!serverConnection.get().getServer().equals(from)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerSection current = plugin.getSectionManager().getByServer(from);
|
||||||
|
|
||||||
|
if (current != null) {
|
||||||
|
if (props.getExcludedSections().contains(current.getName())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current != null) {
|
||||||
|
ServerSection target = current.getParent();
|
||||||
|
|
||||||
|
String bindName = props.getRules().get(current.getName());
|
||||||
|
if (bindName != null) {
|
||||||
|
ServerSection bind = plugin.getSectionManager().getByName(bindName);
|
||||||
|
if (bind != null) {
|
||||||
|
target = bind;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target == null) {
|
||||||
|
MessageUtils.send(player, messages.getUnavailableServerMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.isRestrictive()) {
|
||||||
|
if (current.getPosition() >= 0 && target.getPosition() < 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return target;
|
||||||
|
} else {
|
||||||
|
if (plugin.getSettings().getFeaturesProps().getBalancerProps().isDefaultPrincipal()) {
|
||||||
|
return plugin.getSectionManager().getPrincipal();
|
||||||
|
} else {
|
||||||
|
MessageUtils.send(player, messages.getUnavailableServerMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.ping;
|
||||||
|
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.PlayerBalancer;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.utils.ServerListPing;
|
||||||
|
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||||
|
import com.velocitypowered.api.proxy.server.ServerPing;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public enum PingTactic {
|
||||||
|
CUSTOM {
|
||||||
|
@Override
|
||||||
|
public void ping(RegisteredServer server, Consumer<ServerStatus> callback, PlayerBalancer plugin) {
|
||||||
|
plugin.getProxyServer().getScheduler().buildTask(plugin, () -> {
|
||||||
|
try {
|
||||||
|
ServerListPing utility = new ServerListPing();
|
||||||
|
ServerListPing.StatusResponse response = utility.ping(
|
||||||
|
server.getServerInfo().getAddress(),
|
||||||
|
plugin.getSettings().getFeaturesProps().getServerCheckerProps().getTimeout());
|
||||||
|
callback.accept(new ServerStatus(
|
||||||
|
response.getDescription(),
|
||||||
|
response.getPlayers().getOnline(),
|
||||||
|
response.getPlayers().getMax()));
|
||||||
|
} catch (IOException e) {
|
||||||
|
callback.accept(null);
|
||||||
|
}
|
||||||
|
}).schedule();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
GENERIC {
|
||||||
|
@Override
|
||||||
|
public void ping(RegisteredServer server, Consumer<ServerStatus> callback, PlayerBalancer plugin) {
|
||||||
|
try {
|
||||||
|
server.ping().whenComplete((ping, throwable) -> {
|
||||||
|
if (ping != null) {
|
||||||
|
// Using deprecated method for bungee 1.8 compatibility
|
||||||
|
callback.accept(new ServerStatus(
|
||||||
|
ping.getDescriptionComponent(),
|
||||||
|
ping.getPlayers().map(ServerPing.Players::getOnline).orElse(0),
|
||||||
|
ping.getPlayers().map(ServerPing.Players::getMax).orElse(0)
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
callback.accept(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
callback.accept(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public abstract void ping(RegisteredServer server, Consumer<ServerStatus> callback, PlayerBalancer plugin);
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.ping;
|
||||||
|
|
||||||
|
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
|
||||||
|
public class ServerStatus {
|
||||||
|
private final Component description;
|
||||||
|
private final int players, maximum;
|
||||||
|
private final boolean online;
|
||||||
|
private final boolean fabricated;
|
||||||
|
private boolean outdated = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor when cannot ping the server
|
||||||
|
*/
|
||||||
|
public ServerStatus() {
|
||||||
|
this.description = Component.text("Server Unreachable");
|
||||||
|
this.players = 0;
|
||||||
|
this.maximum = 0;
|
||||||
|
this.online = false;
|
||||||
|
this.fabricated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor when we have to return defaults
|
||||||
|
* Defaulting to be accessible as this is used when the server checker is disabled
|
||||||
|
* @param server the server for providing basic info about itself
|
||||||
|
*/
|
||||||
|
public ServerStatus(ServerInfo server) {
|
||||||
|
this.description = Component.text(server.getName());
|
||||||
|
this.players = 0;
|
||||||
|
this.maximum = Integer.MAX_VALUE;
|
||||||
|
this.online = true;
|
||||||
|
this.fabricated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor when we have to store ping results
|
||||||
|
* @param description the description (aka MOTD) from the ping result
|
||||||
|
* @param players the count of players online from the ping result
|
||||||
|
* @param maximum the maximum amount of players possible from the ping result
|
||||||
|
*/
|
||||||
|
public ServerStatus(Component description, int players, int maximum) {
|
||||||
|
this.description = description;
|
||||||
|
this.players = players;
|
||||||
|
this.maximum = maximum;
|
||||||
|
this.online = maximum != 0 && players < maximum;
|
||||||
|
this.fabricated = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Component getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPlayers() {
|
||||||
|
return players;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaximum() {
|
||||||
|
return maximum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOnline() {
|
||||||
|
return online;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFabricated() {
|
||||||
|
return fabricated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOutdated() {
|
||||||
|
return outdated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOutdated(boolean outdated) {
|
||||||
|
this.outdated = outdated;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,153 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.ping;
|
||||||
|
|
||||||
|
import com.google.common.io.ByteArrayDataInput;
|
||||||
|
import com.google.common.io.ByteStreams;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.PlayerBalancer;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.section.ServerSection;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.settings.props.features.ServerCheckerProps;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.utils.RegExUtils;
|
||||||
|
import com.velocitypowered.api.event.Subscribe;
|
||||||
|
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
||||||
|
import com.velocitypowered.api.proxy.ServerConnection;
|
||||||
|
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||||
|
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||||
|
import com.velocitypowered.api.scheduler.ScheduledTask;
|
||||||
|
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class StatusManager {
|
||||||
|
private final PlayerBalancer plugin;
|
||||||
|
private final ServerCheckerProps props;
|
||||||
|
|
||||||
|
private boolean stopped = true;
|
||||||
|
private PingTactic tactic;
|
||||||
|
private ScheduledTask task;
|
||||||
|
|
||||||
|
private final Map<ServerInfo, ServerStatus> storage = new HashMap<>();
|
||||||
|
private final Map<ServerInfo, Boolean> overriders = new HashMap<>();
|
||||||
|
|
||||||
|
public StatusManager(PlayerBalancer plugin) {
|
||||||
|
this.props = plugin.getSettings().getFeaturesProps().getServerCheckerProps();
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
if (task != null) {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
stopped = false;
|
||||||
|
tactic = props.getTactic();
|
||||||
|
plugin.getLogger().info(String.format("Starting the ping task, the interval is %s",
|
||||||
|
props.getInterval()
|
||||||
|
));
|
||||||
|
|
||||||
|
task = plugin.getProxyServer().getScheduler().buildTask(plugin, () -> {
|
||||||
|
storage.forEach((k, v) -> v.setOutdated(true));
|
||||||
|
|
||||||
|
for (ServerSection section : plugin.getSectionManager().getSections().values()) {
|
||||||
|
for (RegisteredServer server : section.getServers()) {
|
||||||
|
if (stopped) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getStatus(server.getServerInfo()).isOutdated()) {
|
||||||
|
update(server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).repeat(props.getInterval(), TimeUnit.MILLISECONDS).schedule();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
if (task != null) {
|
||||||
|
task.cancel();
|
||||||
|
task = null;
|
||||||
|
stopped = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void update(RegisteredServer server) {
|
||||||
|
tactic.ping(server, (status) -> {
|
||||||
|
if (status == null) {
|
||||||
|
status = new ServerStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.isDebug()) {
|
||||||
|
plugin.getLogger().info(String.format(
|
||||||
|
"Updated server %s, status: [Description: \"%s\", Players: %s, Maximum Players: %s, Online: %s]",
|
||||||
|
server.getServerInfo().getName(), status.getDescription(), status.getPlayers(), status.getMaximum(), status.isOnline()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
status.setOutdated(false);
|
||||||
|
storage.put(server.getServerInfo(), status);
|
||||||
|
}, plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerStatus getStatus(ServerInfo server) {
|
||||||
|
ServerStatus status = storage.get(server);
|
||||||
|
|
||||||
|
if (status == null) {
|
||||||
|
return new ServerStatus(server);
|
||||||
|
} else {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAccessible(ServerInfo server) {
|
||||||
|
Boolean override = overriders.get(server);
|
||||||
|
|
||||||
|
if (override != null) {
|
||||||
|
return override;
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerStatus status = getStatus(server);
|
||||||
|
|
||||||
|
if (!status.isOnline()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String pattern : props.getMarkerDescs()) {
|
||||||
|
if (RegExUtils.matches(LegacyComponentSerializer.legacyAmpersand().serialize(status.getDescription()), pattern)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onPluginMessage(PluginMessageEvent event) {
|
||||||
|
if (event.getIdentifier().equals(PlayerBalancer.PB_CHANNEL) && event.getSource() instanceof ServerConnection) {
|
||||||
|
ByteArrayDataInput in = ByteStreams.newDataInput(event.getData());
|
||||||
|
String request = in.readUTF();
|
||||||
|
ServerInfo sender = ((ServerConnection) event.getSource()).getServerInfo();
|
||||||
|
|
||||||
|
switch (request) {
|
||||||
|
case "ClearStatusOverride": {
|
||||||
|
Optional<RegisteredServer> server = plugin.getProxyServer().getServer(in.readUTF());
|
||||||
|
|
||||||
|
if (!server.isPresent())
|
||||||
|
break;
|
||||||
|
|
||||||
|
overriders.remove(server.get().getServerInfo());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "SetStatusOverride": {
|
||||||
|
Optional<RegisteredServer> server = plugin.getProxyServer().getServer(in.readUTF());
|
||||||
|
|
||||||
|
if (!server.isPresent())
|
||||||
|
break;
|
||||||
|
|
||||||
|
overriders.put(server.get().getServerInfo(), in.readBoolean());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.section;
|
||||||
|
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.PlayerBalancer;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.commands.FallbackCommand;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.settings.props.shared.CommandProps;
|
||||||
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
|
||||||
|
public class SectionCommand extends FallbackCommand {
|
||||||
|
private final ServerSection section;
|
||||||
|
private final String permission;
|
||||||
|
|
||||||
|
public SectionCommand(PlayerBalancer plugin, ServerSection section) {
|
||||||
|
super(plugin);
|
||||||
|
this.section = section;
|
||||||
|
this.permission = getCommandProps().getPermission();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasPermission(Invocation invocation) {
|
||||||
|
return invocation.source().hasPermission(permission);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ServerSection getSection(Player player) {
|
||||||
|
return section;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandProps getCommandProps() {
|
||||||
|
return section.getProps().getCommandProps();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,396 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.section;
|
||||||
|
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.PlayerBalancer;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.settings.props.features.BalancerProps;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.settings.props.shared.SectionProps;
|
||||||
|
import com.velocitypowered.api.command.CommandMeta;
|
||||||
|
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.ServerInfo;
|
||||||
|
import com.velocitypowered.api.scheduler.ScheduledTask;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class SectionManager {
|
||||||
|
private final PlayerBalancer plugin;
|
||||||
|
private final BalancerProps props;
|
||||||
|
@Getter
|
||||||
|
private ServerSection principal;
|
||||||
|
private ScheduledTask refreshTask;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final Map<String, ServerSection> sections = Collections.synchronizedMap(new HashMap<>());
|
||||||
|
@Getter
|
||||||
|
private final Map<RegisteredServer, ServerSection> servers = Collections.synchronizedMap(new HashMap<>());
|
||||||
|
|
||||||
|
private static final Map<String, Stage> stages = Collections.synchronizedMap(new LinkedHashMap<>());
|
||||||
|
|
||||||
|
public SectionManager(PlayerBalancer plugin) {
|
||||||
|
this.props = plugin.getSettings().getFeaturesProps().getBalancerProps();
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void load() throws RuntimeException {
|
||||||
|
plugin.getLogger().info("Executing section initialization stages, this may take a while...");
|
||||||
|
long starting = System.currentTimeMillis();
|
||||||
|
|
||||||
|
stages.put("constructing-sections", new SectionStage("Constructing sections") {
|
||||||
|
@Override
|
||||||
|
public void execute(String sectionName, SectionProps sectionProps, ServerSection section) throws RuntimeException {
|
||||||
|
ServerSection object = new ServerSection(sectionName, sectionProps);
|
||||||
|
sections.put(sectionName, object);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stages.put("processing-principal-section", new Stage("Processing principal section") {
|
||||||
|
@Override
|
||||||
|
public void execute() {
|
||||||
|
principal = sections.get(props.getPrincipalSectionName());
|
||||||
|
if (principal == null) {
|
||||||
|
throw new IllegalArgumentException(String.format(
|
||||||
|
"Could not set principal section, there is no section called \"%s\"",
|
||||||
|
props.getPrincipalSectionName()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stages.put("processing-parents", new SectionStage("Processing parents") {
|
||||||
|
@Override
|
||||||
|
public void execute(String sectionName, SectionProps sectionProps, ServerSection section) throws RuntimeException {
|
||||||
|
if (sectionProps.getParentName() != null) {
|
||||||
|
ServerSection parent = getByName(sectionProps.getParentName());
|
||||||
|
if (principal.equals(section) && parent == null) {
|
||||||
|
throw new IllegalArgumentException(String.format(
|
||||||
|
"The section \"%s\" has an invalid parent set",
|
||||||
|
section.getName()
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
section.setParent(parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stages.put("validating-parents", new SectionStage("Validating parents") {
|
||||||
|
@Override
|
||||||
|
public void execute(String sectionName, SectionProps sectionProps, ServerSection section) throws RuntimeException {
|
||||||
|
ServerSection parent = section.getParent();
|
||||||
|
if (parent != null && parent.getParent() == section) {
|
||||||
|
throw new IllegalStateException(String.format(
|
||||||
|
"The sections \"%s\" and \"%s\" are parents of each other",
|
||||||
|
section.getName(),
|
||||||
|
parent.getName()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stages.put("validating-providers", new SectionStage("Validating providers") {
|
||||||
|
@Override
|
||||||
|
public void execute(String sectionName, SectionProps sectionProps, ServerSection section) throws RuntimeException {
|
||||||
|
if (sectionProps.getProvider() == null) {
|
||||||
|
ServerSection current = section.getParent();
|
||||||
|
if (current != null) {
|
||||||
|
while (current.getProps().getProvider() == null) {
|
||||||
|
current = current.getParent();
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin.getLogger().info(String.format(
|
||||||
|
"The section \"%s\" inherits the provider from the section \"%s\"",
|
||||||
|
section.getName(),
|
||||||
|
current.getName()
|
||||||
|
));
|
||||||
|
|
||||||
|
section.setInherited(true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
section.setInherited(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stages.put("calculating-positions", new SectionStage("Calculating positions") {
|
||||||
|
@Override
|
||||||
|
public void execute(String sectionName, SectionProps sectionProps, ServerSection section) throws RuntimeException {
|
||||||
|
section.setPosition(calculatePosition(section));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stages.put("resolving-servers", new SectionStage("Resolving servers") {
|
||||||
|
@Override
|
||||||
|
public void execute(String sectionName, SectionProps sectionProps, ServerSection section) throws RuntimeException {
|
||||||
|
calculateServers(section);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stages.put("section-server-processing", new SectionStage("Section server processing") {
|
||||||
|
@Override
|
||||||
|
public void execute(String sectionName, SectionProps sectionProps, ServerSection section) throws RuntimeException {
|
||||||
|
if (sectionProps.getServerName() != null) {
|
||||||
|
ServerInfo serverInfo = new ServerInfo("@" + sectionProps.getServerName(), new InetSocketAddress("0.0.0.0", (int) Math.floor(Math.random() * (0xFFFF + 1))));
|
||||||
|
RegisteredServer sectionServer = plugin.getProxyServer().registerServer(serverInfo);
|
||||||
|
plugin.getSectionManager().registerServer(sectionServer, section);
|
||||||
|
section.setServer(sectionServer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stages.put("section-command-processing", new SectionStage("Section command processing") {
|
||||||
|
@Override
|
||||||
|
public void execute(String sectionName, SectionProps sectionProps, ServerSection section) throws RuntimeException {
|
||||||
|
if (sectionProps.getCommandProps() != null) {
|
||||||
|
SectionCommand command = new SectionCommand(plugin, section);
|
||||||
|
section.setCommand(command);
|
||||||
|
CommandMeta sectionCommandMeta = plugin.getProxyServer().getCommandManager()
|
||||||
|
.metaBuilder(command.getCommandProps().getName())
|
||||||
|
.aliases(command.getCommandProps().getAliasesArray())
|
||||||
|
.build();
|
||||||
|
plugin.getProxyServer().getCommandManager().register(sectionCommandMeta, command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stages.put("finishing-loading", new SectionStage("Finishing loading sections") {
|
||||||
|
@Override
|
||||||
|
public void execute(String sectionName, SectionProps sectionProps, ServerSection section) throws RuntimeException {
|
||||||
|
section.setValid(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stages.forEach((name, stage) -> {
|
||||||
|
plugin.getLogger().info("Executing stage \"" + stage.title + "\"");
|
||||||
|
stage.execute();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (plugin.getSettings().getFeaturesProps().getServerRefreshProps().isEnabled()) {
|
||||||
|
plugin.getLogger().info("Starting automatic server refresh task");
|
||||||
|
refreshTask = plugin.getProxyServer().getScheduler().buildTask(plugin, () -> {
|
||||||
|
props.getSectionProps().forEach((name, props) -> {
|
||||||
|
ServerSection section = sections.get(name);
|
||||||
|
calculateServers(section);
|
||||||
|
});
|
||||||
|
}).delay(plugin.getSettings().getFeaturesProps().getServerRefreshProps().getDelay(), TimeUnit.MILLISECONDS)
|
||||||
|
.repeat(plugin.getSettings().getFeaturesProps().getServerRefreshProps().getInterval(), TimeUnit.MILLISECONDS)
|
||||||
|
.schedule();
|
||||||
|
}
|
||||||
|
|
||||||
|
long ending = System.currentTimeMillis() - starting;
|
||||||
|
plugin.getLogger().info(String.format("A total of %s section(s) have been loaded in %sms", sections.size(), ending));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void flush() {
|
||||||
|
plugin.getLogger().info("Flushing section storage because of plugin shutdown");
|
||||||
|
sections.forEach((key, value) -> {
|
||||||
|
value.setValid(false);
|
||||||
|
|
||||||
|
if (value.getCommand() != null) {
|
||||||
|
SectionCommand command = value.getCommand();
|
||||||
|
plugin.getProxyServer().getCommandManager().unregister(command.getCommandProps().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.getServer() != null) {
|
||||||
|
ServerInfo server = value.getServer().getServerInfo();
|
||||||
|
|
||||||
|
plugin.getProxyServer().unregisterServer(server);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (refreshTask != null) {
|
||||||
|
refreshTask.cancel();
|
||||||
|
refreshTask = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
principal = null;
|
||||||
|
sections.clear();
|
||||||
|
servers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerSection getByName(String name) {
|
||||||
|
if (name == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sections.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerSection getByServer(RegisteredServer server) {
|
||||||
|
if (server == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return servers.get(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerSection getByPlayer(Player player) {
|
||||||
|
if (player == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<ServerConnection> server = player.getCurrentServer();
|
||||||
|
|
||||||
|
return server.map(serverConnection -> getByServer(serverConnection.getServer())).orElse(null);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerServer(RegisteredServer server, ServerSection section) {
|
||||||
|
if (!isDummy(section)) {
|
||||||
|
// Checking for already we already added this server to other section
|
||||||
|
// This can only happen if another non dummy section registers this server
|
||||||
|
if (servers.containsKey(server)) {
|
||||||
|
ServerSection other = servers.get(server);
|
||||||
|
throw new IllegalArgumentException(String.format(
|
||||||
|
"The server \"%s\" is already in the section \"%s\"",
|
||||||
|
server.getServerInfo().getName(),
|
||||||
|
other.getName()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin.getLogger().info(String.format("Registering server \"%s\" to section \"%s\"",
|
||||||
|
server.getServerInfo().getName(),
|
||||||
|
section.getName()
|
||||||
|
));
|
||||||
|
|
||||||
|
servers.put(server, section);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void calculateServers(ServerSection section) {
|
||||||
|
Set<RegisteredServer> results = new HashSet<>();
|
||||||
|
|
||||||
|
// Searches for matches
|
||||||
|
section.getProps().getServerEntries().forEach(entry -> {
|
||||||
|
Pattern pattern = Pattern.compile(entry);
|
||||||
|
plugin.getProxyServer().getAllServers().forEach((server) -> {
|
||||||
|
Matcher matcher = pattern.matcher(server.getServerInfo().getName());
|
||||||
|
if (matcher.matches()) {
|
||||||
|
results.add(server);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Checks if there are servers previously matched that are no longer valid
|
||||||
|
section.getServers().forEach(server -> {
|
||||||
|
ServerInfo info = server.getServerInfo();
|
||||||
|
if (!results.contains(server)) {
|
||||||
|
servers.remove(info);
|
||||||
|
section.getServers().remove(server);
|
||||||
|
plugin.getLogger().info(String.format("Removed the server %s from %s as it does no longer exist",
|
||||||
|
info.getName(), section.getName()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add matched servers to the section
|
||||||
|
int addedServers = 0;
|
||||||
|
for (RegisteredServer server : results) {
|
||||||
|
if (!section.getServers().contains(server)) {
|
||||||
|
section.getServers().add(server);
|
||||||
|
registerServer(server, section);
|
||||||
|
addedServers++;
|
||||||
|
plugin.getLogger().info(String.format("Added the server %s to %s",
|
||||||
|
server.getServerInfo().getName(), section.getName()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addedServers > 0) {
|
||||||
|
plugin.getLogger().info(String.format("Recognized %s server%s in the section \"%s\"",
|
||||||
|
addedServers,
|
||||||
|
addedServers != 1 ? "s" : "",
|
||||||
|
section.getName()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int calculatePosition(ServerSection section) {
|
||||||
|
ServerSection current = section;
|
||||||
|
|
||||||
|
// Calculate above principal
|
||||||
|
int iterations = 0;
|
||||||
|
while (current != null) {
|
||||||
|
if (current == principal) {
|
||||||
|
return iterations;
|
||||||
|
}
|
||||||
|
|
||||||
|
current = current.getParent();
|
||||||
|
iterations++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate below principal
|
||||||
|
if (principal != null) {
|
||||||
|
iterations = 0;
|
||||||
|
current = principal;
|
||||||
|
while (current != null) {
|
||||||
|
if (current.equals(section)) {
|
||||||
|
return iterations;
|
||||||
|
}
|
||||||
|
|
||||||
|
current = current.getParent();
|
||||||
|
iterations--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return iterations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPrincipal(ServerSection section) {
|
||||||
|
return principal.equals(section);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDummy(ServerSection section) {
|
||||||
|
List<String> dummySections = props.getDummySectionNames();
|
||||||
|
return dummySections.contains(section.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isReiterative(ServerSection section) {
|
||||||
|
List<String> reiterativeSections = props.getReiterativeSectionNames();
|
||||||
|
return reiterativeSections.contains(section.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stage getStage(String name) {
|
||||||
|
return stages.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private abstract class Stage {
|
||||||
|
private final String title;
|
||||||
|
|
||||||
|
private Stage(String title) {
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void execute() throws RuntimeException;
|
||||||
|
}
|
||||||
|
|
||||||
|
private abstract class SectionStage extends Stage {
|
||||||
|
private SectionStage(String title) {
|
||||||
|
super(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute() throws RuntimeException {
|
||||||
|
props.getSectionProps().forEach((name, props) -> {
|
||||||
|
execute(name, props, sections.get(name));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void execute(
|
||||||
|
String sectionName,
|
||||||
|
SectionProps sectionProps,
|
||||||
|
ServerSection section
|
||||||
|
) throws RuntimeException;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.section;
|
||||||
|
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.connection.ProviderType;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.connection.provider.AbstractProvider;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.settings.props.shared.SectionProps;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.utils.AlphanumComparator;
|
||||||
|
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class ServerSection {
|
||||||
|
private final String name;
|
||||||
|
private final SectionProps props;
|
||||||
|
|
||||||
|
private boolean inherited = false;
|
||||||
|
private ServerSection parent;
|
||||||
|
private int position;
|
||||||
|
|
||||||
|
private transient RegisteredServer server;
|
||||||
|
private transient SectionCommand command;
|
||||||
|
private transient AbstractProvider externalProvider;
|
||||||
|
private Set<RegisteredServer> servers;
|
||||||
|
|
||||||
|
private boolean valid = false;
|
||||||
|
|
||||||
|
public ServerSection(String name, SectionProps props) {
|
||||||
|
this.name = name;
|
||||||
|
this.props = props;
|
||||||
|
|
||||||
|
this.servers = Collections.synchronizedNavigableSet(new TreeSet<>((lhs, rhs) ->
|
||||||
|
AlphanumComparator.getInstance().compare(lhs.getServerInfo().getName(), rhs.getServerInfo().getName())
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProviderType getImplicitProvider() {
|
||||||
|
if (inherited) {
|
||||||
|
return parent.getImplicitProvider();
|
||||||
|
} else {
|
||||||
|
return props.getProvider();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.settings;
|
||||||
|
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.settings.props.FeaturesProps;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.settings.props.GeneralProps;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.settings.props.MessagesProps;
|
||||||
|
import lombok.Data;
|
||||||
|
import ninja.leaping.configurate.objectmapping.Setting;
|
||||||
|
import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable;
|
||||||
|
|
||||||
|
@ConfigSerializable
|
||||||
|
@Data
|
||||||
|
public class SettingsHolder {
|
||||||
|
@Setting(value = "general")
|
||||||
|
private GeneralProps generalProps;
|
||||||
|
|
||||||
|
@Setting(value = "messages")
|
||||||
|
private MessagesProps messagesProps;
|
||||||
|
|
||||||
|
@Setting(value = "features")
|
||||||
|
private FeaturesProps featuresProps;
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.settings.props;
|
||||||
|
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.settings.props.features.BalancerProps;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.settings.props.features.FallbackCommandProps;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.settings.props.features.KickHandlerProps;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.settings.props.features.PermissionRouterProps;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.settings.props.features.ServerCheckerProps;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.settings.props.features.ServerRefreshProps;
|
||||||
|
import lombok.Data;
|
||||||
|
import ninja.leaping.configurate.objectmapping.Setting;
|
||||||
|
import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable;
|
||||||
|
|
||||||
|
@ConfigSerializable
|
||||||
|
@Data
|
||||||
|
public class FeaturesProps {
|
||||||
|
@Setting(value = "balancer")
|
||||||
|
private BalancerProps balancerProps;
|
||||||
|
|
||||||
|
@Setting(value = "fallback-command")
|
||||||
|
private FallbackCommandProps fallbackCommandProps;
|
||||||
|
|
||||||
|
@Setting(value = "server-checker")
|
||||||
|
private ServerCheckerProps serverCheckerProps;
|
||||||
|
|
||||||
|
@Setting(value = "kick-handler")
|
||||||
|
private KickHandlerProps kickHandlerProps;
|
||||||
|
|
||||||
|
@Setting(value = "server-refresh")
|
||||||
|
private ServerRefreshProps serverRefreshProps;
|
||||||
|
|
||||||
|
@Setting(value = "permission-router")
|
||||||
|
private PermissionRouterProps permissionRouterProps;
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.settings.props;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import ninja.leaping.configurate.objectmapping.Setting;
|
||||||
|
import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable;
|
||||||
|
|
||||||
|
@ConfigSerializable
|
||||||
|
@Data
|
||||||
|
public class GeneralProps {
|
||||||
|
@Setting
|
||||||
|
private boolean enabled;
|
||||||
|
|
||||||
|
@Setting
|
||||||
|
private boolean silent;
|
||||||
|
|
||||||
|
@Setting(value = "auto-reload")
|
||||||
|
private boolean autoReload;
|
||||||
|
|
||||||
|
@Setting(value = "plugin-messaging")
|
||||||
|
private boolean pluginMessaging;
|
||||||
|
|
||||||
|
@Setting(value = "redis-bungee")
|
||||||
|
private boolean redisBungee;
|
||||||
|
|
||||||
|
@Setting
|
||||||
|
private String version;
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.settings.props;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import ninja.leaping.configurate.objectmapping.Setting;
|
||||||
|
import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable;
|
||||||
|
|
||||||
|
@ConfigSerializable
|
||||||
|
@Data
|
||||||
|
public class MessagesProps {
|
||||||
|
@Setting(value = "connecting-server")
|
||||||
|
private String connectingMessage;
|
||||||
|
|
||||||
|
@Setting(value = "connected-server")
|
||||||
|
private String connectedMessage;
|
||||||
|
|
||||||
|
@Setting(value = "misc-failure")
|
||||||
|
private String failureMessage;
|
||||||
|
|
||||||
|
@Setting(value = "unknown-section")
|
||||||
|
private String unknownSectionMessage;
|
||||||
|
|
||||||
|
@Setting(value = "invalid-input")
|
||||||
|
private String invalidInputMessage;
|
||||||
|
|
||||||
|
@Setting(value = "unavailable-server")
|
||||||
|
private String unavailableServerMessage;
|
||||||
|
|
||||||
|
@Setting(value = "player-kicked")
|
||||||
|
private String kickMessage;
|
||||||
|
|
||||||
|
@Setting(value = "player-bypass")
|
||||||
|
private String bypassMessage;
|
||||||
|
|
||||||
|
@Setting(value = "same-section")
|
||||||
|
private String sameSectionMessage;
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.settings.props.features;
|
||||||
|
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.settings.props.shared.SectionProps;
|
||||||
|
import lombok.Data;
|
||||||
|
import ninja.leaping.configurate.objectmapping.Setting;
|
||||||
|
import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ConfigSerializable
|
||||||
|
@Data
|
||||||
|
public class BalancerProps {
|
||||||
|
@Setting(value = "principal-section")
|
||||||
|
private String principalSectionName;
|
||||||
|
|
||||||
|
@Setting(value = "default-principal")
|
||||||
|
private boolean defaultPrincipal;
|
||||||
|
|
||||||
|
@Setting(value = "dummy-sections")
|
||||||
|
private List<String> dummySectionNames;
|
||||||
|
|
||||||
|
@Setting(value = "reiterative-sections")
|
||||||
|
private List<String> reiterativeSectionNames;
|
||||||
|
|
||||||
|
@Setting(value = "sections")
|
||||||
|
private Map<String, SectionProps> sectionProps;
|
||||||
|
|
||||||
|
@Setting(value = "show-players")
|
||||||
|
private boolean showPlayers;
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.settings.props.features;
|
||||||
|
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.settings.props.shared.CommandProps;
|
||||||
|
import lombok.Data;
|
||||||
|
import ninja.leaping.configurate.objectmapping.Setting;
|
||||||
|
import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ConfigSerializable
|
||||||
|
@Data
|
||||||
|
public class FallbackCommandProps {
|
||||||
|
@Setting
|
||||||
|
private boolean enabled;
|
||||||
|
|
||||||
|
@Setting
|
||||||
|
private CommandProps command;
|
||||||
|
|
||||||
|
@Setting(value = "excluded-sections")
|
||||||
|
private List<String> excludedSections;
|
||||||
|
|
||||||
|
@Setting
|
||||||
|
private boolean restrictive;
|
||||||
|
|
||||||
|
@Setting(value = "prevent-same-section")
|
||||||
|
private boolean preventSameSection;
|
||||||
|
|
||||||
|
@Setting
|
||||||
|
private Map<String, String> rules;
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.settings.props.features;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import ninja.leaping.configurate.objectmapping.Setting;
|
||||||
|
import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ConfigSerializable
|
||||||
|
@Data
|
||||||
|
public class KickHandlerProps {
|
||||||
|
@Setting
|
||||||
|
private boolean enabled;
|
||||||
|
|
||||||
|
@Setting
|
||||||
|
private boolean inverted;
|
||||||
|
|
||||||
|
@Setting
|
||||||
|
private List<String> reasons;
|
||||||
|
|
||||||
|
@Setting(value = "excluded-sections")
|
||||||
|
private List<String> excludedSections;
|
||||||
|
|
||||||
|
@Setting
|
||||||
|
private boolean restrictive;
|
||||||
|
|
||||||
|
@Setting(value = "force-principal")
|
||||||
|
private boolean forcePrincipal;
|
||||||
|
|
||||||
|
@Setting
|
||||||
|
private Map<String, String> rules;
|
||||||
|
|
||||||
|
@Setting(value = "debug-info")
|
||||||
|
private boolean debug;
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.settings.props.features;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import ninja.leaping.configurate.objectmapping.Setting;
|
||||||
|
import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ConfigSerializable
|
||||||
|
@Data
|
||||||
|
public class PermissionRouterProps {
|
||||||
|
@Setting
|
||||||
|
private boolean enabled;
|
||||||
|
|
||||||
|
@Setting
|
||||||
|
private Map<String, Map<String, String>> rules;
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.settings.props.features;
|
||||||
|
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.ping.PingTactic;
|
||||||
|
import lombok.Data;
|
||||||
|
import ninja.leaping.configurate.objectmapping.Setting;
|
||||||
|
import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@ConfigSerializable
|
||||||
|
@Data
|
||||||
|
public class ServerCheckerProps {
|
||||||
|
@Setting
|
||||||
|
private boolean enabled;
|
||||||
|
|
||||||
|
@Setting
|
||||||
|
private PingTactic tactic;
|
||||||
|
|
||||||
|
@Setting
|
||||||
|
private int attempts;
|
||||||
|
|
||||||
|
@Setting
|
||||||
|
private int interval;
|
||||||
|
|
||||||
|
@Setting
|
||||||
|
private int timeout;
|
||||||
|
|
||||||
|
@Setting(value = "marker-descs")
|
||||||
|
private List<String> markerDescs;
|
||||||
|
|
||||||
|
@Setting(value = "debug-info")
|
||||||
|
private boolean debug;
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.settings.props.features;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import ninja.leaping.configurate.objectmapping.Setting;
|
||||||
|
import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable;
|
||||||
|
|
||||||
|
@ConfigSerializable
|
||||||
|
@Data
|
||||||
|
public class ServerRefreshProps {
|
||||||
|
@Setting
|
||||||
|
private boolean enabled;
|
||||||
|
|
||||||
|
@Setting
|
||||||
|
private int delay;
|
||||||
|
|
||||||
|
@Setting
|
||||||
|
private int interval;
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.settings.props.shared;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import ninja.leaping.configurate.objectmapping.Setting;
|
||||||
|
import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@ConfigSerializable
|
||||||
|
@Data
|
||||||
|
public class CommandProps {
|
||||||
|
@Setting
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Setting
|
||||||
|
private String permission;
|
||||||
|
|
||||||
|
@Setting
|
||||||
|
private List<String> aliases;
|
||||||
|
|
||||||
|
public String getPermission() {
|
||||||
|
if (permission != null) {
|
||||||
|
return permission;
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getAliases() {
|
||||||
|
if (aliases != null) {
|
||||||
|
return aliases;
|
||||||
|
} else {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] getAliasesArray() {
|
||||||
|
if (aliases != null) {
|
||||||
|
return aliases.toArray(new String[aliases.size()]);
|
||||||
|
} else {
|
||||||
|
return new String[] {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.settings.props.shared;
|
||||||
|
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.connection.ProviderType;
|
||||||
|
import lombok.Data;
|
||||||
|
import ninja.leaping.configurate.objectmapping.Setting;
|
||||||
|
import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@ConfigSerializable
|
||||||
|
@Data
|
||||||
|
public class SectionProps {
|
||||||
|
@Setting
|
||||||
|
private ProviderType provider;
|
||||||
|
|
||||||
|
@Setting
|
||||||
|
private String alias;
|
||||||
|
|
||||||
|
@Setting(value = "parent")
|
||||||
|
private String parentName;
|
||||||
|
|
||||||
|
@Setting(value = "servers")
|
||||||
|
private List<String> serverEntries;
|
||||||
|
|
||||||
|
@Setting(value = "section-command")
|
||||||
|
private CommandProps commandProps;
|
||||||
|
|
||||||
|
@Setting(value = "section-server")
|
||||||
|
private String serverName;
|
||||||
|
}
|
@ -0,0 +1,137 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.utils;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The Alphanum Algorithm is an improved sorting algorithm for strings
|
||||||
|
* containing numbers. Instead of sorting numbers in ASCII order like
|
||||||
|
* a standard sort, this algorithm sorts numbers in numeric order.
|
||||||
|
*
|
||||||
|
* The Alphanum Algorithm is discussed at http://www.DaveKoelle.com
|
||||||
|
*
|
||||||
|
* Released under the MIT License - https://opensource.org/licenses/MIT
|
||||||
|
*
|
||||||
|
* Copyright 2007-2017 David Koelle
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the "Software"),
|
||||||
|
* to deal in the Software without restriction, including without limitation
|
||||||
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
* and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
* Software is furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included
|
||||||
|
* in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||||
|
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||||
|
* USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is an updated version with enhancements made by Daniel Migowski,
|
||||||
|
* Andre Bogus, and David Koelle. Updated by David Koelle in 2017.
|
||||||
|
*
|
||||||
|
* To use this class:
|
||||||
|
* Use the static "sort" method from the java.util.Collections class:
|
||||||
|
* Collections.sort(your list, new AlphanumComparator());
|
||||||
|
*/
|
||||||
|
public class AlphanumComparator implements Comparator<String>
|
||||||
|
{
|
||||||
|
private final boolean isDigit(char ch)
|
||||||
|
{
|
||||||
|
return ((ch >= 48) && (ch <= 57));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Length of string is passed in for improved efficiency (only need to calculate it once) **/
|
||||||
|
private final String getChunk(String s, int slength, int marker)
|
||||||
|
{
|
||||||
|
StringBuilder chunk = new StringBuilder();
|
||||||
|
char c = s.charAt(marker);
|
||||||
|
chunk.append(c);
|
||||||
|
marker++;
|
||||||
|
if (isDigit(c))
|
||||||
|
{
|
||||||
|
while (marker < slength)
|
||||||
|
{
|
||||||
|
c = s.charAt(marker);
|
||||||
|
if (!isDigit(c))
|
||||||
|
break;
|
||||||
|
chunk.append(c);
|
||||||
|
marker++;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
while (marker < slength)
|
||||||
|
{
|
||||||
|
c = s.charAt(marker);
|
||||||
|
if (isDigit(c))
|
||||||
|
break;
|
||||||
|
chunk.append(c);
|
||||||
|
marker++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return chunk.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int compare(String s1, String s2)
|
||||||
|
{
|
||||||
|
if ((s1 == null) || (s2 == null))
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int thisMarker = 0;
|
||||||
|
int thatMarker = 0;
|
||||||
|
int s1Length = s1.length();
|
||||||
|
int s2Length = s2.length();
|
||||||
|
|
||||||
|
while (thisMarker < s1Length && thatMarker < s2Length)
|
||||||
|
{
|
||||||
|
String thisChunk = getChunk(s1, s1Length, thisMarker);
|
||||||
|
thisMarker += thisChunk.length();
|
||||||
|
|
||||||
|
String thatChunk = getChunk(s2, s2Length, thatMarker);
|
||||||
|
thatMarker += thatChunk.length();
|
||||||
|
|
||||||
|
// If both chunks contain numeric characters, sort them numerically
|
||||||
|
int result = 0;
|
||||||
|
if (isDigit(thisChunk.charAt(0)) && isDigit(thatChunk.charAt(0)))
|
||||||
|
{
|
||||||
|
// Simple chunk comparison by length.
|
||||||
|
int thisChunkLength = thisChunk.length();
|
||||||
|
result = thisChunkLength - thatChunk.length();
|
||||||
|
// If equal, the first different number counts
|
||||||
|
if (result == 0)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < thisChunkLength; i++)
|
||||||
|
{
|
||||||
|
result = thisChunk.charAt(i) - thatChunk.charAt(i);
|
||||||
|
if (result != 0)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = thisChunk.compareTo(thatChunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result != 0)
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return s1Length - s2Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final AlphanumComparator instance = new AlphanumComparator();
|
||||||
|
public static AlphanumComparator getInstance() {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.utils;
|
||||||
|
|
||||||
|
public class BuildInfo {
|
||||||
|
public static String getUserId() {
|
||||||
|
return "%%__USER__%%";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getResourceId() {
|
||||||
|
return "%%__RESOURCE__%%";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getNonceId() {
|
||||||
|
return "%%__NONCE__%%";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.utils;
|
||||||
|
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.logging.Formatter;
|
||||||
|
import java.util.logging.LogRecord;
|
||||||
|
|
||||||
|
public class CustomFormatter extends Formatter {
|
||||||
|
private final DateFormat format = new SimpleDateFormat("HH:mm:ss");
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String format(LogRecord record) {
|
||||||
|
StringBuilder builder = new StringBuilder(String.format("[%s %s] %s\n",
|
||||||
|
format.format(record.getMillis()),
|
||||||
|
record.getLevel().getName(),
|
||||||
|
formatMessage(record)
|
||||||
|
));
|
||||||
|
|
||||||
|
if (record.getThrown() != null) {
|
||||||
|
StringWriter writer = new StringWriter();
|
||||||
|
record.getThrown().printStackTrace(new PrintWriter(writer));
|
||||||
|
builder.append(writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.utils;
|
||||||
|
|
||||||
|
public final class DigitUtils {
|
||||||
|
public static int getDigits(String string, int digits) {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
|
||||||
|
for (char character : string.toCharArray()) {
|
||||||
|
if (Character.isDigit(character)) {
|
||||||
|
if (builder.length() >= digits) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.append(character);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (builder.length() < digits) {
|
||||||
|
builder.append("0");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Integer.parseInt(builder.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getDigits(String string) {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
|
||||||
|
for (char character : string.toCharArray()) {
|
||||||
|
if (Character.isDigit(character)) {
|
||||||
|
builder.append(character);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Integer.parseInt(builder.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private DigitUtils() {}
|
||||||
|
}
|
@ -0,0 +1,174 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.utils;
|
||||||
|
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.util.AbstractMap.SimpleEntry;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class GuestPaste {
|
||||||
|
private final String key;
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
private String name = null;
|
||||||
|
private String format = null;
|
||||||
|
private Expiration expiration = null;
|
||||||
|
private Exposure exposure = null;
|
||||||
|
|
||||||
|
public GuestPaste(String key, String code) {
|
||||||
|
this.key = key;
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public URL paste() throws Exception {
|
||||||
|
URL url = new URL("https://pastebin.com/api/api_post.php");
|
||||||
|
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
|
||||||
|
|
||||||
|
con.setRequestMethod("POST");
|
||||||
|
con.setRequestProperty("User-Agent", "Mozilla/5.0");
|
||||||
|
|
||||||
|
List<SimpleEntry<String, String>> params = new LinkedList<>();
|
||||||
|
params.add(new SimpleEntry<>("api_dev_key", key));
|
||||||
|
params.add(new SimpleEntry<>("api_option", "paste"));
|
||||||
|
params.add(new SimpleEntry<>("api_paste_code", code));
|
||||||
|
|
||||||
|
if (name != null) {
|
||||||
|
params.add(new SimpleEntry<>("api_paste_name", name));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (format != null) {
|
||||||
|
params.add(new SimpleEntry<>("api_paste_format", format));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expiration != null) {
|
||||||
|
params.add(new SimpleEntry<>("api_paste_expire_date", expiration.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exposure != null) {
|
||||||
|
params.add(new SimpleEntry<>("api_paste_private", String.valueOf(exposure.value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder output = new StringBuilder();
|
||||||
|
for (SimpleEntry<String, String> entry : params) {
|
||||||
|
if (output.length() > 0)
|
||||||
|
output.append('&');
|
||||||
|
|
||||||
|
output.append(URLEncoder.encode(entry.getKey(), "UTF-8"));
|
||||||
|
output.append('=');
|
||||||
|
output.append(URLEncoder.encode(entry.getValue(), "UTF-8"));
|
||||||
|
}
|
||||||
|
|
||||||
|
con.setDoOutput(true);
|
||||||
|
try (DataOutputStream dos = new DataOutputStream(con.getOutputStream())) {
|
||||||
|
dos.writeBytes(output.toString());
|
||||||
|
dos.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
int status = con.getResponseCode();
|
||||||
|
if (status >= 200 && status < 300) {
|
||||||
|
try (BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream()))) {
|
||||||
|
String inputLine;
|
||||||
|
StringBuilder response = new StringBuilder();
|
||||||
|
|
||||||
|
while ((inputLine = br.readLine()) != null) {
|
||||||
|
response.append(inputLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new URL(response.toString());
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
throw new PasteException(response.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new PasteException("Unexpected response code " + status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Expiration {
|
||||||
|
NEVER("N"),
|
||||||
|
TEN_MINUTES("10M"),
|
||||||
|
ONE_HOUR("1H"),
|
||||||
|
ONE_DAY("1D"),
|
||||||
|
ONE_WEEK("1W"),
|
||||||
|
TWO_WEEKS("2W"),
|
||||||
|
ONE_MONTH("1M"),
|
||||||
|
SIX_MONTHS("6M"),
|
||||||
|
ONE_YEAR("1Y");
|
||||||
|
|
||||||
|
private final String value;
|
||||||
|
Expiration(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Exposure {
|
||||||
|
PUBLIC(0),
|
||||||
|
UNLISTED(1),
|
||||||
|
PRIVATE(2);
|
||||||
|
|
||||||
|
private final int value;
|
||||||
|
Exposure(int value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PasteException extends Exception {
|
||||||
|
public PasteException(String response) {
|
||||||
|
super(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFormat() {
|
||||||
|
return format;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFormat(String format) {
|
||||||
|
this.format = format;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Expiration getExpiration() {
|
||||||
|
return expiration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExpiration(Expiration expiration) {
|
||||||
|
this.expiration = expiration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Exposure getExposure() {
|
||||||
|
return exposure;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExposure(Exposure exposure) {
|
||||||
|
this.exposure = exposure;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.utils;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
public class HastebinPaste {
|
||||||
|
private final String site;
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
public HastebinPaste(String site, String code) {
|
||||||
|
this.site = site.endsWith("/") ? site : site + "/";
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public URL paste() throws Exception {
|
||||||
|
URL url = new URL(site + "documents");
|
||||||
|
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
|
||||||
|
|
||||||
|
con.setRequestMethod("POST");
|
||||||
|
con.setRequestProperty("User-Agent", "Mozilla/5.0");
|
||||||
|
|
||||||
|
con.setDoOutput(true);
|
||||||
|
try (DataOutputStream dos = new DataOutputStream(con.getOutputStream())) {
|
||||||
|
dos.writeBytes(code);
|
||||||
|
dos.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
int status = con.getResponseCode();
|
||||||
|
if (status >= 200 && status < 300) {
|
||||||
|
try (BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream()))) {
|
||||||
|
JsonObject root = new JsonParser().parse(br.readLine()).getAsJsonObject();
|
||||||
|
return new URL(site + root.getAsJsonPrimitive("key").getAsString());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Exception("Unexpected response code " + status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.utils;
|
||||||
|
|
||||||
|
public final class LevenshteinDistance {
|
||||||
|
public static <T> T closest(Iterable<T> collection, T target) {
|
||||||
|
int distance = Integer.MAX_VALUE;
|
||||||
|
T closest = null;
|
||||||
|
|
||||||
|
for (T object : collection) {
|
||||||
|
int current = distance(object.toString(), target.toString());
|
||||||
|
if (current < distance) {
|
||||||
|
distance = current;
|
||||||
|
closest = object;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return closest;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int distance(CharSequence lhs, CharSequence rhs) {
|
||||||
|
int len0 = lhs.length() + 1;
|
||||||
|
int len1 = rhs.length() + 1;
|
||||||
|
|
||||||
|
// the array of distances
|
||||||
|
int[] cost = new int[len0];
|
||||||
|
int[] newcost = new int[len0];
|
||||||
|
|
||||||
|
// initial cost of skipping prefix in String s0
|
||||||
|
for (int i = 0; i < len0; i++) cost[i] = i;
|
||||||
|
|
||||||
|
// dynamically computing the array of distances
|
||||||
|
|
||||||
|
// transformation cost for each letter in s1
|
||||||
|
for (int j = 1; j < len1; j++) {
|
||||||
|
// initial cost of skipping prefix in String s1
|
||||||
|
newcost[0] = j;
|
||||||
|
|
||||||
|
// transformation cost for each letter in s0
|
||||||
|
for (int i = 1; i < len0; i++) {
|
||||||
|
// matching current letters in both strings
|
||||||
|
int match = (lhs.charAt(i - 1) == rhs.charAt(j - 1)) ? 0 : 1;
|
||||||
|
|
||||||
|
// computing cost for each transformation
|
||||||
|
int cost_replace = cost[i - 1] + match;
|
||||||
|
int cost_insert = cost[i] + 1;
|
||||||
|
int cost_delete = newcost[i - 1] + 1;
|
||||||
|
|
||||||
|
// keep minimum cost
|
||||||
|
newcost[i] = Math.min(Math.min(cost_insert, cost_delete), cost_replace);
|
||||||
|
}
|
||||||
|
|
||||||
|
// swap cost/newcost arrays
|
||||||
|
int[] swap = cost;
|
||||||
|
cost = newcost;
|
||||||
|
newcost = swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cost[len0 - 1];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.utils;
|
||||||
|
|
||||||
|
import com.velocitypowered.api.command.CommandSource;
|
||||||
|
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public final class MessageUtils {
|
||||||
|
public static void send(CommandSource sender, String text) {
|
||||||
|
if (text != null) {
|
||||||
|
sender.sendMessage(MiniMessage.miniMessage().deserialize(text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void send(CommandSource sender, String text, Function<String, String> postProcess) {
|
||||||
|
if (text != null) {
|
||||||
|
text = postProcess.apply(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
send(sender, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String revertColor(String string) {
|
||||||
|
return string.replace('§', '&');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String safeNull(String string) {
|
||||||
|
if (string == null) {
|
||||||
|
return "Undefined";
|
||||||
|
}
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MessageUtils() {}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.utils;
|
||||||
|
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class RandomUtils {
|
||||||
|
private static final SecureRandom instance = new SecureRandom();
|
||||||
|
|
||||||
|
public static <T> T random(List<T> list) {
|
||||||
|
return list.get(instance.nextInt(list.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private RandomUtils() {}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.utils;
|
||||||
|
|
||||||
|
import com.google.common.cache.CacheBuilder;
|
||||||
|
import com.google.common.cache.CacheLoader;
|
||||||
|
import com.google.common.cache.LoadingCache;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public final class RegExUtils {
|
||||||
|
private static final LoadingCache<String, Pattern> COMPILED_PATTERNS = CacheBuilder.newBuilder().build(new CacheLoader<String, Pattern>() {
|
||||||
|
@Override
|
||||||
|
public Pattern load(String string) throws Exception {
|
||||||
|
return Pattern.compile(string);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
public static Pattern getPattern(String regexp) {
|
||||||
|
try {
|
||||||
|
return COMPILED_PATTERNS.get(regexp);
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
throw new RuntimeException("Error while getting a pattern from the cache");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean matches(String string, String expression) {
|
||||||
|
return getMatcher(string, expression).matches();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Matcher getMatcher(String string, String expression) {
|
||||||
|
Pattern pattern = getPattern(expression);
|
||||||
|
return pattern.matcher(string);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RegExUtils() {}
|
||||||
|
}
|
@ -0,0 +1,227 @@
|
|||||||
|
package com.jaimemartz.playerbalancer.velocity.utils;
|
||||||
|
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public final class ServerListPing {
|
||||||
|
|
||||||
|
private static GsonComponentSerializer gson = GsonComponentSerializer.gson();
|
||||||
|
|
||||||
|
private static int readVarInt(DataInputStream in) throws IOException {
|
||||||
|
int i = 0;
|
||||||
|
int j = 0;
|
||||||
|
while (true) {
|
||||||
|
int k = in.readByte();
|
||||||
|
i |= (k & 0x7F) << j++ * 7;
|
||||||
|
if (j > 5) throw new RuntimeException("VarInt too big");
|
||||||
|
if ((k & 0x80) != 128) break;
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeVarInt(DataOutputStream out, int paramInt) throws IOException {
|
||||||
|
while (true) {
|
||||||
|
if ((paramInt & 0xFFFFFF80) == 0) {
|
||||||
|
out.writeByte(paramInt);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.writeByte(paramInt & 0x7F | 0x80);
|
||||||
|
paramInt >>>= 7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public StatusResponse ping(InetSocketAddress host, int timeout) throws IOException {
|
||||||
|
try (Socket socket = new Socket()) {
|
||||||
|
socket.setSoTimeout(timeout);
|
||||||
|
socket.connect(host, timeout);
|
||||||
|
|
||||||
|
try (DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
|
||||||
|
DataInputStream dataInputStream = new DataInputStream(socket.getInputStream())) {
|
||||||
|
ByteArrayOutputStream b = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream handshake = new DataOutputStream(b);
|
||||||
|
handshake.writeByte(0x00); // packet id for handshake
|
||||||
|
writeVarInt(handshake, 4); // protocol version
|
||||||
|
writeVarInt(handshake, host.getHostString().length()); // host length
|
||||||
|
handshake.writeBytes(host.getHostString()); // host string
|
||||||
|
handshake.writeShort(host.getPort()); // port
|
||||||
|
writeVarInt(handshake, 1); // state (1 for handshake)
|
||||||
|
|
||||||
|
writeVarInt(dataOutputStream, b.size()); // prepend size
|
||||||
|
dataOutputStream.write(b.toByteArray()); // write handshake packet
|
||||||
|
|
||||||
|
|
||||||
|
dataOutputStream.writeByte(0x01); // size is only 1
|
||||||
|
dataOutputStream.writeByte(0x00); // packet id for ping
|
||||||
|
int size = readVarInt(dataInputStream); // size of packet
|
||||||
|
int id = readVarInt(dataInputStream); // packet id
|
||||||
|
|
||||||
|
if (id == -1) {
|
||||||
|
throw new IOException("Premature end of stream.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id != 0x00) { // we want a status response
|
||||||
|
throw new IOException("Invalid packetID");
|
||||||
|
}
|
||||||
|
|
||||||
|
int length = readVarInt(dataInputStream); // length of json string
|
||||||
|
if (length == -1) {
|
||||||
|
throw new IOException("Premature end of stream.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (length == 0) {
|
||||||
|
throw new IOException("Invalid string length.");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] in = new byte[length];
|
||||||
|
dataInputStream.readFully(in); // read json string
|
||||||
|
String json = new String(in);
|
||||||
|
|
||||||
|
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
dataOutputStream.writeByte(0x09); // size of packet
|
||||||
|
dataOutputStream.writeByte(0x01); // 0x01 for ping
|
||||||
|
dataOutputStream.writeLong(now); // time!?
|
||||||
|
|
||||||
|
readVarInt(dataInputStream);
|
||||||
|
id = readVarInt(dataInputStream);
|
||||||
|
if (id == -1) {
|
||||||
|
throw new IOException("Premature end of stream.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id != 0x01) {
|
||||||
|
throw new IOException("Invalid packetID");
|
||||||
|
}
|
||||||
|
|
||||||
|
long pingTime = dataInputStream.readLong(); // read response
|
||||||
|
|
||||||
|
StatusResponse response = StatusResponse.fromJson(json);
|
||||||
|
|
||||||
|
response.time = (int) (now - pingTime);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class StatusResponse {
|
||||||
|
private Component description;
|
||||||
|
private Players players;
|
||||||
|
private Version version;
|
||||||
|
private String favicon;
|
||||||
|
private int time;
|
||||||
|
|
||||||
|
public Component getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Players getPlayers() {
|
||||||
|
return players;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Version getVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFavicon() {
|
||||||
|
return favicon;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTime() {
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Players {
|
||||||
|
private int max;
|
||||||
|
private int online;
|
||||||
|
private List<Player> sample;
|
||||||
|
|
||||||
|
public int getMax() {
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getOnline() {
|
||||||
|
return online;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Player> getSample() {
|
||||||
|
return sample;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Player {
|
||||||
|
private String name;
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Version {
|
||||||
|
private String name;
|
||||||
|
private String protocol;
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProtocol() {
|
||||||
|
return protocol;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static StatusResponse fromJson(String json) {
|
||||||
|
StatusResponse response = new StatusResponse();
|
||||||
|
|
||||||
|
JsonObject jsonObject = new JsonParser().parse(json).getAsJsonObject();
|
||||||
|
|
||||||
|
// Extract and set the "description" field as an Adventure Component
|
||||||
|
JsonElement descriptionElement = jsonObject.get("description");
|
||||||
|
if (descriptionElement != null) {
|
||||||
|
response.description = gson.deserialize(descriptionElement.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract and set the "players" field
|
||||||
|
JsonObject playersObject = jsonObject.getAsJsonObject("players");
|
||||||
|
if (playersObject != null) {
|
||||||
|
response.players = new Players();
|
||||||
|
response.players.max = playersObject.get("max").getAsInt();
|
||||||
|
response.players.online = playersObject.get("online").getAsInt();
|
||||||
|
// You can also extract the "sample" field if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract and set the "version" field
|
||||||
|
JsonObject versionObject = jsonObject.getAsJsonObject("version");
|
||||||
|
if (versionObject != null) {
|
||||||
|
response.version = new Version();
|
||||||
|
response.version.name = versionObject.get("name").getAsString();
|
||||||
|
response.version.protocol = versionObject.get("protocol").getAsString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract and set the "favicon" field
|
||||||
|
JsonElement faviconElement = jsonObject.get("favicon");
|
||||||
|
if (faviconElement != null) {
|
||||||
|
response.favicon = faviconElement.getAsString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
236
balancer-velocity/src/main/resources/velocity.conf
Normal file
236
balancer-velocity/src/main/resources/velocity.conf
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
# PlayerBalancer Configuration (https://www.spigotmc.org/resources/10788/)
|
||||||
|
# Read the comments, they are a very important part of the configuration
|
||||||
|
# To get support please message us on Discord (https://bghddevelopment.com/discord) or on the "Issues"
|
||||||
|
# section of the GitHub repository.
|
||||||
|
# To easily paste the config file (and other relevant files) use the command /balancer paste
|
||||||
|
# If the plugin has issues loading the configuration, try putting quotes around text
|
||||||
|
|
||||||
|
general {
|
||||||
|
# IMPORTANT! Set this to true after configuring the plugin!
|
||||||
|
enabled=false
|
||||||
|
|
||||||
|
# When true, the plugin will reload when you execute /greload
|
||||||
|
auto-reload=true
|
||||||
|
|
||||||
|
# When true, the plugin will get player counts from RedisBungee (Limework Fork: https://www.spigotmc.org/resources/87700/)
|
||||||
|
redis-bungee=false
|
||||||
|
|
||||||
|
# When true, this plugin will print less messages when loading
|
||||||
|
silent=false
|
||||||
|
|
||||||
|
# When true, spigot plugins will be able to contact with this plugin
|
||||||
|
# Do not disable if you are using the addon!
|
||||||
|
plugin-messaging=true
|
||||||
|
|
||||||
|
# Do not modify this
|
||||||
|
version="${project.version}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Effectively remove (i.e comment) a message to disable it
|
||||||
|
# Supported variables are shown in the default messages
|
||||||
|
messages {
|
||||||
|
# connecting-server="&aConnecting to an {section} ({alias}) server" # this message is disabled by default!
|
||||||
|
connected-server="<green>Connected to {server} (an {alias} server)"
|
||||||
|
invalid-input="<red>This is an invalid input type for this command"
|
||||||
|
misc-failure="<red>Could not find a server to get connected to"
|
||||||
|
player-bypass="<red>You have not been moved because you have the playerbalancer.bypass permission"
|
||||||
|
player-kicked="<red>You have been kicked from <green>{from} <red>so you are being moved to <green>{to}\n<red>Reason: <green>{reason}"
|
||||||
|
same-section="<red>You are already connected to a server on {alias}!"
|
||||||
|
unavailable-server="<red>This command cannot be executed on this server"
|
||||||
|
unknown-section="<green>Could not find a section with that name"
|
||||||
|
}
|
||||||
|
|
||||||
|
features {
|
||||||
|
balancer {
|
||||||
|
# Here you have an example of what you can do with the sections
|
||||||
|
# The best way to understand this is to play around with it
|
||||||
|
# You can have as many sections as you want, there is no limit here
|
||||||
|
# If a section does not have a provider it will try to inherit it from the parent
|
||||||
|
# When connecting to a server on a section while not being on it already, you get distributed
|
||||||
|
# You can use regex to match a set of servers instead of adding each server individually
|
||||||
|
|
||||||
|
# Providers you can use: (you can request more!)
|
||||||
|
# NONE: Returns no server (no one will be able to connect to this section)
|
||||||
|
# RANDOM: Returns a random server, generated by SecureRandom
|
||||||
|
# RANDOM_LOWEST: Returns a random server between the ones with the least players online
|
||||||
|
# RANDOM_FILLER: Returns a random server between the ones with the most players online that is not full
|
||||||
|
# PROGRESSIVE: Returns the first server that is not full
|
||||||
|
# PROGRESSIVE_LOWEST: Returns the first server with the least players online
|
||||||
|
# PROGRESSIVE_FILLER: Returns the first server with the most players online that is not full
|
||||||
|
# EXTERNAL: Returns the server calculated by a provider created by other plugin
|
||||||
|
|
||||||
|
sections {
|
||||||
|
auth-lobbies {
|
||||||
|
provider=RANDOM
|
||||||
|
servers=[
|
||||||
|
"Auth1",
|
||||||
|
"Auth2",
|
||||||
|
"Auth3"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
general-lobbies {
|
||||||
|
parent="auth-lobbies"
|
||||||
|
alias="General Lobbies"
|
||||||
|
servers=[
|
||||||
|
"Lobby[1-3]"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
skywars-lobbies {
|
||||||
|
parent="general-lobbies"
|
||||||
|
provider=PROGRESSIVE_LOWEST
|
||||||
|
servers=[
|
||||||
|
"SWLobby1",
|
||||||
|
"SWLobby2",
|
||||||
|
"SWLobby3"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
skywars-games {
|
||||||
|
parent="skywars-lobbies"
|
||||||
|
provider=RANDOM_FILLER
|
||||||
|
servers=["SW_A[1-5]", "SW_B[1-5]"]
|
||||||
|
section-command {
|
||||||
|
name=skywars
|
||||||
|
permission=""
|
||||||
|
aliases=[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# The principal section is very important for other features
|
||||||
|
# Normally set this to the section that has your main lobbies
|
||||||
|
principal-section="general-lobbies"
|
||||||
|
|
||||||
|
# When a player is not in any section, the player will go to the principal section
|
||||||
|
# This affects both the fallback command and kick handler features
|
||||||
|
default-principal=true
|
||||||
|
|
||||||
|
# Dummy sections can have servers from other non-dummy sections
|
||||||
|
# When a player connects to a dummy section, nothing will happen
|
||||||
|
dummy-sections=[]
|
||||||
|
|
||||||
|
# Reiterative sections remember the server the player connected to previously
|
||||||
|
# The plugin will keep connecting the player to that server repeatedly
|
||||||
|
reiterative-sections=[]
|
||||||
|
|
||||||
|
# When true, section servers will show the sum of the players on all servers on that section
|
||||||
|
# Important: This will make some plugins think that your proxy has more players than it really does
|
||||||
|
show-players=true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Pings servers to see if they are online or not and if they are accessible
|
||||||
|
server-checker {
|
||||||
|
enabled=true
|
||||||
|
|
||||||
|
# Use either CUSTOM or GENERIC, the first one generally works the best
|
||||||
|
tactic=CUSTOM
|
||||||
|
|
||||||
|
# The attempts before giving up on getting a server for a player
|
||||||
|
attempts=5
|
||||||
|
|
||||||
|
# The interval between every round of checks (in milliseconds)
|
||||||
|
interval=10000
|
||||||
|
|
||||||
|
# The timeout of a ping, only applied to the CUSTOM tactic
|
||||||
|
timeout=7000
|
||||||
|
|
||||||
|
# When true, the plugin will print useful info when a server gets checked
|
||||||
|
debug-info=false
|
||||||
|
|
||||||
|
# When the description of a server matches these, it will be set as non accessible
|
||||||
|
# Be aware of colors, it is recommended to use the "contains" rule below or some others
|
||||||
|
marker-descs=[
|
||||||
|
"(?i).*maintenance*", # match if contains (regex)
|
||||||
|
"Game in progress" # match if exactly equal
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Connects a player to the parent of current section the player is connected to
|
||||||
|
fallback-command {
|
||||||
|
enabled=true
|
||||||
|
|
||||||
|
# Leave permission empty for no permission
|
||||||
|
command {
|
||||||
|
name=fallback
|
||||||
|
permission=""
|
||||||
|
aliases=[
|
||||||
|
lobby,
|
||||||
|
hub,
|
||||||
|
back
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add sections here where you do not want this feature to work
|
||||||
|
excluded-sections=[]
|
||||||
|
|
||||||
|
# When true, players will not be able to get connected to sections that are parents of the principal section
|
||||||
|
restrictive=true
|
||||||
|
|
||||||
|
# When true, players will not be able get connected within servers of the same section
|
||||||
|
# This does not affect the parametized variant of the command (/command [number])
|
||||||
|
# This also affects section commands
|
||||||
|
prevent-same-section=true
|
||||||
|
|
||||||
|
# You can override the behavior with rules, overriding the parent section
|
||||||
|
# This will set the section to go when you come from the section specified
|
||||||
|
rules {
|
||||||
|
section-from=section-to
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Connects a player to other section when kicked
|
||||||
|
kick-handler {
|
||||||
|
enabled=true
|
||||||
|
|
||||||
|
# When true, the reasons will work as a blacklist instead of a whitelist
|
||||||
|
# Blacklist: A player must be kicked with a reason that is NOT in the reasons
|
||||||
|
# Whitelist: A player must be kicked with a reason that is in the reasons
|
||||||
|
inverted=true
|
||||||
|
|
||||||
|
# The reasons that determine if a player is reconnected or not, supports regex
|
||||||
|
reasons=[]
|
||||||
|
|
||||||
|
# When true, players that are kicked while connecting to the proxy will be forced to reconnect to the principal section
|
||||||
|
force-principal=false
|
||||||
|
|
||||||
|
# Add sections here where you do not want this feature to work
|
||||||
|
excluded-sections=[]
|
||||||
|
|
||||||
|
# When true, players will not be able to get connected to sections that are parents of the principal section
|
||||||
|
restrictive=true
|
||||||
|
|
||||||
|
# When true, the plugin will print useful info when a player gets kicked
|
||||||
|
debug-info=false
|
||||||
|
|
||||||
|
# You can override the behavior with rules, overriding the parent section
|
||||||
|
# When you get kicked from the section on the left, you go to the one on the right
|
||||||
|
rules {
|
||||||
|
section-from=section-to
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Periodically adds servers that weren't there before the plugin loaded
|
||||||
|
server-refresh {
|
||||||
|
enabled=false
|
||||||
|
|
||||||
|
# The delay to the first refresh (in milliseconds)
|
||||||
|
delay=2000
|
||||||
|
|
||||||
|
# The interval between every refresh (in milliseconds)
|
||||||
|
interval=5000
|
||||||
|
}
|
||||||
|
|
||||||
|
# Redirect players when connecting to a section in case they have a permission, useful for special lobbies
|
||||||
|
# Players will not get redirected if they are connected to a server where they were previously redirected to
|
||||||
|
permission-router {
|
||||||
|
enabled=false
|
||||||
|
|
||||||
|
rules {
|
||||||
|
general-lobbies {
|
||||||
|
"special.permission"=other-lobby-section
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
balancer-velocity/src/test/java/DefaultConfigLoadTest.java
Normal file
33
balancer-velocity/src/test/java/DefaultConfigLoadTest.java
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import com.google.common.reflect.TypeToken;
|
||||||
|
import com.jaimemartz.playerbalancer.velocity.settings.SettingsHolder;
|
||||||
|
import ninja.leaping.configurate.commented.CommentedConfigurationNode;
|
||||||
|
import ninja.leaping.configurate.hocon.HoconConfigurationLoader;
|
||||||
|
import ninja.leaping.configurate.objectmapping.ObjectMappingException;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
public class DefaultConfigLoadTest {
|
||||||
|
private URL file;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() throws IOException {
|
||||||
|
file = getClass().getResource("default.conf");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() throws IOException, ObjectMappingException {
|
||||||
|
HoconConfigurationLoader loader = HoconConfigurationLoader
|
||||||
|
.builder()
|
||||||
|
.setURL(file)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
CommentedConfigurationNode node = loader.load();
|
||||||
|
SettingsHolder settings = node.getValue(TypeToken.of(SettingsHolder.class));
|
||||||
|
|
||||||
|
System.out.println(settings);
|
||||||
|
}
|
||||||
|
}
|
25
balancer-velocity/src/test/java/HastebinPasteTest.java
Normal file
25
balancer-velocity/src/test/java/HastebinPasteTest.java
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import com.jaimemartz.playerbalancer.velocity.utils.HastebinPaste;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class HastebinPasteTest {
|
||||||
|
@Test
|
||||||
|
public void test() throws Exception {
|
||||||
|
HastebinPaste paste = new HastebinPaste("https://haste.zneix.eu/",
|
||||||
|
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed " +
|
||||||
|
"iaculis, sapien et vehicula tristique, diam libero bibendum " +
|
||||||
|
"nunc, et rutrum nisl nulla quis diam. Cras ipsum enim, molestie" +
|
||||||
|
" eget bibendum nec, porta quis ex. Nunc ac sem lorem. Duis eget" +
|
||||||
|
" vestibulum libero. Phasellus vitae venenatis arcu, ac volutpat " +
|
||||||
|
"sem. Nunc porttitor lacus nulla, vitae dictum justo porta at. " +
|
||||||
|
"Aliquam erat volutpat. Vestibulum aliquet eget diam eget commodo." +
|
||||||
|
" Integer facilisis ipsum sit amet sem pharetra ultrices. Nulla diam" +
|
||||||
|
" orci, posuere malesuada ante non, elementum vehicula libero."
|
||||||
|
);
|
||||||
|
|
||||||
|
URL pasteUrl = paste.paste();
|
||||||
|
assertNotNull(pasteUrl);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user