/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.royaldev.royalcommands; import com.griefcraft.lwc.LWCPlugin; import com.onarandombox.MultiverseCore.MultiverseCore; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.block.Block; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandMap; import org.bukkit.command.CommandSender; import org.bukkit.command.PluginCommand; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.scheduler.BukkitScheduler; import org.jetbrains.annotations.Contract; import org.kitteh.tag.TagAPI; import org.kitteh.vanish.VanishPlugin; import org.royaldev.royalcommands.api.RApiMain; import org.royaldev.royalcommands.configuration.Configuration; import org.royaldev.royalcommands.configuration.PlayerConfiguration; import org.royaldev.royalcommands.configuration.PlayerConfigurationManager; import org.royaldev.royalcommands.gui.inventory.listeners.ClickListener; import org.royaldev.royalcommands.gui.inventory.listeners.InventoryGUIEventListener; import org.royaldev.royalcommands.listeners.BackpackListener; import org.royaldev.royalcommands.listeners.BlockListener; import org.royaldev.royalcommands.listeners.EntityListener; import org.royaldev.royalcommands.listeners.MonitorListener; import org.royaldev.royalcommands.listeners.PlayerListener; import org.royaldev.royalcommands.listeners.ServerListener; import org.royaldev.royalcommands.listeners.SignListener; import org.royaldev.royalcommands.nms.api.NMSFace; import org.royaldev.royalcommands.protocol.ProtocolListener; import org.royaldev.royalcommands.rcommands.BaseCommand; import org.royaldev.royalcommands.rcommands.ReflectCommand; import org.royaldev.royalcommands.rcommands.trade.TradeListener; import org.royaldev.royalcommands.runners.AFKWatcher; import org.royaldev.royalcommands.runners.FreezeWatcher; import org.royaldev.royalcommands.runners.MailRunner; import org.royaldev.royalcommands.runners.UserdataRunner; import org.royaldev.royalcommands.runners.WarnWatcher; import org.royaldev.royalcommands.shaded.com.sk89q.util.config.FancyConfiguration; import org.royaldev.royalcommands.spawninfo.ItemListener; import org.royaldev.royalcommands.tools.UUIDFetcher; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.nio.file.Files; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; // import com.sk89q.worldguard.bukkit.WorldGuardPlugin; // TODO: Add banning for no-UUID players? Wait for Bukkit to fix? Investigate. // TODO: Rewrite /gm // TODO: Add config option for dangerous async (some people like living on the edge) // TODO: Add config for getting info about player for tooltip (use vault for group, etc) "{{group}}\n{{name}}" etc public class RoyalCommands extends JavaPlugin { public static ConfigurationSection commands = null; public static File dataFolder; public static ItemNameManager inm; public static WorldManager wm = null; public static MultiverseCore mvc = null; private static RoyalCommands instance; public final AuthorizationHandler ah = new AuthorizationHandler(this); public final VaultHandler vh = new VaultHandler(this); private final int minVersion = 2645; private final Pattern versionPattern = Pattern.compile("((\\d+\\.?){3})(\\-SNAPSHOT)?(\\-local\\-(\\d{8}\\.\\d{6})|\\-(\\d+))?"); private final long startTime = System.currentTimeMillis(); public Configuration whl; public String version = null; public String newVersion = null; public Metrics m = null; public Config c; public Help h; private NMSFace nmsFace; private CommandMap cm = null; private YamlConfiguration pluginYml = null; private RApiMain api; private VanishPlugin vp = null; // private WorldGuardPlugin wg = null; private LWCPlugin lwc = null; private ProtocolListener pl = null; /** * Joins an array of strings with spaces * * @param array Array to join * @param position Position to start joining from * @return Joined string */ @Contract("null, _ -> null; !null, _ -> !null") public static String getFinalArg(final String[] array, final int position) { if (array == null) return null; final StringBuilder sb = new StringBuilder(); for (int i = position; i < array.length; i++) sb.append(array[i]).append(" "); return sb.substring(0, sb.length() - 1); } public static RoyalCommands getInstance() { return RoyalCommands.instance; } private List<String> getAliases(String command) { final List<String> aliasesList = this.getCommandInfo(command).getStringList("aliases"); if (aliasesList == null) return new ArrayList<>(); return aliasesList; } private ConfigurationSection getCommandInfo(String command) { final ConfigurationSection ci = this.getCommands().getConfigurationSection(command); if (ci == null) throw new IllegalArgumentException("No such command registered!"); return ci; } private CommandMap getCommandMap() { if (this.cm != null) return this.cm; Field map; try { map = this.getServer().getPluginManager().getClass().getDeclaredField("commandMap"); map.setAccessible(true); this.cm = (CommandMap) map.get(this.getServer().getPluginManager()); return this.cm; } catch (Exception e) { e.printStackTrace(); return null; } } private ConfigurationSection getCommands() { return this.pluginYml.getConfigurationSection("reflectcommands"); } private String getDescription(String command) { return this.getCommandInfo(command).getString("description", command); } private VUUpdater.VUUpdateInfo getNewestVersions() throws Exception { return VUUpdater.getUpdateInfo("34507"); } private String getUsage(String command) { return this.getCommandInfo(command).getString("usage", "/<command>"); } private void initializeConfManagers() { final String[] cms = new String[]{"whitelist.yml", "warps.yml", "publicassignments.yml"}; for (final String name : cms) { final Configuration cm = Configuration.getConfiguration(name); if (!cm.exists()) cm.createFile(); cm.forceSave(); } } private void initializeMetrics() { try { this.m = new Metrics(this); Matcher matcher = this.versionPattern.matcher(this.version); if (matcher.matches()) { // 1 = base version // 3 = -SNAPSHOT // 6 = build # String versionMinusBuild = (matcher.group(1) == null) ? "Unknown" : matcher.group(1); String build = (matcher.group(6) == null) ? "local build" : matcher.group(6); if (matcher.group(3) == null) build = "release"; Metrics.Graph g = m.createGraph("Version"); // get our custom version graph g.addPlotter(new Metrics.Plotter(versionMinusBuild + "~=~" + build) { @Override public int getValue() { return 1; // this value doesn't matter } }); // add the donut graph with major version inside and build outside m.addGraph(g); // add the graph } if (!m.start()) this.getLogger().info("You have Metrics off! I like to keep accurate usage statistics, but okay. :("); else this.getLogger().info("Metrics enabled. Thank you!"); } catch (Exception ignore) { this.getLogger().warning("Could not start Metrics!"); } } private void initializeNMS() { // Get full package string of CraftServer. // org.bukkit.craftbukkit.versionstring (or for pre-refactor, just org.bukkit.craftbukkit final String packageName = getServer().getClass().getPackage().getName(); // Get the last element of the package // If the last element of the package was "craftbukkit" we are now pre-refactor String versionNMS = packageName.substring(packageName.lastIndexOf('.') + 1); if ("craftbukkit".equals(versionNMS)) versionNMS = "NoSupport"; try { // Check if we have a NMSHandler class at that location. final Class<?> clazz = Class.forName("org.royaldev.royalcommands.nms." + versionNMS + ".NMSHandler"); // Make sure it actually implements NMS and set our handler if (NMSFace.class.isAssignableFrom(clazz)) this.nmsFace = (NMSFace) clazz.getConstructor().newInstance(); } catch (final Exception e) { this.getLogger().warning("Could not find support for this CraftBukkit version."); this.getLogger().info("The BukkitDev page has links to the newest development builds to fix this."); this.getLogger().info("For now, NMS/CB internal support will be disabled."); this.nmsFace = new org.royaldev.royalcommands.nms.NoSupport.NMSHandler(); } if (this.nmsFace.hasSupport()) getLogger().info("Loaded support for " + this.nmsFace.getVersion() + "."); } private void initializeTasks() { final BukkitScheduler bs = this.getServer().getScheduler(); bs.runTaskTimerAsynchronously(this, new Runnable() { @Override public void run() { if (!Config.updateCheck) return; try { Matcher m = versionPattern.matcher(version); if (!m.matches()) return; final StringBuilder useVersion = new StringBuilder(); if (m.group(1) != null) useVersion.append(m.group(1)); // add base version # if (m.group(3) != null) useVersion.append(m.group(3)); // add SNAPSHOT status /* This does not need to compare build numbers. Everyone would be out of date all the time if it did. This method will compare root versions. */ final VUUpdater.VUUpdateInfo vuui = RoyalCommands.this.getNewestVersions(); final String stable = vuui.getStable(); final String dev = vuui.getDevelopment() + "-SNAPSHOT"; String currentVersion = useVersion.toString().toLowerCase(); if (!dev.equalsIgnoreCase(currentVersion) && currentVersion.contains("-SNAPSHOT")) { RoyalCommands.this.getLogger().warning("A newer version of RoyalCommands is available!"); RoyalCommands.this.getLogger().warning("Currently installed: v" + currentVersion + ", newest: v" + dev); RoyalCommands.this.getLogger().warning("Development builds are available at https://ci.royaldev.org/"); } else if (!stable.equalsIgnoreCase(currentVersion) && !currentVersion.equalsIgnoreCase(dev)) { RoyalCommands.this.getLogger().warning("A newer version of RoyalCommands is available!"); RoyalCommands.this.getLogger().warning("Currently installed: v" + currentVersion + ", newest: v" + stable); RoyalCommands.this.getLogger().warning("Stable builds are available at http://dev.bukkit.org/server-mods/royalcommands"); } else if (!stable.equalsIgnoreCase(currentVersion) && currentVersion.replace("-SNAPSHOT", "").equalsIgnoreCase(stable)) { RoyalCommands.this.getLogger().warning("A newer version of RoyalCommands is available!"); RoyalCommands.this.getLogger().warning("Currently installed: v" + currentVersion + ", newest: v" + stable); RoyalCommands.this.getLogger().warning("Stable builds are available at http://dev.bukkit.org/server-mods/royalcommands"); } } catch (Exception ignored) {} } }, 0L, 36000L); bs.scheduleSyncDelayedTask(this, new Runnable() { // load after server starts up @Override public void run() { h.reloadHelp(); RoyalCommands.this.getLogger().info("Help loaded for all plugins."); } }); bs.runTaskTimerAsynchronously(this, new AFKWatcher(this), 0L, 200L); bs.runTaskTimerAsynchronously(this, new WarnWatcher(this), 20L, 12000L); bs.scheduleSyncRepeatingTask(this, new FreezeWatcher(this), 20L, 100L); long mail = RUtils.timeFormatToSeconds(Config.mailCheckTime); if (mail > 0L) bs.scheduleSyncRepeatingTask(this, new MailRunner(this), 20L, mail * 20L); long every = RUtils.timeFormatToSeconds(Config.saveInterval); if (every < 1L) every = 600L; // 600s = 10m bs.runTaskTimerAsynchronously(this, new UserdataRunner(this), 20L, every * 20L); // tick = 1/20s } private <T> List<List<T>> partitionList(List<T> original, int maxSize) { final List<List<T>> partitions = new LinkedList<>(); for (int i = 0; i < original.size(); i += maxSize) partitions.add(original.subList(i, i + Math.min(maxSize, original.size() - i))); return partitions; } /** * Registers a command in the server's CommandMap. * * @param ce CommandExecutor to be registered * @param command Command name as specified in plugin.yml */ private void registerCommand(CommandExecutor ce, String command) { if (Config.disabledCommands.contains(command.toLowerCase())) return; try { final Constructor c = PluginCommand.class.getDeclaredConstructor(String.class, Plugin.class); c.setAccessible(true); final PluginCommand pc = (PluginCommand) c.newInstance(command, this); pc.setExecutor(ce); pc.setAliases(this.getAliases(command)); pc.setDescription(this.getDescription(command)); pc.setUsage(this.getUsage(command)); this.getCommandMap().register(this.getDescription().getName(), pc); } catch (Exception e) { this.getLogger().warning("Could not register command \"" + command + "\" - an error occurred: " + e.getMessage() + "."); } } private void update() { final File userdataFolder = new File(dataFolder, "userdata"); if (!userdataFolder.exists() || !userdataFolder.isDirectory()) return; final List<String> playersToConvert = new ArrayList<>(); final List<String> playersConverted = new ArrayList<>(); for (String fileName : userdataFolder.list(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.toLowerCase().endsWith(".yml"); } })) { String playerName = fileName.substring(0, fileName.length() - 4); // ".yml" = 4 try { //noinspection ResultOfMethodCallIgnored UUID.fromString(playerName); continue; } catch (IllegalArgumentException ignored) {} playersToConvert.add(playerName); } final List<List<String>> partitions = this.partitionList(playersToConvert, 100); this.getLogger().info("Converting " + playersToConvert.size() + " players in " + partitions.size() + " request" + (partitions.size() == 1 ? "" : "s") + "."); for (List<String> lookup : partitions) { this.getLogger().info("Converting next " + lookup.size() + " players."); final Map<String, UUID> uuids; try { uuids = new UUIDFetcher(lookup).call(); } catch (Exception ex) { ex.printStackTrace(); continue; } for (Map.Entry<String, UUID> e : uuids.entrySet()) { File userFile = new File(userdataFolder, e.getKey().toLowerCase() + ".yml"); if (!userFile.exists()) continue; try { Files.move(userFile.toPath(), new File(userdataFolder, e.getValue() + ".yml").toPath()); this.getLogger().info("Converted " + e.getKey().toLowerCase() + ".yml to " + e.getValue() + ".yml"); playersConverted.add(e.getKey().toLowerCase()); } catch (IOException ex) { this.getLogger().warning("Could not convert " + e.getKey() + ".yml: " + ex.getClass().getSimpleName() + " (" + ex.getMessage() + ")"); } } } playersToConvert.removeAll(playersConverted); // left over should be offline-mode players if (playersToConvert.size() > 0) this.getLogger().info("Converting offline-mode players."); for (final String name : playersToConvert) { if (name.trim().isEmpty()) continue; //noinspection deprecation final UUID uuid = this.getServer().getOfflinePlayer(name).getUniqueId(); try { Files.move(new File(userdataFolder, name + ".yml").toPath(), new File(userdataFolder, uuid + ".yml").toPath()); this.getLogger().info("Converted offline-mode player " + name + ".yml to " + uuid + ".yml"); } catch (IOException ex) { this.getLogger().warning("Could not convert " + name + ".yml: " + ex.getClass().getSimpleName() + " (" + ex.getMessage() + ")"); } } } /*private boolean unregisterCommand(String command) { final Command c = getCommandMap().getCommand(command); return c != null && c.unregister(getCommandMap()); } save for overriding commands in the config*/ private boolean versionCheck() { // If someone happens to be looking through this and knows a better way, let me know. if (!Config.checkVersion) return true; Pattern p = Pattern.compile(".+b(\\d+)jnks.+"); Matcher m = p.matcher(getServer().getVersion()); if (!m.matches() || m.groupCount() < 1) { this.getLogger().warning("Could not get CraftBukkit version! No version checking will take place."); return true; } Integer currentVersion = RUtils.getInt(m.group(1)); return currentVersion == null || currentVersion >= minVersion; } public boolean canAccessChest(Player p, Block b) { return this.lwc == null || this.lwc.getLWC().canAccessProtection(p, b); } @SuppressWarnings("unused") public boolean canBuild(Player p, Location l) { // return this.wg == null || this.wg.canBuild(p, l); return true; } public boolean canBuild(Player p, Block b) { // return this.wg == null || this.wg.canBuild(p, b); return true; } @SuppressWarnings("unused") public RApiMain getAPI() { return this.api; } public FancyConfiguration getFancyConfig() { final FancyConfiguration fc = new FancyConfiguration(new File(this.getDataFolder(), "config.yml")); fc.load(); return fc; } public NMSFace getNMSFace() { return this.nmsFace; } public int getNumberVanished() { int hid = 0; for (Player p : getServer().getOnlinePlayers()) if (this.isVanished(p)) hid++; return hid; } public YamlConfiguration getPluginYml() { return this.pluginYml; } public long getStartTime() { return this.startTime; } public boolean isVanished(Player p) { if (!Config.useVNP) return false; if (this.vp == null) { this.vp = (VanishPlugin) this.getServer().getPluginManager().getPlugin("VanishNoPacket"); return false; } return this.vp.getManager().isVanished(p); } public boolean isVanished(Player p, CommandSender cs) { if (!Config.useVNP) return false; if (this.vp == null) { this.vp = (VanishPlugin) this.getServer().getPluginManager().getPlugin("VanishNoPacket"); return false; } return !this.ah.isAuthorized(cs, "rcmds.seehidden") && this.vp.getManager().isVanished(p); } public void loadConfiguration() { if (!new File(getDataFolder(), "config.yml").exists()) saveDefaultConfig(); if (!new File(getDataFolder(), "items.csv").exists()) saveResource("items.csv", false); if (!new File(getDataFolder(), "rules.txt").exists()) saveResource("rules.txt", false); if (!new File(getDataFolder(), "help.txt").exists()) saveResource("help.txt", false); if (!new File(getDataFolder(), "warps.yml").exists()) saveResource("warps.yml", false); final File file = new File(getDataFolder(), "userdata"); if (!file.exists()) { try { boolean success = file.mkdir(); if (success) this.getLogger().info("Created userdata directory."); } catch (Exception e) { this.getLogger().severe("Failed to make userdata directory!"); this.getLogger().severe(e.getMessage()); } } } @Override public void onDisable() { //-- Cancel scheduled tasks --// this.getServer().getScheduler().cancelTasks(this); //-- Save inventories --// WorldManager.il.saveAllInventories(); //-- Save all userdata --// this.getLogger().info("Saving userdata and configurations (" + (PlayerConfigurationManager.configurationsCreated() + Configuration.configurationsCreated()) + " files in all)..."); PlayerConfigurationManager.saveAllConfigurations(); Configuration.saveAllConfigurations(); this.getLogger().info("Userdata saved."); //-- ProtocolLib --// if (this.pl != null) this.pl.uninitialize(); //-- We're done! --// this.getLogger().info("RoyalCommands v" + this.version + " disabled."); } @Override public void onEnable() { //-- Set fields --// RoyalCommands.instance = this; // Using deprecated method for backwards-compatibility. Will update eventually. //noinspection deprecation this.pluginYml = YamlConfiguration.loadConfiguration(this.getResource("plugin.yml")); RoyalCommands.dataFolder = getDataFolder(); this.whl = Configuration.getConfiguration("whitelist.yml"); RoyalCommands.commands = pluginYml.getConfigurationSection("reflectcommands"); this.version = getDescription().getVersion(); //-- Initialize ConfManagers if not made --// this.initializeConfManagers(); //-- Work out NMS magic using mbaxter's glorious methods --// this.initializeNMS(); //-- Hidendra's Metrics --// this.initializeMetrics(); //-- Get help --// this.h = new Help(this); //-- Get configs --// this.loadConfiguration(); this.c = new Config(this); this.c.reloadConfiguration(); //-- Check CB version --// if (!versionCheck()) { this.getLogger().severe("This version of CraftBukkit is too old to run RoyalCommands!"); this.getLogger().severe("This version of RoyalCommands needs at least CraftBukkit " + this.minVersion + "."); this.getLogger().severe("Disabling plugin. You can turn this check off in the config."); this.getServer().getPluginManager().disablePlugin(this); return; } //-- Set up Vault --// this.vh.setUpVault(); //-- Update old userdata --// if (Config.updateOldUserdata) this.update(); //-- Schedule tasks --// this.initializeTasks(); //-- Get dependencies --// this.vp = (VanishPlugin) this.getServer().getPluginManager().getPlugin("VanishNoPacket"); // this.wg = (WorldGuardPlugin) this.getServer().getPluginManager().getPlugin("WorldGuard"); this.lwc = (LWCPlugin) this.getServer().getPluginManager().getPlugin("LWC"); RoyalCommands.mvc = (MultiverseCore) this.getServer().getPluginManager().getPlugin("Multiverse-Core"); //-- Register events --// final PluginManager pm = this.getServer().getPluginManager(); pm.registerEvents(new PlayerListener(this), this); pm.registerEvents(new EntityListener(this), this); pm.registerEvents(new BlockListener(this), this); pm.registerEvents(new SignListener(this), this); pm.registerEvents(new MonitorListener(this), this); pm.registerEvents(new ServerListener(this), this); pm.registerEvents(new ItemListener(this), this); pm.registerEvents(new BackpackListener(), this); pm.registerEvents(new ClickListener(), this); pm.registerEvents(new TradeListener(), this); pm.registerEvents(new InventoryGUIEventListener(), this); //-- ProtocolLib things --// final Plugin plPlugin = this.getServer().getPluginManager().getPlugin("ProtocolLib"); if (Config.useProtocolLib && plPlugin != null && plPlugin.isEnabled()) { this.pl = new ProtocolListener(this); this.pl.initialize(); } //-- Register commands --// for (final String command : this.getCommands().getValues(false).keySet()) { final ConfigurationSection ci = this.getCommandInfo(command); if (ci == null) continue; final String className = ci.getString("class"); if (className == null) continue; try { final Class<?> clazz = Class.forName("org.royaldev.royalcommands.rcommands." + className); if (!clazz.isAnnotationPresent(ReflectCommand.class)) continue; final Constructor c = clazz.getConstructor(RoyalCommands.class, String.class); final Object o = c.newInstance(this, command); if (!(o instanceof BaseCommand)) continue; this.registerCommand((CommandExecutor) o, command); } catch (Exception e) { this.getLogger().warning("Could not register command \"" + command + "\" - an error occurred (" + e.getClass().getSimpleName() + "): " + e.getMessage() + "."); } } //-- Make the API --// this.api = new RApiMain(); //-- Convert old backpacks --// pm.registerEvents(new BackpackConverter(), this); //-- We're done! --// this.getLogger().info("RoyalCommands v" + this.version + " initiated."); } private class BackpackConverter implements Listener { @EventHandler public void onJoin(PlayerJoinEvent e) { final Player p = e.getPlayer(); final PlayerConfiguration pcm = PlayerConfigurationManager.getConfiguration(p); if (!pcm.isSet("backpack.item")) return; if (!pcm.exists()) pcm.createFile(); int invSize = pcm.getInt("backpack.size", -1); if (invSize < 9) invSize = 36; if (invSize % 9 != 0) invSize = 36; final Inventory i = Bukkit.createInventory(p, invSize, "Backpack"); for (int slot = 0; slot < invSize; slot++) { final ItemStack is = pcm.getItemStack("backpack.item." + slot); if (is == null) continue; i.setItem(slot, is); } RUtils.saveBackpack(p, i); pcm.set("backpack.item", null); pcm.set("backpack.size", null); } } }