diff --git a/Bukkit/build.gradle b/Bukkit/build.gradle index 4c9abac..164c4e5 100644 --- a/Bukkit/build.gradle +++ b/Bukkit/build.gradle @@ -5,7 +5,7 @@ archivesBaseName = rootProject.name + '-Bukkit' dependencies { implementation 'co.aikar:acf-paper:0.5.0-SNAPSHOT' - implementation 'org.bstats:bstats-bukkit:1.8' + implementation 'org.bstats:bstats-bukkit:2.2.1' implementation project(":Common") compileOnly 'com.destroystokyo.paper:paper-api:1.16.4-R0.1-SNAPSHOT' } @@ -18,7 +18,7 @@ processResources { } shadowJar { - relocate 'org.bstats.bukkit', dependencyDir + '.bstats' + relocate 'org.bstats', dependencyDir + '.bstats' relocate 'co.aikar.commands', dependencyDir + '.acf' relocate 'co.aikar.locales', dependencyDir + '.locales' } diff --git a/Bukkit/src/main/java/net/frankheijden/serverutils/bukkit/entities/BukkitResourceProvider.java b/Bukkit/src/main/java/net/frankheijden/serverutils/bukkit/entities/BukkitResourceProvider.java index 03ee610..435d493 100644 --- a/Bukkit/src/main/java/net/frankheijden/serverutils/bukkit/entities/BukkitResourceProvider.java +++ b/Bukkit/src/main/java/net/frankheijden/serverutils/bukkit/entities/BukkitResourceProvider.java @@ -3,7 +3,7 @@ package net.frankheijden.serverutils.bukkit.entities; import java.io.File; import java.io.InputStream; import net.frankheijden.serverutils.bukkit.ServerUtils; -import net.frankheijden.serverutils.common.config.YamlConfig; +import net.frankheijden.serverutils.common.config.ServerUtilsConfig; import net.frankheijden.serverutils.common.providers.ResourceProvider; public class BukkitResourceProvider implements ResourceProvider { @@ -20,12 +20,12 @@ public class BukkitResourceProvider implements ResourceProvider { } @Override - public YamlConfig load(InputStream is) { + public ServerUtilsConfig load(InputStream is) { return new BukkitYamlConfig(is); } @Override - public YamlConfig load(File file) { + public ServerUtilsConfig load(File file) { return new BukkitYamlConfig(file); } } diff --git a/Bukkit/src/main/java/net/frankheijden/serverutils/bukkit/entities/BukkitYamlConfig.java b/Bukkit/src/main/java/net/frankheijden/serverutils/bukkit/entities/BukkitYamlConfig.java index c951285..d10794b 100644 --- a/Bukkit/src/main/java/net/frankheijden/serverutils/bukkit/entities/BukkitYamlConfig.java +++ b/Bukkit/src/main/java/net/frankheijden/serverutils/bukkit/entities/BukkitYamlConfig.java @@ -8,11 +8,11 @@ import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; -import net.frankheijden.serverutils.common.config.YamlConfig; +import net.frankheijden.serverutils.common.config.ServerUtilsConfig; import org.bukkit.configuration.MemorySection; import org.bukkit.configuration.file.YamlConfiguration; -public class BukkitYamlConfig implements YamlConfig { +public class BukkitYamlConfig implements ServerUtilsConfig { private final MemorySection config; private File file = null; diff --git a/Bungee/build.gradle b/Bungee/build.gradle index b056f5f..101c2f2 100644 --- a/Bungee/build.gradle +++ b/Bungee/build.gradle @@ -9,7 +9,7 @@ repositories { dependencies { implementation 'co.aikar:acf-bungee:0.5.0-SNAPSHOT' - implementation 'org.bstats:bstats-bungeecord:1.8' + implementation 'org.bstats:bstats-bungeecord:2.2.1' implementation project(":Common") compileOnly 'net.md-5:bungeecord-api:1.16-R0.5-SNAPSHOT' } @@ -22,7 +22,7 @@ processResources { } shadowJar { - relocate 'org.bstats.bungeecord', dependencyDir + '.bstats' + relocate 'org.bstats', dependencyDir + '.bstats' relocate 'co.aikar.commands', dependencyDir + '.acf' relocate 'co.aikar.locales', dependencyDir + '.locales' } diff --git a/Bungee/src/main/java/net/frankheijden/serverutils/bungee/commands/CommandServerUtils.java b/Bungee/src/main/java/net/frankheijden/serverutils/bungee/commands/CommandServerUtils.java index cf5d132..a076a5e 100644 --- a/Bungee/src/main/java/net/frankheijden/serverutils/bungee/commands/CommandServerUtils.java +++ b/Bungee/src/main/java/net/frankheijden/serverutils/bungee/commands/CommandServerUtils.java @@ -3,6 +3,8 @@ package net.frankheijden.serverutils.bungee.commands; import static net.frankheijden.serverutils.common.config.Messenger.sendMessage; import co.aikar.commands.BaseCommand; +import co.aikar.commands.RegisteredCommand; +import co.aikar.commands.RootCommand; import co.aikar.commands.annotation.CommandAlias; import co.aikar.commands.annotation.CommandCompletion; import co.aikar.commands.annotation.CommandPermission; @@ -61,16 +63,21 @@ public class CommandServerUtils extends BaseCommand { FormatBuilder builder = FormatBuilder.create(Messenger.getMessage("serverutils.help.format")) .orderedKeys("%command%", "%subcommand%", "%help%"); - plugin.getCommandManager().getRegisteredRootCommands().stream() - .filter(c -> !ALIASES.contains(c.getCommandName().toLowerCase())) - .forEach(rootCommand -> { - builder.add(rootCommand.getCommandName(), "", rootCommand.getDescription()); - rootCommand.getSubCommands().forEach((str, cmd) -> { - if (cmd.getPrefSubCommand().isEmpty()) return; - builder.add(rootCommand.getCommandName(), " " + cmd.getPrefSubCommand(), cmd.getHelpText()); - }); - }); + Set rootCommands = new HashSet<>(); + for (RootCommand root : plugin.getCommandManager().getRegisteredRootCommands()) { + String rootName = root.getDefCommand().getName(); + if (!rootCommands.add(rootName)) continue; + builder.add(rootName, "", root.getDescription()); + + Set subCommands = new HashSet<>(); + for (RegisteredCommand sub : root.getSubCommands().values()) { + String name = sub.getPrefSubCommand().toLowerCase(); + if (name.isEmpty()) continue; + if (!subCommands.add(name)) continue; + builder.add(rootName, " " + name, sub.getHelpText()); + } + } builder.sendTo(sender); Messenger.sendMessage(sender, "serverutils.help.footer"); } diff --git a/Bungee/src/main/java/net/frankheijden/serverutils/bungee/entities/BungeeResourceProvider.java b/Bungee/src/main/java/net/frankheijden/serverutils/bungee/entities/BungeeResourceProvider.java index 9b39587..f4f688f 100644 --- a/Bungee/src/main/java/net/frankheijden/serverutils/bungee/entities/BungeeResourceProvider.java +++ b/Bungee/src/main/java/net/frankheijden/serverutils/bungee/entities/BungeeResourceProvider.java @@ -4,7 +4,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import net.frankheijden.serverutils.bungee.ServerUtils; -import net.frankheijden.serverutils.common.config.YamlConfig; +import net.frankheijden.serverutils.common.config.ServerUtilsConfig; import net.frankheijden.serverutils.common.providers.ResourceProvider; public class BungeeResourceProvider implements ResourceProvider { @@ -21,12 +21,12 @@ public class BungeeResourceProvider implements ResourceProvider { } @Override - public YamlConfig load(InputStream is) { + public ServerUtilsConfig load(InputStream is) { return new BungeeYamlConfig(is); } @Override - public YamlConfig load(File file) { + public ServerUtilsConfig load(File file) { try { return new BungeeYamlConfig(file); } catch (IOException ex) { diff --git a/Bungee/src/main/java/net/frankheijden/serverutils/bungee/entities/BungeeYamlConfig.java b/Bungee/src/main/java/net/frankheijden/serverutils/bungee/entities/BungeeYamlConfig.java index 933b624..49673df 100644 --- a/Bungee/src/main/java/net/frankheijden/serverutils/bungee/entities/BungeeYamlConfig.java +++ b/Bungee/src/main/java/net/frankheijden/serverutils/bungee/entities/BungeeYamlConfig.java @@ -7,12 +7,12 @@ import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; -import net.frankheijden.serverutils.common.config.YamlConfig; +import net.frankheijden.serverutils.common.config.ServerUtilsConfig; import net.md_5.bungee.config.Configuration; import net.md_5.bungee.config.ConfigurationProvider; import net.md_5.bungee.config.YamlConfiguration; -public class BungeeYamlConfig implements YamlConfig { +public class BungeeYamlConfig implements ServerUtilsConfig { private static final ConfigurationProvider provider = ConfigurationProvider.getProvider(YamlConfiguration.class); private final Configuration config; diff --git a/Common/src/main/java/net/frankheijden/serverutils/common/config/Config.java b/Common/src/main/java/net/frankheijden/serverutils/common/config/Config.java index d6bb679..fcdb0f7 100644 --- a/Common/src/main/java/net/frankheijden/serverutils/common/config/Config.java +++ b/Common/src/main/java/net/frankheijden/serverutils/common/config/Config.java @@ -3,7 +3,7 @@ package net.frankheijden.serverutils.common.config; /** * The general common config class. */ -public class Config extends YamlResource { +public class Config extends ServerUtilsResource { private static Config instance; diff --git a/Common/src/main/java/net/frankheijden/serverutils/common/config/Messenger.java b/Common/src/main/java/net/frankheijden/serverutils/common/config/Messenger.java index 57b586b..9d9f795 100644 --- a/Common/src/main/java/net/frankheijden/serverutils/common/config/Messenger.java +++ b/Common/src/main/java/net/frankheijden/serverutils/common/config/Messenger.java @@ -8,7 +8,7 @@ import net.frankheijden.serverutils.common.utils.StringUtils; /** * The general common messenger class. */ -public class Messenger extends YamlResource { +public class Messenger extends ServerUtilsResource { private static Messenger instance; private static final ServerUtilsPlugin plugin = ServerUtilsApp.getPlugin(); diff --git a/Common/src/main/java/net/frankheijden/serverutils/common/config/YamlConfig.java b/Common/src/main/java/net/frankheijden/serverutils/common/config/ServerUtilsConfig.java similarity index 79% rename from Common/src/main/java/net/frankheijden/serverutils/common/config/YamlConfig.java rename to Common/src/main/java/net/frankheijden/serverutils/common/config/ServerUtilsConfig.java index 3510938..1c975b4 100644 --- a/Common/src/main/java/net/frankheijden/serverutils/common/config/YamlConfig.java +++ b/Common/src/main/java/net/frankheijden/serverutils/common/config/ServerUtilsConfig.java @@ -6,9 +6,9 @@ import java.util.List; import java.util.Map; /** - * A wrap for a Yaml Configuration file. + * A wrap for a Configuration file. */ -public interface YamlConfig { +public interface ServerUtilsConfig { /** * Retrieves the value at a given path. @@ -69,7 +69,7 @@ public interface YamlConfig { * @param def The defaults to copy values over from. * @param conf The configuration to copy the defaults to. */ - static void addDefaults(YamlConfig def, YamlConfig conf) { + static void addDefaults(ServerUtilsConfig def, ServerUtilsConfig conf) { addDefaults(def, conf, ""); } @@ -79,13 +79,13 @@ public interface YamlConfig { * @param conf The configuration to copy the defaults to. * @param root The current root path of the iteration. */ - static void addDefaults(YamlConfig def, YamlConfig conf, String root) { + static void addDefaults(ServerUtilsConfig def, ServerUtilsConfig conf, String root) { if (def == null) return; for (String key : def.getKeys()) { String newKey = (root.isEmpty() ? "" : root + ".") + key; Object value = def.get(key); - if (value instanceof YamlConfig) { - addDefaults((YamlConfig) value, conf, newKey); + if (value instanceof ServerUtilsConfig) { + addDefaults((ServerUtilsConfig) value, conf, newKey); } else if (conf.get(newKey) == null) { conf.set(newKey, value); } @@ -95,22 +95,22 @@ public interface YamlConfig { /** * Removes unused keys from the configuration. */ - static void removeOldKeys(YamlConfig def, YamlConfig conf) { + static void removeOldKeys(ServerUtilsConfig def, ServerUtilsConfig conf) { removeOldKeys(def, conf, ""); } /** * Removes unused keys from the configuration, starting from the root node. */ - static void removeOldKeys(YamlConfig def, YamlConfig conf, String root) { + static void removeOldKeys(ServerUtilsConfig def, ServerUtilsConfig conf, String root) { if (def == null) return; for (String key : conf.getKeys()) { String defKey = (root.isEmpty() ? "" : root + ".") + key; Object value = conf.get(key); if (def.get(defKey) == null) { conf.set(key, null); - } else if (value instanceof YamlConfig) { - removeOldKeys(def, (YamlConfig) value, defKey); + } else if (value instanceof ServerUtilsConfig) { + removeOldKeys(def, (ServerUtilsConfig) value, defKey); } } } @@ -121,9 +121,9 @@ public interface YamlConfig { * @param conf The Configuration where the defaults will be applied to. * @return The loaded Configuration of the file with defaults. */ - static YamlConfig init(YamlConfig def, YamlConfig conf) { - YamlConfig.addDefaults(def, conf); - YamlConfig.removeOldKeys(def, conf); + static ServerUtilsConfig init(ServerUtilsConfig def, ServerUtilsConfig conf) { + ServerUtilsConfig.addDefaults(def, conf); + ServerUtilsConfig.removeOldKeys(def, conf); try { conf.save(); diff --git a/Common/src/main/java/net/frankheijden/serverutils/common/config/YamlResource.java b/Common/src/main/java/net/frankheijden/serverutils/common/config/ServerUtilsResource.java similarity index 76% rename from Common/src/main/java/net/frankheijden/serverutils/common/config/YamlResource.java rename to Common/src/main/java/net/frankheijden/serverutils/common/config/ServerUtilsResource.java index 841844d..17be7e2 100644 --- a/Common/src/main/java/net/frankheijden/serverutils/common/config/YamlResource.java +++ b/Common/src/main/java/net/frankheijden/serverutils/common/config/ServerUtilsResource.java @@ -7,13 +7,13 @@ import net.frankheijden.serverutils.common.entities.ServerUtilsPlugin; import net.frankheijden.serverutils.common.providers.ResourceProvider; /** - * A class which provides functionality for loading and setting defaults of Yaml Configurations. + * A class which provides functionality for loading and setting defaults of Configurations. */ -public class YamlResource { +public class ServerUtilsResource { private static final ServerUtilsPlugin plugin = ServerUtilsApp.getPlugin(); - private final YamlConfig config; + private final ServerUtilsConfig config; /** * Creates a new YamlResource instance. @@ -21,18 +21,18 @@ public class YamlResource { * @param fileName The destination file. * @param resource The resource from the jar file. */ - public YamlResource(String fileName, String resource) { + public ServerUtilsResource(String fileName, String resource) { ResourceProvider provider = plugin.getResourceProvider(); InputStream is = provider.getResource(resource); File file = plugin.copyResourceIfNotExists(fileName, resource); - config = YamlConfig.init(provider.load(is), provider.load(file)); + config = ServerUtilsConfig.init(provider.load(is), provider.load(file)); } /** * Retrieves the YamlConfig of this resource. * @return The YamlConfig. */ - public YamlConfig getConfig() { + public ServerUtilsConfig getConfig() { return config; } } diff --git a/Common/src/main/java/net/frankheijden/serverutils/common/providers/ResourceProvider.java b/Common/src/main/java/net/frankheijden/serverutils/common/providers/ResourceProvider.java index af5ca56..fd1d47b 100644 --- a/Common/src/main/java/net/frankheijden/serverutils/common/providers/ResourceProvider.java +++ b/Common/src/main/java/net/frankheijden/serverutils/common/providers/ResourceProvider.java @@ -2,13 +2,13 @@ package net.frankheijden.serverutils.common.providers; import java.io.File; import java.io.InputStream; -import net.frankheijden.serverutils.common.config.YamlConfig; +import net.frankheijden.serverutils.common.config.ServerUtilsConfig; public interface ResourceProvider { InputStream getResource(String resource); - YamlConfig load(InputStream is); + ServerUtilsConfig load(InputStream is); - YamlConfig load(File file); + ServerUtilsConfig load(File file); } diff --git a/Common/src/main/java/net/frankheijden/serverutils/common/tasks/UpdateCheckerTask.java b/Common/src/main/java/net/frankheijden/serverutils/common/tasks/UpdateCheckerTask.java index 5e746e8..66f009d 100644 --- a/Common/src/main/java/net/frankheijden/serverutils/common/tasks/UpdateCheckerTask.java +++ b/Common/src/main/java/net/frankheijden/serverutils/common/tasks/UpdateCheckerTask.java @@ -12,7 +12,7 @@ import java.util.logging.Level; import net.frankheijden.serverutils.common.ServerUtilsApp; import net.frankheijden.serverutils.common.config.Config; import net.frankheijden.serverutils.common.config.Messenger; -import net.frankheijden.serverutils.common.config.YamlConfig; +import net.frankheijden.serverutils.common.config.ServerUtilsConfig; import net.frankheijden.serverutils.common.entities.LoadResult; import net.frankheijden.serverutils.common.entities.Result; import net.frankheijden.serverutils.common.entities.ServerCommandSender; @@ -27,7 +27,7 @@ import net.frankheijden.serverutilsupdater.common.Updater; public class UpdateCheckerTask implements Runnable { private static final ServerUtilsPlugin plugin = ServerUtilsApp.getPlugin(); - private static final YamlConfig config = Config.getInstance().getConfig(); + private static final ServerUtilsConfig config = Config.getInstance().getConfig(); private final ServerCommandSender sender; private final boolean download; diff --git a/Velocity/build.gradle b/Velocity/build.gradle new file mode 100644 index 0000000..c61aebe --- /dev/null +++ b/Velocity/build.gradle @@ -0,0 +1,34 @@ +plugins { + id 'net.kyori.blossom' version '1.3.0' +} + +group = rootProject.group + '.velocity' +String dependencyDir = group + '.dependencies' +version = rootProject.version +archivesBaseName = rootProject.name + '-Velocity' + +repositories { + maven { url 'https://nexus.velocitypowered.com/repository/maven-public/' } + maven { url 'https://libraries.minecraft.net' } +} + +dependencies { + implementation 'com.github.AlexProgrammerDE.commands:acf-velocity:b0792607db' + implementation 'org.bstats:bstats-velocity:2.2.1' + implementation project(":Common") + compileOnly 'com.velocitypowered:velocity-api:3.0.0' + compileOnly 'com.velocitypowered:velocity-brigadier:1.0.0-SNAPSHOT' + compileOnly 'com.electronwill.night-config:toml:3.6.3' + annotationProcessor 'com.velocitypowered:velocity-api:3.0.0' +} + +shadowJar { + relocate 'org.bstats', dependencyDir + '.bstats' + relocate 'co.aikar.commands', dependencyDir + '.acf' + relocate 'co.aikar.locales', dependencyDir + '.locales' +} + +blossom { + replaceTokenIn('src/main/java/net/frankheijden/serverutils/velocity/ServerUtils.java') + replaceToken '${version}', version +} diff --git a/Velocity/src/main/java/net/frankheijden/serverutils/velocity/ServerUtils.java b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/ServerUtils.java new file mode 100644 index 0000000..a5c81b2 --- /dev/null +++ b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/ServerUtils.java @@ -0,0 +1,159 @@ +package net.frankheijden.serverutils.velocity; + +import co.aikar.commands.CommandCompletions; +import co.aikar.commands.VelocityCommandCompletionContext; +import co.aikar.commands.VelocityCommandManager; +import com.google.inject.Inject; +import com.google.inject.name.Named; +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.Plugin; +import com.velocitypowered.api.plugin.PluginContainer; +import com.velocitypowered.api.plugin.annotation.DataDirectory; +import com.velocitypowered.api.proxy.ProxyServer; +import java.io.IOException; +import java.nio.file.Path; +import net.frankheijden.serverutils.common.ServerUtilsApp; +import net.frankheijden.serverutils.common.config.Config; +import net.frankheijden.serverutils.common.config.Messenger; +import net.frankheijden.serverutils.velocity.commands.CommandPlugins; +import net.frankheijden.serverutils.velocity.commands.CommandServerUtils; +import net.frankheijden.serverutils.velocity.entities.VelocityPlugin; +import net.frankheijden.serverutils.velocity.managers.VelocityPluginCommandManager; +import net.frankheijden.serverutils.velocity.managers.VelocityPluginManager; +import net.frankheijden.serverutils.velocity.reflection.RVelocityCommandManager; +import org.bstats.velocity.Metrics; +import org.slf4j.Logger; + +@Plugin( + id = "serverutils", + name = "ServerUtils", + version = "${version}", + description = "A server utility", + url = "https://github.com/FrankHeijden/ServerUtils", + authors = "FrankHeijden" +) +public class ServerUtils { + + private static ServerUtils instance; + private static final String CONFIG_RESOURCE = "velocity-config.toml"; + private static final String MESSAGES_RESOURCE = "velocity-messages.toml"; + private static final String PLUGIN_COMMANDS_CACHE = ".pluginCommandsCache.json"; + + private VelocityPlugin plugin; + private VelocityCommandManager commandManager; + + @Inject + private ProxyServer proxy; + + @Inject + private Logger logger; + + @Inject + @DataDirectory + private Path dataDirectory; + + @Inject + private Metrics.Factory metricsFactory; + + @Inject + @Named("serverutils") + private PluginContainer pluginContainer; + + private final VelocityPluginCommandManager pluginCommandManager; + + /** + * Initialises ServerUtils. + */ + @Inject + public ServerUtils(ProxyServer proxy, @DataDirectory Path dataDirectory) { + try { + this.pluginCommandManager = VelocityPluginCommandManager.load(dataDirectory.resolve(PLUGIN_COMMANDS_CACHE)); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + + RVelocityCommandManager.proxyRegistrars( + proxy, + getClass().getClassLoader(), + (container, meta) -> pluginCommandManager.getPluginCommands().putAll( + container.getDescription().getId(), + meta.getAliases() + ) + ); + } + + /** + * Initialises and enables ServerUtils. + */ + @Subscribe + public void onEnable(ProxyInitializeEvent event) { + instance = this; + + this.plugin = new VelocityPlugin(this); + ServerUtilsApp.init(this, plugin); + + metricsFactory.make(this, ServerUtilsApp.BSTATS_METRICS_ID); + + this.commandManager = new VelocityCommandManager(proxy, this); + commandManager.registerCommand(new CommandPlugins()); + commandManager.registerCommand(new CommandServerUtils(this)); + + VelocityPluginManager manager = plugin.getPluginManager(); + CommandCompletions completions = commandManager.getCommandCompletions(); + completions.registerAsyncCompletion("plugins", context -> manager.getPluginNames()); + completions.registerAsyncCompletion("pluginJars", context -> manager.getPluginFileNames()); + completions.registerAsyncCompletion("commands", context -> manager.getCommands()); + + reload(); + plugin.enable(); + + ServerUtilsApp.tryCheckForUpdates(); + } + + /** + * De-initialises and disables ServerUtils. + */ + @Subscribe + public void onDisable(ProxyShutdownEvent event) { + try { + pluginCommandManager.save(); + } catch (IOException ex) { + ex.printStackTrace(); + } + } + + public static ServerUtils getInstance() { + return instance; + } + + public ProxyServer getProxy() { + return proxy; + } + + public Logger getLogger() { + return logger; + } + + public Path getDataDirectory() { + return dataDirectory; + } + + public VelocityCommandManager getCommandManager() { + return commandManager; + } + + public VelocityPlugin getPlugin() { + return plugin; + } + + public VelocityPluginCommandManager getPluginCommandManager() { + return pluginCommandManager; + } + + public void reload() { + new Config("config.toml", CONFIG_RESOURCE); + new Messenger("messages.toml", MESSAGES_RESOURCE); + } +} diff --git a/Velocity/src/main/java/net/frankheijden/serverutils/velocity/commands/CommandPlugins.java b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/commands/CommandPlugins.java new file mode 100644 index 0000000..dbc3696 --- /dev/null +++ b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/commands/CommandPlugins.java @@ -0,0 +1,46 @@ +package net.frankheijden.serverutils.velocity.commands; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Default; +import co.aikar.commands.annotation.Description; +import com.velocitypowered.api.command.CommandSource; +import net.frankheijden.serverutils.common.commands.Plugins; +import net.frankheijden.serverutils.common.config.Messenger; +import net.frankheijden.serverutils.velocity.managers.VelocityPluginManager; +import net.frankheijden.serverutils.velocity.utils.VelocityUtils; + +@CommandAlias("vpl|vplugins|velocitypl") +public class CommandPlugins extends BaseCommand { + + private static final VelocityPluginManager manager = VelocityPluginManager.get(); + + /** + * Sends the plugin list to the sender. + * The `-v` flag will output the plugins with version. + * The `-m` flag will also output modules in the plugin list. + * @param sender The sender of the command. + */ + @Default + @CommandCompletion("-v") + @CommandPermission("serverutils.plugins") + @Description("Shows the plugins of this proxy.") + public void onPlugins(CommandSource sender, String... args) { + boolean version = contains(args, "-v"); + Plugins.sendPlugins(VelocityUtils.wrap(sender), manager.getPluginsSorted(), pl -> { + String ver = version ? Messenger.getMessage("serverutils.plugins.version", + "%version%", pl.getDescription().getVersion().orElse("")) : ""; + return Messenger.getMessage("serverutils.plugins.format", + "%plugin%", pl.getDescription().getId()) + ver; + }); + } + + private static boolean contains(String[] arr, String val) { + for (String s : arr) { + if (s.equalsIgnoreCase(val)) return true; + } + return false; + } +} diff --git a/Velocity/src/main/java/net/frankheijden/serverutils/velocity/commands/CommandServerUtils.java b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/commands/CommandServerUtils.java new file mode 100644 index 0000000..fc6084e --- /dev/null +++ b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/commands/CommandServerUtils.java @@ -0,0 +1,275 @@ +package net.frankheijden.serverutils.velocity.commands; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.RegisteredCommand; +import co.aikar.commands.RootCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Default; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.tree.CommandNode; +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.plugin.PluginContainer; +import com.velocitypowered.api.plugin.PluginDescription; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import net.frankheijden.serverutils.common.config.Messenger; +import net.frankheijden.serverutils.common.entities.AbstractResult; +import net.frankheijden.serverutils.common.entities.CloseableResult; +import net.frankheijden.serverutils.common.entities.Result; +import net.frankheijden.serverutils.common.entities.ServerCommandSender; +import net.frankheijden.serverutils.common.utils.FormatBuilder; +import net.frankheijden.serverutils.common.utils.HexUtils; +import net.frankheijden.serverutils.common.utils.ListBuilder; +import net.frankheijden.serverutils.common.utils.ListFormat; +import net.frankheijden.serverutils.velocity.ServerUtils; +import net.frankheijden.serverutils.velocity.entities.VelocityLoadResult; +import net.frankheijden.serverutils.velocity.reflection.RVelocityCommandManager; +import net.frankheijden.serverutils.velocity.utils.VelocityUtils; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; + +@CommandAlias("vsu|vserverutils") +public class CommandServerUtils extends BaseCommand { + + private static final Set ALIASES; + + static { + ALIASES = new HashSet<>(); + ALIASES.add("vserverutils"); + ALIASES.add("vplugins"); + ALIASES.add("velocitypl"); + } + + private final ServerUtils plugin; + + public CommandServerUtils(ServerUtils plugin) { + this.plugin = plugin; + } + + /** + * Shows the help page to the sender. + * @param source The sender of the command. + */ + @Default + @Subcommand("help") + @CommandPermission("serverutils.help") + @Description("Shows a help page with a few commands.") + public void onHelp(CommandSource source) { + ServerCommandSender sender = VelocityUtils.wrap(source); + Messenger.sendMessage(sender, "serverutils.help.header"); + + FormatBuilder builder = FormatBuilder.create(Messenger.getMessage("serverutils.help.format")) + .orderedKeys("%command%", "%subcommand%", "%help%"); + + Set rootCommands = new HashSet<>(); + for (RootCommand root : plugin.getCommandManager().getRegisteredRootCommands()) { + String rootName = root.getDefCommand().getName(); + if (!rootCommands.add(rootName)) continue; + builder.add(rootName, "", root.getDescription()); + + Set subCommands = new HashSet<>(); + for (RegisteredCommand sub : root.getSubCommands().values()) { + String name = sub.getPrefSubCommand().toLowerCase(); + if (name.isEmpty()) continue; + if (!subCommands.add(name)) continue; + builder.add(rootName, " " + name, sub.getHelpText()); + } + } + builder.sendTo(sender); + Messenger.sendMessage(sender, "serverutils.help.footer"); + } + + /** + * Reloads the configurations of ServerUtils. + * @param sender The sender of the command. + */ + @Subcommand("reload") + @CommandPermission("serverutils.reload") + @Description("Reloads the ServerUtils plugin.") + public void onReload(CommandSource sender) { + plugin.reload(); + Messenger.sendMessage(VelocityUtils.wrap(sender), "serverutils.success", + "%action%", "reload", + "%what%", "ServerUtils Bungee configurations"); + } + + /** + * Loads the specified plugin on the proxy. + * @param source The sender of the command. + * @param jarFile The filename of the plugin in the plugins/ directory. + */ + @Subcommand("loadplugin|lp") + @CommandCompletion("@pluginJars") + @CommandPermission("serverutils.loadplugin") + @Description("Loads the specified jar file as a plugin.") + public void onLoadPlugin(CommandSource source, String jarFile) { + ServerCommandSender sender = VelocityUtils.wrap(source); + + VelocityLoadResult loadResult = plugin.getPlugin().getPluginManager().loadPlugin(jarFile); + if (!loadResult.isSuccess()) { + loadResult.getResult().sendTo(sender, "load", jarFile); + return; + } + + PluginContainer container = loadResult.get(); + Result result = plugin.getPlugin().getPluginManager().enablePlugin(container); + result.sendTo(sender, "load", container.getDescription().getId()); + } + + /** + * Unloads the specified plugin from the proxy. + * @param source The sender of the command. + * @param pluginName The plugin name. + */ + @Subcommand("unloadplugin|up") + @CommandCompletion("@plugins") + @CommandPermission("serverutils.unloadplugin") + @Description("Disables and unloads the specified plugin.") + public void onUnloadPlugin(CommandSource source, String pluginName) { + CloseableResult result = plugin.getPlugin().getPluginManager().unloadPlugin(pluginName); + result.getResult().sendTo(VelocityUtils.wrap(source), "unload", pluginName); + result.tryClose(); + } + + /** + * Reloads the specified plugin on the proxy. + * @param sender The sender of the command. + * @param pluginName The plugin name. + */ + @Subcommand("reloadplugin|rp") + @CommandCompletion("@plugins") + @CommandPermission("serverutils.reloadplugin") + @Description("Reloads a specified plugin.") + public void onReloadPlugin(CommandSource sender, String pluginName) { + // Wacky method to have the resources needed for the reload in memory, in case of a self reload. + HexUtils utils = new HexUtils(); + Map section = Messenger.getInstance().getConfig().getMap("serverutils"); + String result = plugin.getPlugin().getPluginManager().reloadPlugin(pluginName).toString(); + + String msg = (String) section.get(result.toLowerCase()); + if (msg != null && !msg.isEmpty()) { + sender.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(utils.convertHexString( + msg.replace("%action%", "reload").replace("%what%", pluginName)))); + } + } + + /** + * Watches the given plugin and reloads it when a change is detected to the file. + * @param source The sender of the command. + * @param pluginName The plugin name. + */ + @Subcommand("watchplugin|wp") + @CommandCompletion("@plugins") + @CommandPermission("serverutils.watchplugin") + @Description("Watches the specified plugin for changes.") + public void onWatchPlugin(CommandSource source, String pluginName) { + ServerCommandSender sender = VelocityUtils.wrap(source); + AbstractResult result = plugin.getPlugin().getPluginManager().watchPlugin(sender, pluginName); + result.sendTo(sender, "watch", pluginName); + } + + /** + * Stops watching the given plugin. + * @param source The sender of the command. + * @param pluginName The plugin name. + */ + @Subcommand("unwatchplugin|uwp") + @CommandCompletion("@plugins") + @CommandPermission("serverutils.watchplugin") + @Description("Stops watching the specified plugin for changes.") + public void onUnwatchPlugin(CommandSource source, String pluginName) { + AbstractResult result = plugin.getPlugin().getPluginManager().unwatchPlugin(pluginName); + result.sendTo(VelocityUtils.wrap(source), "unwatch", pluginName); + } + + /** + * Shows information about the specified plugin. + * @param source The sender of the command. + * @param pluginName The plugin name. + */ + @Subcommand("plugininfo|pi") + @CommandCompletion("@plugins") + @CommandPermission("serverutils.plugininfo") + @Description("Shows information about the specified plugin.") + public void onPluginInfo(CommandSource source, String pluginName) { + ServerCommandSender sender = VelocityUtils.wrap(source); + + Optional container = plugin.getProxy().getPluginManager().getPlugin(pluginName); + if (!container.isPresent()) { + Result.NOT_EXISTS.sendTo(sender, "fetch", pluginName); + return; + } + + PluginDescription desc = container.get().getDescription(); + String format = Messenger.getMessage("serverutils.plugininfo.format"); + String listFormatString = Messenger.getMessage("serverutils.plugininfo.list_format"); + String seperator = Messenger.getMessage("serverutils.plugininfo.seperator"); + String lastSeperator = Messenger.getMessage("serverutils.plugininfo.last_seperator"); + + ListFormat listFormat = str -> listFormatString.replace("%value%", str); + + Messenger.sendMessage(sender, "serverutils.plugininfo.header"); + + FormatBuilder builder = FormatBuilder.create(format) + .orderedKeys("%key%", "%value%") + .add("Id", desc.getId()) + .add("Name", desc.getName().orElse(null)) + .add("Version", desc.getVersion().orElse("")) + .add("Author" + (desc.getAuthors().size() == 1 ? "" : "s"), ListBuilder.create(desc.getAuthors()) + .format(listFormat) + .seperator(seperator) + .lastSeperator(lastSeperator) + .toString()) + .add("Description", desc.getDescription().orElse(null)) + .add("URL", desc.getUrl().orElse(null)) + .add("Source", desc.getSource().map(Path::toString).orElse(null)) + .add("Dependencies", ListBuilder.create(desc.getDependencies()) + .format(d -> listFormat.format(d.getId())) + .seperator(seperator) + .lastSeperator(lastSeperator) + .toString()); + + builder.sendTo(sender); + Messenger.sendMessage(sender, "serverutils.plugininfo.footer"); + } + + /** + * Shows information about a provided command. + * @param source The sender of the command. + * @param command The command to lookup. + */ + @Subcommand("commandinfo|ci") + @CommandCompletion("@commands") + @CommandPermission("serverutils.commandinfo") + @Description("Shows information about the specified command.") + public void onCommandInfo(CommandSource source, String command) { + ServerCommandSender sender = VelocityUtils.wrap(source); + + CommandDispatcher dispatcher = RVelocityCommandManager.getDispatcher( + plugin.getProxy().getCommandManager() + ); + + CommandNode node = dispatcher.getRoot().getChild(command); + if (node == null) { + Messenger.sendMessage(sender, "serverutils.commandinfo.not_exists"); + return; + } + + String format = Messenger.getMessage("serverutils.commandinfo.format"); + + Messenger.sendMessage(sender, "serverutils.commandinfo.header"); + FormatBuilder builder = FormatBuilder.create(format) + .orderedKeys("%key%", "%value%") + .add("Name", node.getName()) + .add("Plugin", plugin.getPluginCommandManager().findPluginId(command).orElse("")); + + builder.sendTo(sender); + Messenger.sendMessage(sender, "serverutils.commandinfo.footer"); + } +} diff --git a/Velocity/src/main/java/net/frankheijden/serverutils/velocity/entities/VelocityChatProvider.java b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/entities/VelocityChatProvider.java new file mode 100644 index 0000000..1332e27 --- /dev/null +++ b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/entities/VelocityChatProvider.java @@ -0,0 +1,36 @@ +package net.frankheijden.serverutils.velocity.entities; + +import net.frankheijden.serverutils.common.entities.ServerCommandSender; +import net.frankheijden.serverutils.common.providers.ChatProvider; +import net.frankheijden.serverutils.common.utils.HexUtils; +import net.frankheijden.serverutils.velocity.ServerUtils; +import net.frankheijden.serverutils.velocity.utils.VelocityUtils; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; + +public class VelocityChatProvider extends ChatProvider { + + private final ServerUtils plugin; + + public VelocityChatProvider(ServerUtils plugin) { + this.plugin = plugin; + } + + @Override + public ServerCommandSender getConsoleSender() { + return VelocityUtils.wrap(plugin.getProxy().getConsoleCommandSource()); + } + + @Override + public String color(String str) { + return HexUtils.convertHexString(str); + } + + @Override + public void broadcast(String permission, String message) { + Component msg = LegacyComponentSerializer.legacyAmpersand().deserialize(message); + plugin.getProxy().getAllPlayers().stream() + .filter(p -> p.hasPermission(permission)) + .forEach(p -> p.sendMessage(msg)); + } +} diff --git a/Velocity/src/main/java/net/frankheijden/serverutils/velocity/entities/VelocityCommandSender.java b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/entities/VelocityCommandSender.java new file mode 100644 index 0000000..c543d94 --- /dev/null +++ b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/entities/VelocityCommandSender.java @@ -0,0 +1,34 @@ +package net.frankheijden.serverutils.velocity.entities; + +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.proxy.Player; +import net.frankheijden.serverutils.common.entities.ServerCommandSender; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; + +public class VelocityCommandSender implements ServerCommandSender { + + private final CommandSource source; + + public VelocityCommandSender(CommandSource source) { + this.source = source; + } + + @Override + public void sendMessage(String message) { + source.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(message)); + } + + @Override + public boolean hasPermission(String permission) { + return source.hasPermission(permission); + } + + /** + * Whether or not the given instance is a player. + * @return Boolean true or false. + */ + @Override + public boolean isPlayer() { + return source instanceof Player; + } +} diff --git a/Velocity/src/main/java/net/frankheijden/serverutils/velocity/entities/VelocityLoadResult.java b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/entities/VelocityLoadResult.java new file mode 100644 index 0000000..68d8bba --- /dev/null +++ b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/entities/VelocityLoadResult.java @@ -0,0 +1,20 @@ +package net.frankheijden.serverutils.velocity.entities; + +import com.velocitypowered.api.plugin.PluginContainer; +import net.frankheijden.serverutils.common.entities.LoadResult; +import net.frankheijden.serverutils.common.entities.Result; + +public class VelocityLoadResult extends LoadResult { + + public VelocityLoadResult(PluginContainer obj, Result result) { + super(obj, result); + } + + public VelocityLoadResult(PluginContainer obj) { + super(obj); + } + + public VelocityLoadResult(Result result) { + super(result); + } +} diff --git a/Velocity/src/main/java/net/frankheijden/serverutils/velocity/entities/VelocityPlugin.java b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/entities/VelocityPlugin.java new file mode 100644 index 0000000..5904083 --- /dev/null +++ b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/entities/VelocityPlugin.java @@ -0,0 +1,72 @@ +package net.frankheijden.serverutils.velocity.entities; + +import com.velocitypowered.api.plugin.PluginDescription; +import java.io.File; +import java.nio.file.Path; +import java.util.logging.Logger; +import net.frankheijden.serverutils.common.entities.ServerUtilsPlugin; +import net.frankheijden.serverutils.velocity.ServerUtils; +import net.frankheijden.serverutils.velocity.managers.VelocityPluginManager; +import net.frankheijden.serverutils.velocity.managers.VelocityTaskManager; +import net.frankheijden.serverutils.velocity.reflection.RJavaPluginLoader; + +public class VelocityPlugin extends ServerUtilsPlugin { + + private final ServerUtils plugin; + private final VelocityPluginManager pluginManager; + private final VelocityTaskManager taskManager; + private final VelocityResourceProvider resourceProvider; + private final VelocityChatProvider chatProvider; + + /** + * Creates a new BungeePlugin instance of ServerUtils. + * @param plugin The ServerUtils plugin. + */ + public VelocityPlugin(ServerUtils plugin) { + this.plugin = plugin; + this.pluginManager = new VelocityPluginManager(); + this.taskManager = new VelocityTaskManager(plugin); + this.resourceProvider = new VelocityResourceProvider(plugin); + this.chatProvider = new VelocityChatProvider(plugin); + } + + @Override + @SuppressWarnings("unchecked") + public VelocityPluginManager getPluginManager() { + return pluginManager; + } + + @Override + @SuppressWarnings("unchecked") + public VelocityTaskManager getTaskManager() { + return taskManager; + } + + @Override + public VelocityResourceProvider getResourceProvider() { + return resourceProvider; + } + + @Override + public VelocityChatProvider getChatProvider() { + return chatProvider; + } + + @Override + public Logger getLogger() { + return Logger.getLogger(plugin.getLogger().getName()); + } + + @Override + public File getDataFolder() { + return plugin.getDataDirectory().toFile(); + } + + @Override + @SuppressWarnings("unchecked") + public PluginDescription fetchUpdaterData() { + Path pluginPath = pluginManager.getPluginFile("ServerUtils").toPath(); + Object javaPluginLoader = RJavaPluginLoader.newInstance(plugin.getProxy(), pluginPath.getParent()); + return RJavaPluginLoader.loadPluginDescription(javaPluginLoader, pluginPath); + } +} diff --git a/Velocity/src/main/java/net/frankheijden/serverutils/velocity/entities/VelocityResourceProvider.java b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/entities/VelocityResourceProvider.java new file mode 100644 index 0000000..c061a92 --- /dev/null +++ b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/entities/VelocityResourceProvider.java @@ -0,0 +1,46 @@ +package net.frankheijden.serverutils.velocity.entities; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import net.frankheijden.serverutils.common.config.ServerUtilsConfig; +import net.frankheijden.serverutils.common.providers.ResourceProvider; +import net.frankheijden.serverutils.velocity.ServerUtils; + +public class VelocityResourceProvider implements ResourceProvider { + + private final ServerUtils plugin; + + public VelocityResourceProvider(ServerUtils plugin) { + this.plugin = plugin; + } + + @Override + public InputStream getResource(String resource) { + return plugin.getClass().getClassLoader().getResourceAsStream(resource); + } + + @Override + public ServerUtilsConfig load(InputStream is) { + try { + Path tmpFile = Files.createTempFile(null, null); + Files.copy(is, tmpFile, StandardCopyOption.REPLACE_EXISTING); + + VelocityTomlConfig config = new VelocityTomlConfig(tmpFile.toFile()); + Files.delete(tmpFile); + + return config; + } catch (IOException ex) { + ex.printStackTrace(); + } + return null; + } + + @Override + public ServerUtilsConfig load(File file) { + return new VelocityTomlConfig(file); + } +} diff --git a/Velocity/src/main/java/net/frankheijden/serverutils/velocity/entities/VelocityTomlConfig.java b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/entities/VelocityTomlConfig.java new file mode 100644 index 0000000..88d2ef5 --- /dev/null +++ b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/entities/VelocityTomlConfig.java @@ -0,0 +1,85 @@ +package net.frankheijden.serverutils.velocity.entities; + +import com.electronwill.nightconfig.core.CommentedConfig; +import com.electronwill.nightconfig.core.file.CommentedFileConfig; +import com.electronwill.nightconfig.toml.TomlFormat; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import net.frankheijden.serverutils.common.config.ServerUtilsConfig; + +public class VelocityTomlConfig implements ServerUtilsConfig { + + private final CommentedConfig config; + private final File file; + + /** + * Creates a new VelocityTomlConfig instance. + */ + public VelocityTomlConfig(File file) { + CommentedFileConfig config = CommentedFileConfig.of(file, TomlFormat.instance()); + config.load(); + + this.config = config; + this.file = file; + } + + public VelocityTomlConfig(CommentedConfig config, File file) { + this.config = config; + this.file = file; + } + + @Override + public Object get(String path) { + Object obj = config.get(path); + if (obj instanceof CommentedConfig) { + return new VelocityTomlConfig((CommentedConfig) obj, file); + } + return obj; + } + + @Override + public List getStringList(String path) { + return config.getOrElse(path, new ArrayList<>()); + } + + @Override + public Map getMap(String path) { + CommentedConfig section = config.get(path); + if (section == null) return new HashMap<>(); + return section.valueMap(); + } + + @Override + public void set(String path, Object value) { + config.set(path, value); + } + + @Override + public String getString(String path) { + return config.get(path); + } + + @Override + public boolean getBoolean(String path) { + return config.get(path); + } + + @Override + public Collection getKeys() { + return config.valueMap().keySet(); + } + + @Override + public void save() throws IOException { + if (config instanceof CommentedFileConfig) { + ((CommentedFileConfig) config).save(); + } else { + throw new IOException("Config is not an instance of CommentedFileConfig!"); + } + } +} diff --git a/Velocity/src/main/java/net/frankheijden/serverutils/velocity/managers/VelocityPluginCommandManager.java b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/managers/VelocityPluginCommandManager.java new file mode 100644 index 0000000..dafbcb5 --- /dev/null +++ b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/managers/VelocityPluginCommandManager.java @@ -0,0 +1,72 @@ +package net.frankheijden.serverutils.velocity.managers; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.Collection; +import java.util.Map; +import java.util.Optional; + +public class VelocityPluginCommandManager { + + private static final Gson gson = new Gson(); + + private final Multimap pluginCommands; + private final Path path; + + public VelocityPluginCommandManager(Path path) { + this.pluginCommands = Multimaps.synchronizedSetMultimap(HashMultimap.create()); + this.path = path; + } + + /** + * Loads and constructs a new {@link VelocityPluginCommandManager} from the given {@link Path}. + */ + public static VelocityPluginCommandManager load(Path path) throws IOException { + VelocityPluginCommandManager manager = new VelocityPluginCommandManager(path); + if (Files.exists(path)) { + Map> rawMap = gson.fromJson( + Files.newBufferedReader(path), + new TypeToken>>(){}.getType() + ); + rawMap.forEach(manager.pluginCommands::putAll); + } + + return manager; + } + + /** + * Attempts to find the plugin id for a given command alias. + */ + public Optional findPluginId(String alias) { + for (Map.Entry entry : pluginCommands.entries()) { + if (alias.equals(entry.getValue())) { + return Optional.of(entry.getKey()); + } + } + return Optional.empty(); + } + + public Multimap getPluginCommands() { + return pluginCommands; + } + + /** + * Saves the map to the {@link Path} it was loaded from. + */ + public void save() throws IOException { + Files.write( + path, + gson.toJson(pluginCommands.asMap()).getBytes(StandardCharsets.UTF_8), + StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING + ); + } +} diff --git a/Velocity/src/main/java/net/frankheijden/serverutils/velocity/managers/VelocityPluginManager.java b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/managers/VelocityPluginManager.java new file mode 100644 index 0000000..8dc4c61 --- /dev/null +++ b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/managers/VelocityPluginManager.java @@ -0,0 +1,283 @@ +package net.frankheijden.serverutils.velocity.managers; + +import com.google.common.base.Joiner; +import com.google.inject.AbstractModule; +import com.google.inject.Module; +import com.google.inject.name.Names; +import com.mojang.brigadier.tree.CommandNode; +import com.velocitypowered.api.command.CommandManager; +import com.velocitypowered.api.event.EventManager; +import com.velocitypowered.api.event.permission.PermissionsSetupEvent; +import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; +import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; +import com.velocitypowered.api.permission.PermissionFunction; +import com.velocitypowered.api.plugin.PluginContainer; +import com.velocitypowered.api.plugin.PluginDescription; +import com.velocitypowered.api.plugin.PluginManager; +import com.velocitypowered.api.plugin.meta.PluginDependency; +import com.velocitypowered.api.proxy.ConsoleCommandSource; +import com.velocitypowered.api.proxy.ProxyServer; +import com.velocitypowered.api.scheduler.ScheduledTask; +import java.io.Closeable; +import java.io.File; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import net.frankheijden.serverutils.common.entities.CloseableResult; +import net.frankheijden.serverutils.common.entities.Result; +import net.frankheijden.serverutils.common.managers.AbstractPluginManager; +import net.frankheijden.serverutils.velocity.ServerUtils; +import net.frankheijden.serverutils.velocity.entities.VelocityLoadResult; +import net.frankheijden.serverutils.velocity.reflection.RJavaPluginLoader; +import net.frankheijden.serverutils.velocity.reflection.RVelocityCommandManager; +import net.frankheijden.serverutils.velocity.reflection.RVelocityConsole; +import net.frankheijden.serverutils.velocity.reflection.RVelocityEventManager; +import net.frankheijden.serverutils.velocity.reflection.RVelocityPluginContainer; +import net.frankheijden.serverutils.velocity.reflection.RVelocityPluginManager; +import net.frankheijden.serverutils.velocity.reflection.RVelocityScheduler; + +public class VelocityPluginManager extends AbstractPluginManager { + + private static VelocityPluginManager instance; + private final ProxyServer proxy; + + public VelocityPluginManager() { + instance = this; + this.proxy = ServerUtils.getInstance().getProxy(); + } + + public static VelocityPluginManager get() { + return instance; + } + + @Override + public VelocityLoadResult loadPlugin(String pluginFile) { + return loadPlugin(new File(getPluginsFolder(), pluginFile)); + } + + @Override + public VelocityLoadResult loadPlugin(File file) { + if (!file.exists() || file.isDirectory()) return new VelocityLoadResult(Result.NOT_EXISTS); + + Object javaPluginLoader = RJavaPluginLoader.newInstance(proxy, file.toPath().getParent()); + PluginDescription candidate = RJavaPluginLoader.loadPluginDescription(javaPluginLoader, file.toPath()); + if (proxy.getPluginManager().isLoaded(candidate.getId())) return new VelocityLoadResult(Result.ALREADY_LOADED); + + for (PluginDependency dependency : candidate.getDependencies()) { + if (!dependency.isOptional() && !proxy.getPluginManager().isLoaded(dependency.getId())) { + ServerUtils.getInstance().getLogger().error( + "Can't load plugin {} due to missing dependency {}", + candidate.getId(), + dependency.getId() + ); + return new VelocityLoadResult(Result.UNKNOWN_DEPENDENCY.arg(dependency.getId())); + } + } + + PluginDescription realPlugin = RJavaPluginLoader.loadPlugin(javaPluginLoader, candidate); + PluginContainer container = RVelocityPluginContainer.newInstance(realPlugin); + + return new VelocityLoadResult(container); + } + + @Override + public Result enablePlugin(PluginContainer container) { + if (proxy.getPluginManager().isLoaded(container.getDescription().getId())) return Result.ALREADY_ENABLED; + + Object javaPluginLoader = RJavaPluginLoader.newInstance( + proxy, + container.getDescription().getSource().map(Path::getParent).orElse(null) + ); + PluginDescription realPlugin = container.getDescription(); + Module module = RJavaPluginLoader.createModule(javaPluginLoader, container); + + AbstractModule commonModule = new AbstractModule() { + @Override + protected void configure() { + bind(ProxyServer.class).toInstance(proxy); + bind(PluginManager.class).toInstance(proxy.getPluginManager()); + bind(EventManager.class).toInstance(proxy.getEventManager()); + bind(CommandManager.class).toInstance(proxy.getCommandManager()); + for (PluginContainer container : proxy.getPluginManager().getPlugins()) { + bind(PluginContainer.class) + .annotatedWith(Names.named(container.getDescription().getId())) + .toInstance(container); + } + bind(PluginContainer.class) + .annotatedWith(Names.named(realPlugin.getId())) + .toInstance(container); + } + }; + + try { + RJavaPluginLoader.createPlugin(javaPluginLoader, container, module, commonModule); + } catch (Exception ex) { + ServerUtils.getInstance().getLogger().error( + String.format("Can't create plugin %s", container.getDescription().getId()), + ex + ); + return Result.ERROR; + } + + ServerUtils.getInstance().getLogger().info( + "Loaded plugin {} {} by {}", + realPlugin.getId(), + realPlugin.getVersion().orElse(""), + Joiner.on(", ").join(realPlugin.getAuthors()) + ); + + RVelocityPluginManager.registerPlugin(proxy.getPluginManager(), container); + container.getInstance().ifPresent(instance -> { + RVelocityEventManager.registerInternally(proxy.getEventManager(), container, instance); + RVelocityEventManager.fireForPlugin( + proxy.getEventManager(), + new ProxyInitializeEvent(), + instance + ).join(); + + ConsoleCommandSource console = proxy.getConsoleCommandSource(); + PermissionsSetupEvent event = new PermissionsSetupEvent( + console, + s -> PermissionFunction.ALWAYS_TRUE + ); + PermissionFunction permissionFunction = RVelocityEventManager.fireForPlugin( + proxy.getEventManager(), + event, + instance + ).join().createFunction(console); + + if (permissionFunction == null) { + ServerUtils.getInstance().getLogger().error( + "A plugin permission provider {} provided an invalid permission function for the console." + + " This is a bug in the plugin, not in Velocity." + + " Falling back to the default permission function.", + event.getProvider().getClass().getName() + ); + permissionFunction = PermissionFunction.ALWAYS_TRUE; + } + + RVelocityConsole.setPermissionFunction(console, permissionFunction); + }); + + return Result.SUCCESS; + } + + @Override + public Result disablePlugin(PluginContainer plugin) { + Object pluginInstance = plugin.getInstance().orElse(null); + if (pluginInstance == null) return Result.NOT_EXISTS; + + RVelocityEventManager.fireForPlugin( + proxy.getEventManager(), + pluginInstance, + new ProxyShutdownEvent() + ); + + return Result.SUCCESS; + } + + @Override + public Result reloadPlugin(String pluginName) { + Optional pluginOptional = proxy.getPluginManager().getPlugin(pluginName); + if (!pluginOptional.isPresent()) return Result.NOT_EXISTS; + return reloadPlugin(pluginOptional.get()); + } + + @Override + public Result reloadPlugin(PluginContainer plugin) { + CloseableResult result = unloadPlugin(plugin); + if (result.getResult() != Result.SUCCESS) return result.getResult(); + result.tryClose(); + + File file = getPluginFile(plugin.getDescription().getId()); + if (file == null) return Result.FILE_DELETED; + + VelocityLoadResult loadResult = loadPlugin(file); + if (!loadResult.isSuccess()) return loadResult.getResult(); + + return enablePlugin(loadResult.get()); + } + + @Override + public CloseableResult unloadPlugin(String pluginName) { + Optional pluginOptional = proxy.getPluginManager().getPlugin(pluginName); + if (!pluginOptional.isPresent()) return new CloseableResult(Result.NOT_EXISTS); + return unloadPlugin(pluginOptional.get()); + } + + @Override + public CloseableResult unloadPlugin(PluginContainer plugin) { + Optional pluginInstanceOptional = plugin.getInstance(); + if (!pluginInstanceOptional.isPresent()) return new CloseableResult(Result.INVALID_PLUGIN); + Object pluginInstance = pluginInstanceOptional.get(); + + proxy.getEventManager().unregisterListeners(pluginInstance); + for (ScheduledTask task : RVelocityScheduler.getTasksByPlugin(proxy.getScheduler()).removeAll(pluginInstance)) { + task.cancel(); + } + + String pluginId = plugin.getDescription().getId(); + VelocityPluginCommandManager pluginCommandManager = ServerUtils.getInstance().getPluginCommandManager(); + for (String alias : pluginCommandManager.getPluginCommands().removeAll(pluginId)) { + proxy.getCommandManager().unregister(alias); + } + + RVelocityPluginManager.getPlugins(proxy.getPluginManager()).remove(pluginId); + RVelocityPluginManager.getPluginInstances(proxy.getPluginManager()).remove(pluginInstance); + + List closeables = new ArrayList<>(); + + ClassLoader loader = pluginInstance.getClass().getClassLoader(); + if (loader instanceof Closeable) { + closeables.add((Closeable) loader); + } + + return new CloseableResult(closeables); + } + + @Override + public List getPlugins() { + return new ArrayList<>(proxy.getPluginManager().getPlugins()); + } + + @Override + public String getPluginName(PluginContainer plugin) { + return plugin.getDescription().getId(); + } + + @Override + public File getPluginFile(PluginContainer plugin) { + return plugin.getDescription().getSource() + .map(Path::toFile) + .orElse(null); + } + + @Override + public File getPluginFile(String pluginName) { + Object javaPluginLoader = RJavaPluginLoader.newInstance(instance.proxy, getPluginsFolder().toPath()); + + for (File file : getPluginJars()) { + PluginDescription desc = RJavaPluginLoader.loadPluginDescription(javaPluginLoader, file.toPath()); + + if (desc.getId().equals(pluginName)) { + return file; + } + } + return null; + } + + @Override + public PluginContainer getPlugin(String pluginName) { + return proxy.getPluginManager().getPlugin(pluginName).orElse(null); + } + + @Override + public Set getCommands() { + return RVelocityCommandManager.getDispatcher(proxy.getCommandManager()).getRoot().getChildren().stream() + .map(CommandNode::getName) + .collect(Collectors.toSet()); + } +} diff --git a/Velocity/src/main/java/net/frankheijden/serverutils/velocity/managers/VelocityTaskManager.java b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/managers/VelocityTaskManager.java new file mode 100644 index 0000000..95964bd --- /dev/null +++ b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/managers/VelocityTaskManager.java @@ -0,0 +1,41 @@ +package net.frankheijden.serverutils.velocity.managers; + +import com.velocitypowered.api.scheduler.ScheduledTask; +import java.time.Duration; +import net.frankheijden.serverutils.common.managers.AbstractTaskManager; +import net.frankheijden.serverutils.velocity.ServerUtils; + +public class VelocityTaskManager extends AbstractTaskManager { + + private final ServerUtils plugin; + + public VelocityTaskManager(ServerUtils plugin) { + super(ScheduledTask::cancel); + this.plugin = plugin; + } + + @Override + protected ScheduledTask runTaskImpl(Runnable runnable) { + return runTaskAsynchronously(runnable); + } + + @Override + public ScheduledTask runTaskLater(Runnable runnable, long delay) { + return plugin.getProxy().getScheduler() + .buildTask(plugin, runnable) + .delay(Duration.ofMillis(delay * 50)) + .schedule(); + } + + @Override + protected ScheduledTask runTaskAsynchronouslyImpl(Runnable runnable) { + return plugin.getProxy().getScheduler() + .buildTask(plugin, runnable) + .schedule(); + } + + @Override + public void cancelTask(ScheduledTask task) { + task.cancel(); + } +} diff --git a/Velocity/src/main/java/net/frankheijden/serverutils/velocity/reflection/RJavaPluginLoader.java b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/reflection/RJavaPluginLoader.java new file mode 100644 index 0000000..5f2a375 --- /dev/null +++ b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/reflection/RJavaPluginLoader.java @@ -0,0 +1,59 @@ +package net.frankheijden.serverutils.velocity.reflection; + +import com.google.inject.Module; +import com.velocitypowered.api.plugin.PluginContainer; +import com.velocitypowered.api.plugin.PluginDescription; +import com.velocitypowered.api.proxy.ProxyServer; +import dev.frankheijden.minecraftreflection.ClassObject; +import dev.frankheijden.minecraftreflection.MinecraftReflection; +import java.lang.reflect.Array; +import java.nio.file.Path; + +public class RJavaPluginLoader { + + private static final MinecraftReflection reflection = MinecraftReflection + .of("com.velocitypowered.proxy.plugin.loader.java.JavaPluginLoader"); + + private RJavaPluginLoader() {} + + /** + * Constructs a new instance of a JavaPluginLoader. + */ + public static Object newInstance(ProxyServer proxy, Path baseDirectory) { + return reflection.newInstance( + ClassObject.of(ProxyServer.class, proxy), + ClassObject.of(Path.class, baseDirectory) + ); + } + + public static PluginDescription loadPluginDescription(Object javaPluginLoader, Path source) { + return reflection.invoke(javaPluginLoader, "loadPluginDescription", ClassObject.of(Path.class, source)); + } + + /** + * Loads the plugin from their candidate PluginDescription. + */ + public static PluginDescription loadPlugin(Object javaPluginLoader, PluginDescription candidate) { + return reflection.invoke( + javaPluginLoader, + "loadPlugin", + ClassObject.of(PluginDescription.class, candidate) + ); + } + + public static Module createModule(Object javaPluginLoader, PluginContainer container) { + return reflection.invoke(javaPluginLoader, "createModule", ClassObject.of(PluginContainer.class, container)); + } + + /** + * Creates the plugin. + */ + public static void createPlugin(Object javaPluginLoader, PluginContainer container, Module... modules) { + reflection.invoke( + javaPluginLoader, + "createPlugin", + ClassObject.of(PluginContainer.class, container), + ClassObject.of(Array.newInstance(Module.class, 0).getClass(), modules) + ); + } +} diff --git a/Velocity/src/main/java/net/frankheijden/serverutils/velocity/reflection/RVelocityCommandManager.java b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/reflection/RVelocityCommandManager.java new file mode 100644 index 0000000..4926a74 --- /dev/null +++ b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/reflection/RVelocityCommandManager.java @@ -0,0 +1,125 @@ +package net.frankheijden.serverutils.velocity.reflection; + +import com.mojang.brigadier.CommandDispatcher; +import com.velocitypowered.api.command.CommandManager; +import com.velocitypowered.api.command.CommandMeta; +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.plugin.PluginContainer; +import com.velocitypowered.api.proxy.ProxyServer; +import dev.frankheijden.minecraftreflection.MinecraftReflection; +import dev.frankheijden.minecraftreflection.Reflection; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiConsumer; +import net.frankheijden.serverutils.velocity.ServerUtils; +import net.frankheijden.serverutils.velocity.utils.ReflectionUtils; + +public class RVelocityCommandManager { + + private static final MinecraftReflection reflection = MinecraftReflection + .of("com.velocitypowered.proxy.command.VelocityCommandManager"); + + private RVelocityCommandManager() {} + + public static CommandDispatcher getDispatcher(CommandManager manager) { + return reflection.get(manager, "dispatcher"); + } + + /** + * Proxies the registrars. + */ + @SuppressWarnings("rawtypes") + public static void proxyRegistrars( + ProxyServer proxy, + ClassLoader loader, + BiConsumer registrationConsumer + ) { + List proxiedRegistrars = new ArrayList<>(); + + Class commandRegistrarClass; + try { + commandRegistrarClass = Class.forName("com.velocitypowered.proxy.command.registrar.CommandRegistrar"); + } catch (ClassNotFoundException ex) { + ex.printStackTrace(); + return; + } + + for (Object registrar : (List) reflection.get(proxy.getCommandManager(), "registrars")) { + proxiedRegistrars.add(Proxy.newProxyInstance( + loader, + new Class[]{ commandRegistrarClass }, + new CommandRegistrarInvocationHandler( + proxy, + registrar, + registrationConsumer + ) + )); + } + + Field registrarsField = Reflection.getAccessibleField(reflection.getClazz(), "registrars"); + ReflectionUtils.doPrivilegedWithUnsafe(unsafe -> { + long offset = unsafe.objectFieldOffset(registrarsField); + unsafe.putObject(proxy.getCommandManager(), offset, proxiedRegistrars); + }); + } + + public static final class CommandRegistrarInvocationHandler implements InvocationHandler { + + private final ProxyServer proxy; + private final Object commandRegistrar; + private final BiConsumer registrationConsumer; + + /** + * Constructs a new {@link CommandRegistrarInvocationHandler}. + */ + public CommandRegistrarInvocationHandler( + ProxyServer proxy, + Object commandRegistrar, + BiConsumer registrationConsumer + ) { + this.proxy = proxy; + this.commandRegistrar = commandRegistrar; + this.registrationConsumer = registrationConsumer; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + Object obj = method.invoke(commandRegistrar, args); + if (method.getName().equals("register")) { + handleRegisterMethod((CommandMeta) args[0]); + } + return obj; + } + + private void handleRegisterMethod(CommandMeta commandMeta) { + StackTraceElement[] elements = Thread.currentThread().getStackTrace(); + + // Skip the first four elements, which is our overhead here + for (int i = 4; i < elements.length; i++) { + Class clazz; + try { + clazz = Class.forName(elements[i].getClassName()); + } catch (ClassNotFoundException ex) { + continue; + } + + ClassLoader classLoader = clazz.getClassLoader(); + for (PluginContainer container : proxy.getPluginManager().getPlugins()) { + if (container.getInstance().filter(o -> o.getClass().getClassLoader() == classLoader).isPresent()) { + registrationConsumer.accept(container, commandMeta); + return; + } + } + } + + ServerUtils.getInstance().getLogger().warn( + "Couldn't find the registering plugin for the following aliases: {}", + commandMeta.getAliases() + ); + } + } +} diff --git a/Velocity/src/main/java/net/frankheijden/serverutils/velocity/reflection/RVelocityConsole.java b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/reflection/RVelocityConsole.java new file mode 100644 index 0000000..9268ee5 --- /dev/null +++ b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/reflection/RVelocityConsole.java @@ -0,0 +1,17 @@ +package net.frankheijden.serverutils.velocity.reflection; + +import com.velocitypowered.api.permission.PermissionFunction; +import com.velocitypowered.api.proxy.ConsoleCommandSource; +import dev.frankheijden.minecraftreflection.MinecraftReflection; + +public class RVelocityConsole { + + private static final MinecraftReflection reflection = MinecraftReflection + .of("com.velocitypowered.proxy.console.VelocityConsole"); + + private RVelocityConsole() {} + + public static void setPermissionFunction(ConsoleCommandSource velocityConsole, PermissionFunction function) { + reflection.set(velocityConsole, "permissionFunction", function); + } +} diff --git a/Velocity/src/main/java/net/frankheijden/serverutils/velocity/reflection/RVelocityEventManager.java b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/reflection/RVelocityEventManager.java new file mode 100644 index 0000000..f8a7d71 --- /dev/null +++ b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/reflection/RVelocityEventManager.java @@ -0,0 +1,90 @@ +package net.frankheijden.serverutils.velocity.reflection; + +import com.google.common.collect.Multimap; +import com.velocitypowered.api.event.EventHandler; +import com.velocitypowered.api.event.EventManager; +import com.velocitypowered.api.plugin.PluginContainer; +import dev.frankheijden.minecraftreflection.ClassObject; +import dev.frankheijden.minecraftreflection.MinecraftReflection; +import java.lang.reflect.Array; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +public class RVelocityEventManager { + + private static final MinecraftReflection reflection = MinecraftReflection + .of("com.velocitypowered.proxy.event.VelocityEventManager"); + + private RVelocityEventManager() {} + + @SuppressWarnings("rawtypes") + public static Multimap getHandlersByType(EventManager manager) { + return reflection.get(manager, "handlersByType"); + } + + /** + * Retrieves the registrations from a plugin for a specific event. + */ + @SuppressWarnings("unchecked") + public static List getRegistrationsByPlugin(EventManager manager, Object plugin, Class eventClass) { + return (List) getHandlersByType(manager).get(eventClass).stream() + .filter(r -> RHandlerRegistration.getPlugin(r).getInstance().orElse(null) == plugin) + .collect(Collectors.toList()); + } + + /** + * Registers the listener for a given plugin. + */ + public static void registerInternally(EventManager manager, PluginContainer container, Object listener) { + reflection.invoke( + manager, + "registerInternally", + ClassObject.of(PluginContainer.class, container), + ClassObject.of(Object.class, listener) + ); + } + + /** + * Fires an event specifically for one plugin. + */ + public static CompletableFuture fireForPlugin( + EventManager manager, + E event, + Object plugin + ) { + List registrations = getRegistrationsByPlugin(manager, plugin, event.getClass()); + CompletableFuture future = new CompletableFuture<>(); + + Object registrationsEmptyArray = Array.newInstance(RHandlerRegistration.reflection.getClazz(), 0); + Class registrationsArrayClass = registrationsEmptyArray.getClass(); + + reflection.invoke( + manager, + "fire", + ClassObject.of(CompletableFuture.class, future), + ClassObject.of(Object.class, event), + ClassObject.of(int.class, 0), + ClassObject.of(boolean.class, false), + ClassObject.of(registrationsArrayClass, registrations.toArray((Object[]) registrationsEmptyArray)) + ); + + return future; + } + + public static class RHandlerRegistration { + + private static final MinecraftReflection reflection = MinecraftReflection + .of("com.velocitypowered.proxy.event.VelocityEventManager$HandlerRegistration"); + + private RHandlerRegistration() {} + + public static PluginContainer getPlugin(Object registration) { + return reflection.get(registration, "plugin"); + } + + public static EventHandler getEventHandler(Object registration) { + return reflection.get(registration, "handler"); + } + } +} diff --git a/Velocity/src/main/java/net/frankheijden/serverutils/velocity/reflection/RVelocityPluginContainer.java b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/reflection/RVelocityPluginContainer.java new file mode 100644 index 0000000..23bb685 --- /dev/null +++ b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/reflection/RVelocityPluginContainer.java @@ -0,0 +1,18 @@ +package net.frankheijden.serverutils.velocity.reflection; + +import com.velocitypowered.api.plugin.PluginContainer; +import com.velocitypowered.api.plugin.PluginDescription; +import dev.frankheijden.minecraftreflection.ClassObject; +import dev.frankheijden.minecraftreflection.MinecraftReflection; + +public class RVelocityPluginContainer { + + private static final MinecraftReflection reflection = MinecraftReflection + .of("com.velocitypowered.proxy.plugin.loader.VelocityPluginContainer"); + + private RVelocityPluginContainer() {} + + public static PluginContainer newInstance(PluginDescription description) { + return reflection.newInstance(ClassObject.of(PluginDescription.class, description)); + } +} diff --git a/Velocity/src/main/java/net/frankheijden/serverutils/velocity/reflection/RVelocityPluginManager.java b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/reflection/RVelocityPluginManager.java new file mode 100644 index 0000000..585ee63 --- /dev/null +++ b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/reflection/RVelocityPluginManager.java @@ -0,0 +1,27 @@ +package net.frankheijden.serverutils.velocity.reflection; + +import com.velocitypowered.api.plugin.PluginContainer; +import com.velocitypowered.api.plugin.PluginManager; +import dev.frankheijden.minecraftreflection.ClassObject; +import dev.frankheijden.minecraftreflection.MinecraftReflection; +import java.util.Map; + +public class RVelocityPluginManager { + + private static final MinecraftReflection reflection = MinecraftReflection + .of("com.velocitypowered.proxy.plugin.VelocityPluginManager"); + + private RVelocityPluginManager() {} + + public static Map getPlugins(PluginManager manager) { + return reflection.get(manager, "plugins"); + } + + public static Map getPluginInstances(PluginManager manager) { + return reflection.get(manager, "pluginInstances"); + } + + public static void registerPlugin(PluginManager manager, PluginContainer container) { + reflection.invoke(manager, "registerPlugin", ClassObject.of(PluginContainer.class, container)); + } +} diff --git a/Velocity/src/main/java/net/frankheijden/serverutils/velocity/reflection/RVelocityScheduler.java b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/reflection/RVelocityScheduler.java new file mode 100644 index 0000000..71d04ee --- /dev/null +++ b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/reflection/RVelocityScheduler.java @@ -0,0 +1,18 @@ +package net.frankheijden.serverutils.velocity.reflection; + +import com.google.common.collect.Multimap; +import com.velocitypowered.api.scheduler.ScheduledTask; +import com.velocitypowered.api.scheduler.Scheduler; +import dev.frankheijden.minecraftreflection.MinecraftReflection; + +public class RVelocityScheduler { + + private static final MinecraftReflection reflection = MinecraftReflection + .of("com.velocitypowered.proxy.scheduler.VelocityScheduler"); + + private RVelocityScheduler() {} + + public static Multimap getTasksByPlugin(Scheduler scheduler) { + return reflection.get(scheduler, "tasksByPlugin"); + } +} diff --git a/Velocity/src/main/java/net/frankheijden/serverutils/velocity/utils/ReflectionUtils.java b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/utils/ReflectionUtils.java new file mode 100644 index 0000000..99c779c --- /dev/null +++ b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/utils/ReflectionUtils.java @@ -0,0 +1,41 @@ +package net.frankheijden.serverutils.velocity.utils; + +import dev.frankheijden.minecraftreflection.Reflection; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.function.Consumer; +import sun.misc.Unsafe; + +public class ReflectionUtils { + + private static MethodHandle theUnsafeFieldMethodHandle; + + static { + try { + theUnsafeFieldMethodHandle = MethodHandles.lookup().unreflectGetter(Reflection.getAccessibleField( + Unsafe.class, + "theUnsafe" + )); + } catch (Throwable th) { + th.printStackTrace(); + } + } + + private ReflectionUtils() {} + + /** + * Performs a privileged action while accessing {@link Unsafe}. + */ + public static void doPrivilegedWithUnsafe(Consumer privilegedAction) { + AccessController.doPrivileged((PrivilegedAction) () -> { + try { + privilegedAction.accept((Unsafe) theUnsafeFieldMethodHandle.invoke()); + } catch (Throwable th) { + th.printStackTrace(); + } + return null; + }); + } +} diff --git a/Velocity/src/main/java/net/frankheijden/serverutils/velocity/utils/VelocityUtils.java b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/utils/VelocityUtils.java new file mode 100644 index 0000000..f8058c8 --- /dev/null +++ b/Velocity/src/main/java/net/frankheijden/serverutils/velocity/utils/VelocityUtils.java @@ -0,0 +1,12 @@ +package net.frankheijden.serverutils.velocity.utils; + +import com.velocitypowered.api.command.CommandSource; +import net.frankheijden.serverutils.common.entities.ServerCommandSender; +import net.frankheijden.serverutils.velocity.entities.VelocityCommandSender; + +public class VelocityUtils { + + public static ServerCommandSender wrap(CommandSource source) { + return new VelocityCommandSender(source); + } +} diff --git a/Velocity/src/main/resources/velocity-config.toml b/Velocity/src/main/resources/velocity-config.toml new file mode 100644 index 0000000..500ec8e --- /dev/null +++ b/Velocity/src/main/resources/velocity-config.toml @@ -0,0 +1,7 @@ +[settings] +check-updates-boot = true +check-updates-login = false +download-updates-boot = false +download-updates-login = false +install-updates-boot = false +install-updates-login = false diff --git a/Velocity/src/main/resources/velocity-messages.toml b/Velocity/src/main/resources/velocity-messages.toml new file mode 100644 index 0000000..5dec827 --- /dev/null +++ b/Velocity/src/main/resources/velocity-messages.toml @@ -0,0 +1,67 @@ +[serverutils] +success = "&3Successfully %action%ed &b%what%&3!" +warning = "&3Successfully %action%ed &b%what%&3, but with warnings." +error = "&cAn error occurred while %action%ing &4%what%&c, please check the console!" +not_exists = "&cAn error occurred while %action%ing &4%what%&c, plugin does not exist!" +not_enabled = "&cAn error occurred while %action%ing &4%what%&c, plugin is not enabled!" +already_loaded = "&cAn error occurred while %action%ing &4%what%&c, plugin is already loaded!" +already_enabled = "&cAn error occurred while %action%ing &4%what%&c, plugin is already enabled!" +already_disabled = "&cAn error occurred while %action%ing &4%what%&c, plugin is already disabled!" +file_deleted = "&cAccessing the jar file while %action%ing &4%what%&c went wrong, plugin has been deleted!" +invalid_description = "&cAn error occurred while %action%ing &4%what%&c, plugin doesn't have a valid description, please check the console!" +invalid_plugin = "&cAn error occurred while %action%ing &4%what%&c, plugin is invalid!" +unknown_dependency = "&cAn error occurred while %action%ing &4%what%&c, plugin has a dependeny which is not loaded: &4%arg%" + +[serverutils.watcher] +start = "&3Started watching &b%what%&3!" +change = "&3Change detected for plugin &b%what%&3, reloading now..." +stopped = "&3Stopped watching &b%what%&3!" +not_watching = "&cWe aren't watching that plugin!" + +[serverutils.update] +available = """ +&8&m--------=&r&8[ &b&lServerUtils Velocity Update&r &8]&m=--------- + &3Current version: &b%old% + &3New version: &b%new% + &3Release info: &b%info% +&8&m-------------------------------------------------""" +downloading = """ +&8&m--------=&r&8[ &b&lServerUtils Velocity Update&r &8]&m=---------- + &3A new version of ServerUtils will be downloaded and installed after a restart! + &3Current version: &b%old% + &3New version: &b%new% + &3Release info: &b%info% +&8&m-------------------------------------------------""" +download_failed = "&cFailed to download version %new% of ServerUtils. Please update manually." +download_success = "&3ServerUtils has been downloaded and will be installed on the next restart." + +[serverutils.help] +header = "&8&m---------=&r&8[ &b&lServerUtils Velocity Help&r &8]&m=---------" +format = "&8/&3%command%&b%subcommand% &f(&7%help%&f)" +footer = "&8&m-------------------------------------------------" + +[serverutils.plugins] +header = "&8&m--------=&r&8[ &b&lServerUtils Velocity Plugins&r &8]&m=-------" +prefix = " &3Plugins &8(&a%count%&8)&b: " +format = "&3%plugin%" +seperator = "&b, " +last_seperator = " &band " +version = " &8(&a%version%&8)" +footer = "&8&m-------------------------------------------------" + +[serverutils.plugininfo] +header = "&8&m------=&r&8[ &b&lServerUtils Velocity PluginInfo&r &8]&m=------" +format = " &3%key%&8: &b%value%" +list_format = "&b%value%" +seperator = "&8, " +last_seperator = " &8and " +footer = "&8&m-------------------------------------------------" + +[serverutils.commandinfo] +header = "&8&m-----=&r&8[ &b&lServerUtils Velocity CommandInfo&r &8]&m=------" +format = " &3%key%&8: &b%value%" +list_format = "&b%value%" +seperator = "&8, " +last_seperator = " &8and " +footer = "&8&m-------------------------------------------------" +not_exists = "&cThat command is not a valid registered command." diff --git a/build.gradle b/build.gradle index 10d5713..398cad3 100644 --- a/build.gradle +++ b/build.gradle @@ -62,6 +62,7 @@ dependencies { implementation project(path: ':Common', configuration: 'shadow') implementation project(path: ':Bukkit', configuration: 'shadow') implementation project(path: ':Bungee', configuration: 'shadow') + implementation project(path: ':Velocity', configuration: 'shadow') } shadowJar { @@ -71,7 +72,12 @@ shadowJar { } def outputTasks() { - ["shadowJar", ":Bukkit:shadowJar", ":Bungee:shadowJar"].stream().map({ tasks.findByPath(it) }) + [ + "shadowJar", + ":Bukkit:shadowJar", + ":Bungee:shadowJar", + ":Velocity:shadowJar", + ].stream().map({ tasks.findByPath(it) }) } task copyJars(type: Copy) { diff --git a/settings.gradle b/settings.gradle index 7729be8..fdaf6d3 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,3 +2,4 @@ rootProject.name = 'ServerUtils' include 'Common' include 'Bukkit' include 'Bungee' +include 'Velocity'