Some more progress

This commit is contained in:
Jaime Martínez Rincón 2017-09-13 21:06:15 +02:00
parent 44a79c0ed9
commit 21291af0c0
13 changed files with 206 additions and 125 deletions

View File

@ -4,20 +4,14 @@
[![Build Status](https://travis-ci.com/Jamezrin/PlayerBalancer.svg?token=2yUi9WpA9QzSbJx9eTmy&branch=master)](https://travis-ci.com/Jamezrin/PlayerBalancer)
### Things to do:
- [x] Get dummy sections able to have already registered serverEntries on other sections
- [x] Add a new message for when a player gets connected to a serverName and repurpose the connecting one
- [ ] Add support for wildcards, contains, equalsIgnoreCase and regex at the same time
- [ ] Add option to force joining a specific section (to the command)
- [x] Add tooltip when you hover over a serverName in /section info
- [ ] Stop using inventivetalent's deprecated bungee-update
- [ ] Create a spigot addon that adds connector signs and placeholders
- [x] Separate the types of connections in classes instead of being in ConnectionIntent
- [ ] Separate the types of connections in classes instead of being in ConnectionIntent
- [ ] Make the plugin API be not so dependent on a instance of PlayerBalancer
- [ ] Separate connection providers in classes instead of being hardcoded in an enum
- [ ] Make the feature `marker-descs` work per section
- [ ] Add a identifier to get the serverEntries of a section (auto complete)
- [ ] Implement fast connect (dimension change)
- [ ] Implement a way to redirect premium players to a section and cracked ones to other section (not sure how this works)
- [ ] Unify the code that loads serverName into a section (duplicated at SectionManager and ServerSection)
- [ ] Unify some of the code used in the FallbackCommand and SectionCommand
- [x] Unify the code that loads serverName into a section (duplicated at SectionManager and ServerSection)
- [x] Unify some of the code used in the FallbackCommand and SectionCommand
- [ ] Make the section initialization work in stages instead of being hardcoded

View File

@ -14,12 +14,9 @@ import com.jaimemartz.playerbalancer.settings.SettingsHolder;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.api.plugin.Plugin;
import ninja.leaping.configurate.ConfigurationOptions;
import ninja.leaping.configurate.commented.CommentedConfigurationNode;
import ninja.leaping.configurate.hocon.HoconConfigurationLoader;
import ninja.leaping.configurate.loader.ConfigurationLoader;
import ninja.leaping.configurate.objectmapping.serialize.TypeSerializerCollection;
import ninja.leaping.configurate.objectmapping.serialize.TypeSerializers;
import org.bstats.bungeecord.Metrics;
import org.inventivetalent.update.bungee.BungeeUpdater;
@ -32,7 +29,7 @@ import java.util.logging.Level;
public class PlayerBalancer extends Plugin {
private boolean failed = false;
private StatusManager statusManager;
private SettingsHolder mainSettings;
private SettingsHolder settings;
private ConfigurationLoader<CommentedConfigurationNode> loader;
private SectionManager sectionManager;
private NetworkManager networkManager;
@ -52,23 +49,21 @@ public class PlayerBalancer extends Plugin {
File file = new File(getDataFolder(), "plugin.conf");
if (!file.exists()) {
try (InputStream in = getResourceAsStream("default.conf")) {
Files.copy(in, file.toPath());
} catch (IOException e) {
e.printStackTrace();
}
}
if (loader == null) {
TypeSerializerCollection serializers = TypeSerializers.getDefaultSerializers().newChild();
ConfigurationOptions options = ConfigurationOptions.defaults().setSerializers(serializers);
loader = HoconConfigurationLoader.builder().setFile(file).setDefaultOptions(options).build();
loader = HoconConfigurationLoader.builder().setFile(file).build();
}
try {
if (!file.exists()) {
try (InputStream in = getResourceAsStream("default.conf")) {
Files.copy(in, file.toPath());
} catch (IOException e) {
e.printStackTrace();
}
}
CommentedConfigurationNode node = loader.load();
mainSettings = node.getValue(TypeToken.of(SettingsHolder.class));
settings = node.getValue(TypeToken.of(SettingsHolder.class));
} catch (Exception e) {
e.printStackTrace();
}
@ -76,12 +71,12 @@ public class PlayerBalancer extends Plugin {
mainCommand = new MainCommand(this);
getProxy().getPluginManager().registerCommand(this, mainCommand);
if (mainSettings.getGeneralProps().isEnabled()) {
if (mainSettings.getGeneralProps().isSilent()) {
if (settings.getGeneralProps().isEnabled()) {
if (settings.getGeneralProps().isSilent()) {
getLogger().setLevel(Level.WARNING);
}
if (mainSettings.getGeneralProps().isAutoReload()) {
if (settings.getGeneralProps().isAutoReload()) {
reloadListener = new ProxyReloadListener(this);
getProxy().getPluginManager().registerListener(this, reloadListener);
}
@ -99,12 +94,12 @@ public class PlayerBalancer extends Plugin {
sectionManager.load();
statusManager = new StatusManager(this);
if (mainSettings.getServerCheckerProps().isEnabled()) {
if (settings.getServerCheckerProps().isEnabled()) {
statusManager.start();
}
if (mainSettings.getFallbackCommandProps().isEnabled()) {
fallbackCommand = new FallbackCommand(this, mainSettings.getFallbackCommandProps().getCommand());
if (settings.getFallbackCommandProps().isEnabled()) {
fallbackCommand = new FallbackCommand(this, settings.getFallbackCommandProps().getCommand());
getProxy().getPluginManager().registerCommand(this, fallbackCommand);
}
@ -123,7 +118,7 @@ public class PlayerBalancer extends Plugin {
PasteHelper.reset();
if (mainSettings.getKickHandlerProps().isEnabled()) {
if (settings.getKickHandlerProps().isEnabled()) {
kickListener = new ServerKickListener(this);
getProxy().getPluginManager().registerListener(this, kickListener);
}
@ -151,25 +146,25 @@ public class PlayerBalancer extends Plugin {
getProxy().getPluginManager().unregisterCommand(mainCommand);
mainCommand = null;
if (mainSettings.getGeneralProps().isEnabled()) {
if (settings.getGeneralProps().isEnabled()) {
//Do not try to do anything if the plugin has not loaded correctly
if (failed) return;
if (mainSettings.getGeneralProps().isAutoReload()) {
if (settings.getGeneralProps().isAutoReload()) {
getProxy().getPluginManager().unregisterListener(reloadListener);
reloadListener = null;
}
if (mainSettings.getServerCheckerProps().isEnabled()) {
if (settings.getServerCheckerProps().isEnabled()) {
statusManager.stop();
}
if (mainSettings.getFallbackCommandProps().isEnabled()) {
if (settings.getFallbackCommandProps().isEnabled()) {
getProxy().getPluginManager().unregisterCommand(fallbackCommand);
fallbackCommand = null;
}
if (mainSettings.getKickHandlerProps().isEnabled()) {
if (settings.getKickHandlerProps().isEnabled()) {
getProxy().getPluginManager().unregisterListener(kickListener);
kickListener = null;
}
@ -186,7 +181,7 @@ public class PlayerBalancer extends Plugin {
sectionManager.flush();
/*
if (mainSettings.getGeneralProps().isAssignTargets()) {
if (settings.getGeneralProps().isAssignTargets()) {
ServerAssignRegistry.getTable().clear();
}
*/
@ -210,7 +205,7 @@ public class PlayerBalancer extends Plugin {
}
public SettingsHolder getSettings() {
return mainSettings;
return settings;
}
public SectionManager getSectionManager() {

View File

@ -14,6 +14,8 @@ import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Command;
import java.util.ArrayList;
public class FallbackCommand extends Command {
protected final PlayerBalancer plugin;
protected final MessagesProps messages;
@ -38,10 +40,10 @@ public class FallbackCommand extends Command {
int number = Integer.parseInt(args[0]);
if (number <= 0) {
MessageUtils.send(player, messages.getInvalidInputMessage());
} else if (number > target.getMappedServers().size()) {
} else if (number > target.getServers().size()) {
MessageUtils.send(player, messages.getFailureMessage());
} else {
ServerInfo server = target.getSortedServers().get(number - 1);
ServerInfo server = new ArrayList<>(target.getServers()).get(number - 1);
ConnectionIntent.direct(plugin, player, server, (response, throwable) -> {
//todo something missing
});

View File

@ -104,9 +104,9 @@ public class ManageCommand extends Command {
sender.sendMessage(new ComponentBuilder("Provider: ")
.color(ChatColor.GRAY)
.append(section.getEffectiveProvider().name())
.append(section.getImplicitProvider().name())
.color(ChatColor.AQUA)
.append(String.format(" (%s)", section.isInherited() ? "Inherited" : "Specified"))
.append(String.format(" (%s)", section.isInherited() ? "Implicit" : "Explicit"))
.color(ChatColor.GRAY)
.create()
);
@ -160,13 +160,13 @@ public class ManageCommand extends Command {
);
}
if (!section.getMappedServers().isEmpty()) {
if (!section.getServers().isEmpty()) {
sender.sendMessage(new ComponentBuilder("Section Servers: ")
.color(ChatColor.GRAY)
.create()
);
section.getMappedServers().forEach(server -> {
section.getServers().forEach(server -> {
ServerStatus status = plugin.getStatusManager().getStatus(server);
sender.sendMessage(new ComponentBuilder("\u2022 Server: ")
.color(ChatColor.GRAY)

View File

@ -28,19 +28,19 @@ public abstract class ConnectionIntent {
(str) -> str.replace("{section}", section.getName())
);
if (servers == section.getMappedServers()) {
if (servers == section.getServers()) {
throw new IllegalStateException("The servers list parameter is the same reference, this cannot happen");
}
Server current = player.getServer();
if (current != null) {
if (section.getMappedServers().contains(current.getInfo())) {
if (section.getServers().contains(current.getInfo())) {
MessageUtils.send(player, plugin.getSettings().getMessagesProps().getSameSectionMessage());
return;
}
}
if (section.getEffectiveProvider() != ProviderType.NONE) {
if (section.getImplicitProvider() != ProviderType.NONE) {
ServerInfo target = this.fetchServer(plugin, player, section, provider, servers);
if (target != null) {
this.connect(target, (response, throwable) -> {
@ -57,15 +57,15 @@ public abstract class ConnectionIntent {
}
public ConnectionIntent(PlayerBalancer plugin, ProxiedPlayer player, ServerSection section) {
this(plugin, player, section.getEffectiveProvider(), section);
this(plugin, player, section.getImplicitProvider(), section);
}
public ConnectionIntent(PlayerBalancer plugin, ProxiedPlayer player, ProviderType type, ServerSection section) {
this(plugin, player, type, section, new ArrayList<>(section.getMappedServers()));
this(plugin, player, type, section, new ArrayList<>(section.getServers()));
}
public ConnectionIntent(PlayerBalancer plugin, ProxiedPlayer player, ServerSection section, List<ServerInfo> servers) {
this(plugin, player, section.getEffectiveProvider(), section, servers);
this(plugin, player, section.getImplicitProvider(), section, servers);
}
private ServerInfo fetchServer(PlayerBalancer plugin, ProxiedPlayer player, ServerSection section, ProviderType provider, List<ServerInfo> servers) {

View File

@ -51,7 +51,7 @@ public class ServerConnectListener implements Listener {
}
//Checks only for servers (not the section server)
if (section.getMappedServers().contains(target)) {
if (section.getServers().contains(target)) {
if (plugin.getSectionManager().isDummy(section)) {
return null;
}
@ -61,7 +61,7 @@ public class ServerConnectListener implements Listener {
return null;
}
if (player.getServer() != null && section.getMappedServers().contains(player.getServer().getInfo())) {
if (player.getServer() != null && section.getServers().contains(player.getServer().getInfo())) {
if (plugin.getSectionManager().isReiterative(section)) {
ServerAssignRegistry.assignTarget(player, section, target);
}

View File

@ -22,8 +22,8 @@ import java.util.concurrent.TimeUnit;
public class ServerKickListener implements Listener {
private final KickHandlerProps props;
private final PlayerBalancer plugin;
private final MessagesProps messages;
private final PlayerBalancer plugin;
public ServerKickListener(PlayerBalancer plugin) {
this.props = plugin.getSettings().getKickHandlerProps();
@ -64,19 +64,19 @@ public class ServerKickListener implements Listener {
if (section != null) {
List<ServerInfo> servers = new ArrayList<>();
servers.addAll(section.getMappedServers());
servers.addAll(section.getServers());
servers.remove(from);
new ConnectionIntent(plugin, player, section, servers) {
@Override
public void connect(ServerInfo server, Callback<Boolean> callback) {
PlayerLocker.lock(player);
MessageUtils.send(player, messages.getKickMessage(), (str) ->
str.replace("{from}", from.getName())
.replace("{to}", server.getName())
.replace("{reason}", event.getKickReason()));
event.setCancelled(true);
event.setCancelServer(server);
MessageUtils.send(player, messages.getKickMessage(), (str) -> str
.replace("{reason}", event.getKickReason())
.replace("{from}", from.getName())
.replace("{to}", server.getName()));
plugin.getProxy().getScheduler().schedule(plugin, () -> {
PlayerLocker.unlock(player);
}, 5, TimeUnit.SECONDS);

View File

@ -34,7 +34,7 @@ public class StatusManager {
storage.forEach((k, v) -> v.setOutdated(true));
for (ServerSection section : plugin.getSectionManager().getSections().values()) {
for (ServerInfo server : section.getMappedServers()) {
for (ServerInfo server : section.getServers()) {
if (stopped) {
break;
}

View File

@ -1,25 +1,30 @@
package com.jaimemartz.playerbalancer.section;
import com.google.common.base.Preconditions;
import com.jaimemartz.playerbalancer.PlayerBalancer;
import com.jaimemartz.playerbalancer.settings.props.features.BalancerProps;
import com.jaimemartz.playerbalancer.utils.FixedAdapter;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.api.scheduler.ScheduledTask;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.net.InetSocketAddress;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class SectionManager {
private final PlayerBalancer plugin;
private final BalancerProps props;
private ScheduledTask updateTask;
private ServerSection principal;
private final Map<String, ServerSection> sections = new HashMap<>();
private final Map<ServerInfo, ServerSection> servers = new HashMap<>();
public SectionManager(PlayerBalancer plugin) {
this.props = plugin.getSettings().getBalancerProps();
this.plugin = plugin;
}
@ -27,18 +32,18 @@ public class SectionManager {
plugin.getLogger().info("Loading sections from the config, this may take a while...");
long starting = System.currentTimeMillis();
plugin.getSettings().getBalancerProps().getSectionProps().forEach((name, prop) -> {
props.getSectionProps().forEach((name, prop) -> {
plugin.getLogger().info(String.format("Construction of section with name \"%s\"", name));
ServerSection object = new ServerSection(name, prop);
sections.put(name, object);
});
//todo validate principal section
//todo validate dummy sections
Preconditions.checkNotNull(this.getPrincipal(),
"Could not set principal section, there is no section named \"%s\"",
props.getPrincipalSectionName()
);
sections.forEach((name, section) -> {
//load section
});
sections.forEach(this::processSection);
long ending = System.currentTimeMillis() - starting;
plugin.getLogger().info(String.format("A total of %s section(s) have been loaded in %sms", sections.size(), ending));
@ -60,11 +65,11 @@ public class SectionManager {
}
});
principal = null;
if (updateTask != null) {
updateTask.cancel();
updateTask = null;
}
sections.clear();
servers.clear();
}
@ -114,16 +119,103 @@ public class SectionManager {
return getByServer(server.getInfo());
}
/**
* Calculates the position of a section in relation to other sections
* This is supposed to be called on section construction
* @param section the section we want to get the position of
* @return the position of {@param section}
*/
private int calculatePosition(ServerSection section) {
public void processSection(String sectionName, ServerSection section) throws RuntimeException {
plugin.getLogger().info(String.format("Loading section with name \"%s\"", sectionName));
Optional.ofNullable(section.getProps().getParentName()).ifPresent(parentName -> {
ServerSection parent = getByName(parentName);
if (parent == null) {
throw new IllegalArgumentException(String.format("The section \"%s\" has an invalid parent set", sectionName));
} else {
section.setParent(parent);
}
});
Optional.ofNullable(section.getParent()).ifPresent(parent -> {
if (parent.getProps().getParentName().equals(sectionName)) {
throw new IllegalStateException(String.format("The sections \"%s\" and \"%s\" are parents of each other",
sectionName,
section.getParent().getName()
));
}
});
Set<ServerInfo> servers = calculateServers(section);
section.getServers().addAll(servers);
//TODO move this to other stage
if (section.getProps().getProvider() != null) {
section.setInherited(false);
} else {
section.setInherited(true);
if (section.getImplicitProvider() != null) {
} else {
throw new IllegalStateException(String.format("The section \"%s\" does not have a provider", sectionName));
}
}
section.setPosition(calculatePosition(section));
Optional.ofNullable(section.getProps().getServerName()).ifPresent(serverName -> {
int port = (int) Math.floor(Math.random() * (0xFFFF + 1)); //Get a random valid port for our fake server
ServerInfo server = plugin.getProxy().constructServerInfo(
"@" + serverName,
new InetSocketAddress("0.0.0.0", port),
String.format("Server of Section %s", sectionName),
false);
section.setServer(server);
plugin.getSectionManager().register(server, section);
FixedAdapter.getFakeServers().put(server.getName(), server);
plugin.getProxy().getServers().put(server.getName(), server);
});
Optional.ofNullable(section.getProps().getCommand()).ifPresent(props -> {
SectionCommand command = new SectionCommand(plugin, props, section);
section.setCommand(command);
plugin.getProxy().getPluginManager().registerCommand(plugin, command);
});
section.setValid(true);
}
public Set<ServerInfo> calculateServers(ServerSection section) {
Set<ServerInfo> results = new HashSet<>();
section.getProps().getServerEntries().forEach(entry -> {
Pattern pattern = Pattern.compile(entry);
AtomicBoolean matches = new AtomicBoolean(false);
plugin.getProxy().getServers().forEach((name, server) -> {
Matcher matcher = pattern.matcher(name);
if (matcher.matches()) {
plugin.getLogger().info(String.format("Found a match with \"%s\" for entry \"%s\"", name, entry));
results.add(server);
register(server, section);
matches.set(true);
}
});
if (!matches.get()) {
plugin.getLogger().warning(String.format("Could not match any servers with the entry \"%s\"", entry));
}
});
plugin.getLogger().info(String.format("Recognized %s server(s) out of %s entries on the section \"%s\"",
servers.size(),
section.getProps().getServerEntries(),
section.getName()
));
return results;
}
public int calculatePosition(ServerSection section) {
ServerSection principal = this.getPrincipal();
ServerSection current = section;
//Calculate above principal
int iterations = 0;
ServerSection current = section;
while (current != null) {
if (current == principal) {
return iterations;
@ -150,29 +242,32 @@ public class SectionManager {
return iterations;
}
public ServerSection getPrincipal() {
return principal;
}
public boolean isPrincipal(ServerSection section) {
return section.equals(principal);
}
public boolean isDummy(ServerSection section) {
BalancerProps props = plugin.getSettings().getBalancerProps();
return props.getDummySectionNames().contains(section.getName());
List<String> dummySections = props.getDummySectionNames();
return dummySections.contains(section.getName());
}
public boolean isReiterative(ServerSection section) {
BalancerProps props = plugin.getSettings().getBalancerProps();
return props.getReiterativeSectionNames().contains(section.getName());
List<String> reiterativeSections = props.getReiterativeSectionNames();
return reiterativeSections.contains(section.getName());
}
public Optional<ServerSection> getBind(Map<String, String> rules, ServerSection section) {
String bind = rules.get(section.getName());
ServerSection res = this.getByName(bind);
return Optional.ofNullable(res);
}
//maybe store this as a variable?
public ServerSection getPrincipal() {
return getByName(props.getPrincipalSectionName());
}
public boolean isPrincipal(ServerSection section) {
return getPrincipal().equals(section);
}
public Map<String, ServerSection> getSections() {
return sections;
}
public Optional<ServerSection> getBind(Map<String, String> rules, ServerSection section) {
return Optional.ofNullable(getByName(rules.get(section.getName())));
}
}

View File

@ -2,9 +2,12 @@ package com.jaimemartz.playerbalancer.section;
import com.jaimemartz.playerbalancer.connection.ProviderType;
import com.jaimemartz.playerbalancer.settings.props.shared.SectionProps;
import com.jaimemartz.playerbalancer.utils.AlphanumComparator;
import net.md_5.bungee.api.config.ServerInfo;
import java.util.List;
import java.util.Collections;
import java.util.Set;
import java.util.TreeSet;
public class ServerSection {
private final String name;
@ -16,15 +19,16 @@ public class ServerSection {
private ServerInfo server;
private SectionCommand command;
private List<ServerInfo> mappedServers;
private List<ServerInfo> sortedServers;
private Set<ServerInfo> servers;
private boolean valid = false;
public ServerSection(String name, SectionProps props) {
this.name = name;
this.props = props;
AlphanumComparator<ServerInfo> comparator = new AlphanumComparator<>();
this.servers = Collections.synchronizedSortedSet(new TreeSet<>(comparator));
}
public String getName() {
@ -59,11 +63,11 @@ public class ServerSection {
this.parent = parent;
}
public ProviderType getEffectiveProvider() {
return inherited ? parent.getEffectiveProvider() : props.getProvider();
public ProviderType getImplicitProvider() {
return inherited ? parent.getImplicitProvider() : props.getProvider();
}
public void setProvider(ProviderType provider) {
public void setExplicitProvider(ProviderType provider) {
props.setProvider(provider);
inherited = false;
}
@ -84,20 +88,12 @@ public class ServerSection {
this.command = command;
}
public List<ServerInfo> getMappedServers() {
return mappedServers;
public void addServer(ServerInfo server) {
servers.add(server);
}
public void setMappedServers(List<ServerInfo> mappedServers) {
this.mappedServers = mappedServers;
}
public List<ServerInfo> getSortedServers() {
return sortedServers;
}
public void setSortedServers(List<ServerInfo> sortedServers) {
this.sortedServers = sortedServers;
public Set<ServerInfo> getServers() {
return servers;
}
public boolean isValid() {
@ -118,8 +114,7 @@ public class ServerSection {
", position=" + position +
", server=" + server +
", command=" + command +
", mappedServers=" + mappedServers +
", sortedServers=" + sortedServers +
", servers=" + servers +
", valid=" + valid +
'}';
}

View File

@ -20,7 +20,7 @@ public class ServerCheckerProps {
@Setting
private int interval;
@Setting("marker-descs")
@Setting(value = "marker-descs")
private List<String> markerDescs;
@Setting(value = "debug-info")

View File

@ -39,7 +39,7 @@ import java.util.Comparator;
* Use the static "sort" method from the java.util.Collections class:
* Collections.sort(your list, new AlphanumComparator());
*/
public final class AlphanumComparator implements Comparator
public final class AlphanumComparator<T> implements Comparator<T>
{
private final boolean isDigit(char ch)
{

View File

@ -46,12 +46,12 @@ features {
# You can use regex to match a set of servers instead of adding each server
# Providers you can use:
# NONE: Returns no server
# DIRECT: Returns the only server in the list
# LOCALIZED: Returns the server that matches a region (testing)
# LOWEST: Returns the server with the least players online
# RANDOM: Returns a random server
# PROGRESSIVE: Returns the first server that is not full
# NONE: Returns no server (no one will be able to connect to this section)
# DIRECT: Returns the only server that is in the list
# BALANCED: Returns a server between the ones with the least players online
# LOWEST: Returns the first server found with the least players online
# RANDOM: Returns a server selected by a RNG algorithm (random)
# PROGRESSIVE: Returns the first server found that is not full
# FILLER: Returns the server with the most players online that is not full
sections {
auth-lobbies {
@ -112,7 +112,7 @@ features {
dummy-sections=[]
# Reiterative sections remember the server the player connected to previously
# The plugin will keep connecting the player to that server until changes
# The plugin will keep connecting the player to that server until he changes
reiterative-sections=[]
}