commit 915257692fec7435bc57f828886a874341e76021 Author: Lorenzo Date: Thu Apr 9 16:31:14 2020 +0200 Initial commit diff --git a/HubThat-Reborn.iml b/HubThat-Reborn.iml new file mode 100644 index 0000000..95f4596 --- /dev/null +++ b/HubThat-Reborn.iml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ant_build_increment.xml b/ant_build_increment.xml new file mode 100644 index 0000000..c4d5559 --- /dev/null +++ b/ant_build_increment.xml @@ -0,0 +1,38 @@ + + + simple example increment build variable + + + + + + + + + + Changing build version from BUILD_VERSION to ${version}.${build.number} in file plugin.yml... + + + Changed! + + + + + + Changing back build version ${version}.${build.number} in file plugin.yml to BUILD_VERSION... + + + + + Changed! + + + \ No newline at end of file diff --git a/ant_buildversion_end.txt b/ant_buildversion_end.txt new file mode 100644 index 0000000..b1a1360 --- /dev/null +++ b/ant_buildversion_end.txt @@ -0,0 +1 @@ +Changing back build version 10.0.161 in file plugin.yml to BUILD_VERSION... \ No newline at end of file diff --git a/ant_buildversion_init.txt b/ant_buildversion_init.txt new file mode 100644 index 0000000..a290bc0 --- /dev/null +++ b/ant_buildversion_init.txt @@ -0,0 +1 @@ +Changed! \ No newline at end of file diff --git a/build.number b/build.number new file mode 100644 index 0000000..d2f3f38 --- /dev/null +++ b/build.number @@ -0,0 +1,5 @@ +#Build Number for ANT. Do not edit! +#Sat Apr 04 18:08:45 CEST 2020 +build.number=162 +\#Build=Number for ANT. Do not edit\! +\#Tue=Jul 30 16\:09\:09 CEST 2019 diff --git a/src/config.yml b/src/config.yml new file mode 100644 index 0000000..ddb7369 --- /dev/null +++ b/src/config.yml @@ -0,0 +1,19 @@ +hub: + delay: 5 +spawn: + delay: 5 +chat: + limit-to-worlds-sharing-spawns: false +update-checker: + enable: true +movement-detection: + enable: true +gamemode: + set-on-join: true + mode: 0 +teleportation: + teleport-to-hub-on-join: true + teleport-to-hub-on-respawn: false + respawn-handler: true +settings: + multiverse-bypass: false \ No newline at end of file diff --git a/src/hub.yml b/src/hub.yml new file mode 100644 index 0000000..b95f505 --- /dev/null +++ b/src/hub.yml @@ -0,0 +1,7 @@ +hub: + world: __UNSET__ + x: 0.0 + y: 0.0 + z: 0.0 + yaw: 0.0 + pitch: 0.0 \ No newline at end of file diff --git a/src/lang.yml b/src/lang.yml new file mode 100644 index 0000000..dbb78f7 --- /dev/null +++ b/src/lang.yml @@ -0,0 +1,27 @@ +error: + console-access-blocked: '&cSorry, but this command is not available via console.' + already-teleporting: '&cYou are already in the teleportation queue!' + hub-not-set: '&cThe hub is not set.' + spawn-not-set: '&cThe spawn is not set.' + unknown-world: '&cError: world "%w%" does not exist!' + wrong-usage: '&cWrong usage! Correct usage is %usage%' + no-permission: '&cYou do not have permission: &4%permission%' + player-offline: '&cPlayer %player% is offline!' +warning: + console-access: '&eWarning! You are accessing the plugin command from console.' + teleportation-cancelled: '&cYou moved! Teleportation cancelled.' +info: + global: + teleport-delay: '&eYou will be teleported in %delay% seconds!' + prefix: '&0[&3HT&0] &f' + hub: + teleported: '&eYou have been teleported to the hub!' + teleported-other: '&eYou teleported &3%player%&e to the hub!' + set: '&eYou have successfully set the hub! World: &3%w%&e X: &3%x%&e Y: &3%y%&e Z: &3%z%' + spawn: + teleported: '&eYou have been teleported to the spawn!' + teleported-other: '&eYou teleported &3%player%&e to world &3%world%&e''s spawn!' + set: '&eYou have successfully set the spawn for world &3%dw%&e in world: &3%cw%&e X: &3%x%&e Y: &3%y%&e Z: &3%z%' + worldtp: + teleported: '&eYou have been teleported to world "%w%"!' + worlds-list: '&3Worlds List:' \ No newline at end of file diff --git a/src/net/mindoverflow/hubthat/HubThat.java b/src/net/mindoverflow/hubthat/HubThat.java new file mode 100644 index 0000000..d6f225b --- /dev/null +++ b/src/net/mindoverflow/hubthat/HubThat.java @@ -0,0 +1,182 @@ +package net.mindoverflow.hubthat; + +import net.mindoverflow.hubthat.commands.*; +import net.mindoverflow.hubthat.completers.InfoCompleter; +import net.mindoverflow.hubthat.completers.SpawnCompleter; +import net.mindoverflow.hubthat.listeners.PlayerChatListener; +import net.mindoverflow.hubthat.listeners.PlayerJoinListener; +import net.mindoverflow.hubthat.listeners.PlayerMoveListener; +import net.mindoverflow.hubthat.listeners.PlayerRespawnListener; +import net.mindoverflow.hubthat.utils.files.FileUtils; +import net.mindoverflow.hubthat.utils.files.OldConfigConversion; +import net.mindoverflow.hubthat.utils.statistics.Metrics; +import net.mindoverflow.hubthat.utils.statistics.UpdateChecker; +import net.mindoverflow.hubthat.utils.*; +import org.bukkit.World; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.PluginManager; +import org.bukkit.plugin.java.JavaPlugin; + +import java.util.logging.Level; +import java.util.logging.Logger; + +public class HubThat extends JavaPlugin +{ + // Instantiate a Debugger for this class. + private Debugger debugger = new Debugger(getClass().getName()); + + // Initializing needed variables. + public static Logger logger; + private PluginManager pluginManager; + public UpdateChecker updateChecker; + + // Method called when the plugin is being loaded. + @Override + public void onEnable() + { + + // Give the initialized variables their respective values. Absolutely don't do this before as they will look good in the IDE but will result null. + logger = getLogger(); + pluginManager = getServer().getPluginManager(); + + // Check and report if the Debugger is enabled (the method itself does the checking). Would do it before but we need the logger to be initialized! :( + debugger.sendDebugMessage(Level.WARNING, "---[ DEBUGGER IS ENABLED! ]---"); + debugger.sendDebugMessage(Level.WARNING, "---[ INITIALIZING PLUGIN ]---"); + debugger.sendDebugMessage(Level.INFO, "Logger and PluginManager already initialized."); + + // Register instances and give them the plugin parameter (this, because this class is the JavaPlugin) so they can access all of its info. + debugger.sendDebugMessage(Level.INFO, "Instantiating some classes that need to access to plugin data..."); + FileUtils fileUtilsInstance = new FileUtils(this); + HubThatCommand hubThatCommandInstance = new HubThatCommand(this); + HubCommand hubCommandInstance = new HubCommand(this); + SetHubCommand setHubCommandInstance = new SetHubCommand(this); + SpawnCommand spawnCommandInstance = new SpawnCommand(this); + SetSpawnCommand setSpawnCommandInstance = new SetSpawnCommand(this); + WorldListCommand worldListCommandInstance = new WorldListCommand(this); + WorldTpCommand worldTpCommandInstance = new WorldTpCommand(this); + PermissionUtils permissionUtilsInstance = new PermissionUtils(this); + TeleportUtils teleportUtilsInstance = new TeleportUtils(this); + MessageUtils messageUtilsInstance = new MessageUtils(this); + UpdateChecker updateCheckerInstance = new UpdateChecker(this); + updateChecker = new UpdateChecker(this); + debugger.sendDebugMessage(Level.INFO, "Done instantiating classes!"); + + // Register Listeners + debugger.sendDebugMessage(Level.INFO, "Registering listeners..."); + pluginManager.registerEvents(new PlayerJoinListener(this), this); + pluginManager.registerEvents(new PlayerMoveListener(this), this); + pluginManager.registerEvents(new PlayerChatListener(this), this); + pluginManager.registerEvents(new PlayerRespawnListener(this), this); + + + + debugger.sendDebugMessage(Level.INFO, "Done registering listeners!"); + + // Register Commands + debugger.sendDebugMessage(Level.INFO, "Registering commands..."); + + getCommand("hubthat").setExecutor(hubThatCommandInstance); + getCommand("hubthat").setTabCompleter(new InfoCompleter()); + + getCommand("hub").setExecutor(hubCommandInstance); + + getCommand("sethub").setExecutor(setHubCommandInstance); + + getCommand("spawn").setExecutor(spawnCommandInstance); + getCommand("spawn").setTabCompleter(new SpawnCompleter()); + + getCommand("setspawn").setExecutor(setSpawnCommandInstance); + getCommand("setspawn").setTabCompleter(new SpawnCompleter()); + + getCommand("worldlist").setExecutor(worldListCommandInstance); + + getCommand("worldtp").setExecutor(worldTpCommandInstance); + getCommand("worldtp").setTabCompleter(new SpawnCompleter()); + + debugger.sendDebugMessage(Level.INFO, "Done registering commands!"); + + OldConfigConversion.checkOldConfig(this, logger); + // Check if all needed files exist and work correctly, also loading their YAMLs. + debugger.sendDebugMessage(Level.INFO, "Checking files..."); + FileUtils.checkFiles(); + debugger.sendDebugMessage(Level.INFO, "Done checking files!"); + + /* + Load all the YAML files. We are already loading them in FileUtils's checkFiles() method but we are loading them singularly. + With this method we are sure that all the files get successfully loaded. Better twice than never... + */ + debugger.sendDebugMessage(Level.INFO, "Reloading YAML config..."); + FileUtils.reloadYamls(); + debugger.sendDebugMessage(Level.INFO, "Done!"); + + // Check for updates, if they are enabled. + if(FileUtils.FileType.CONFIG_YAML.yaml.getBoolean(ConfigEntries.UPDATE_CHECKER_ENABLED.path)) + { + debugger.sendDebugMessage(Level.INFO, "Update checking is enabled."); + // Start the update checking delayed job. It will handle checking updates, storing variables and telling the console. + debugger.sendDebugMessage(Level.INFO, "Running task (via Main)."); + //UpdateChecker.runTimer(); + // Check if the links are valid. + /*debugger.sendDebugMessage(Level.INFO, "Checking if links are valid via Main."); + if(updateCheckerInstance.linksValid()) + { + debugger.sendDebugMessage(Level.INFO, "Links are valid."); + // Check if the update is needed (if newest version is different from current version). + // We need to surround it with try/catch because it may throw a IOException, + try + { + debugger.sendDebugMessage(Level.INFO, "Checking updates are needed via Main."); + updateCheckerInstance.checkUpdates(); + } catch (IOException e) + { + e.printStackTrace(); + } + } + else + { // If the links are not valid... (eg: server/internet is offline) + // Log it to the console. + logger.log(Level.SEVERE, "There's a problem with the updates server."); + logger.log(Level.SEVERE, "Please check if there are any updates manually."); + }*/ + } + + debugger.sendDebugMessage(Level.INFO,"Setting up Metrics..."); + setupMetrics(); + debugger.sendDebugMessage(Level.INFO,"Done setting up Metrics!"); + + // Send success output message to console. + logger.log(Level.INFO, "Plugin " + getDescription().getName() + " Successfully Loaded!"); + debugger.sendDebugMessage(Level.WARNING, "---[ INITIALIZATION DONE ]---"); + + } + + // Method called when the plugin is being unloaded. + @Override + public void onDisable() { + debugger.sendDebugMessage(Level.WARNING, "---[ DEBUGGER IS ENABLED! ]---"); + debugger.sendDebugMessage(Level.WARNING, "---[ DISABLING PLUGIN ]---"); + getServer().getScheduler().cancelTasks(this); + logger.log(Level.INFO, "Plugin " + getDescription().getName() + " Successfully Unloaded!"); + debugger.sendDebugMessage(Level.WARNING, "---[ PLUGIN DISABLED ]---"); + } + + private void setupMetrics() + { + + Metrics metrics = new Metrics(this); + + YamlConfiguration config = FileUtils.FileType.CONFIG_YAML.yaml; + metrics.addCustomChart(new Metrics.SimplePie("respawn-handler", () -> config.getString(ConfigEntries.TELEPORTATION_RESPAWN_HANDLER.path))); + metrics.addCustomChart(new Metrics.SimplePie("world-related-chat", () -> config.getString(ConfigEntries.WORLD_RELATED_CHAT.path))); + metrics.addCustomChart(new Metrics.SimplePie("update-notify", () -> config.getString(ConfigEntries.UPDATE_CHECKER_ENABLED.path))); + metrics.addCustomChart(new Metrics.SimplePie("set-gamemode-on-join", () -> config.getString(ConfigEntries.GAMEMODE_SET_ON_JOIN.path))); + metrics.addCustomChart(new Metrics.SimplePie("tp-hub-on-join", () -> config.getString(ConfigEntries.TELEPORTATION_TP_HUB_ON_JOIN.path))); + metrics.addCustomChart(new Metrics.SimplePie("tp-hub-on-respawn", () -> config.getString(ConfigEntries.TELEPORTATION_TP_HUB_ON_RESPAWN.path))); + + if(config.getBoolean(ConfigEntries.GAMEMODE_SET_ON_JOIN.path)) + { + metrics.addCustomChart(new Metrics.SimplePie("join-gamemode", () -> config.getString(ConfigEntries.GAMEMODE.path))); + } + } + +} \ No newline at end of file diff --git a/src/net/mindoverflow/hubthat/commands/HubCommand.java b/src/net/mindoverflow/hubthat/commands/HubCommand.java new file mode 100644 index 0000000..5fd0863 --- /dev/null +++ b/src/net/mindoverflow/hubthat/commands/HubCommand.java @@ -0,0 +1,152 @@ +package net.mindoverflow.hubthat.commands; + +import net.mindoverflow.hubthat.HubThat; +import net.mindoverflow.hubthat.utils.*; +import net.mindoverflow.hubthat.utils.files.FileUtils; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.Objects; +import java.util.logging.Level; + +public class HubCommand implements CommandExecutor +{ + + + // Initialize the debugger so I can debug the plugin. + private static Debugger debugger = new Debugger(HubCommand.class.getName()); + + // Initialize the plugin variable so we can access all of the plugin's data. + private static HubThat plugin; + + // Constructor to actually give "plugin" a value. + public HubCommand(HubThat givenPlugin) { plugin = givenPlugin; } + + @Override + public boolean onCommand(CommandSender commandSender, Command command, String s, String[] args) + { + + // Log who is using the command. + debugger.sendDebugMessage(Level.INFO, "Sender is instance of: " + commandSender.getClass().getName()); + + // Store the commandSender name for easy access. + String username = commandSender.getName(); + + + if(args.length > 0) + { + // Check if the player has permission to teleport to the hub. + if(PermissionUtils.playerHasPermission(commandSender, Permissions.HUB_TELEPORT_OTHERS)) + { + String teleportingPlayerName = args[0]; + Player teleportingPlayer = plugin.getServer().getPlayer(teleportingPlayerName); + if(teleportingPlayer == null) + { + String errorMessage = MessageUtils.getLocalizedMessage(LocalizedMessages.ERROR_PLAYER_OFFLINE, true).replace("%player%", teleportingPlayerName); + commandSender.sendMessage(errorMessage); + return true; + } + else + { + teleportToHub(commandSender, teleportingPlayer); + return true; + } + } + else + { + // Warn the player he doesn't have permissions to teleport others to the hub. + String errorMessage = MessageUtils.getLocalizedMessage(LocalizedMessages.NO_PERMISSION, true).replace("%permission%", Permissions.HUB_TELEPORT_OTHERS.permission); + commandSender.sendMessage(errorMessage); + return true; + } + } + + + // If the command comes from Console, stop it and give a warning. + boolean senderIsConsole = !(commandSender instanceof Player); + if(senderIsConsole) + { + MessageUtils.sendLocalizedMessage(commandSender, LocalizedMessages.ERROR_CONSOLE_ACCESS_BLOCKED); + return true; + } + + + // Check if the player has permission to teleport to the hub. + if(PermissionUtils.playerHasPermission(commandSender, Permissions.HUB_TELEPORT)) + { + + // Check if the player has permission to skip the teleport delay. + if(PermissionUtils.playerHasPermission(commandSender, Permissions.NO_HUB_DELAY)) + { + + teleportToHub(commandSender, (Player)commandSender); + return true; + } + // If the player doesn't have permission to skip the teleport delay... + else + { + // Check if he's not already teleporting. + if(!CommonValues.teleporting.contains(username)) + { + // Put the player in the ArrayList of players waiting to be teleported. + CommonValues.teleporting.add(username); + // Load the teleportation delay. + int delay = FileUtils.FileType.CONFIG_YAML.yaml.getInt(ConfigEntries.HUB_DELAY.path); + + + // Warn the player about the delay. + String delayMessage = MessageUtils.getLocalizedMessage(LocalizedMessages.INFO_TELEPORT_DELAY, false); + delayMessage = delayMessage.replace("%delay%", delay + ""); + MessageUtils.sendColorizedMessage(commandSender, delayMessage); + + // Start a timer. + Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() + { + @Override + public void run() + { + if(!CommonValues.cancelRunnable.contains(username) && CommonValues.teleporting.contains(username)) + { + teleportToHub(commandSender, (Player)commandSender); + } + CommonValues.cancelRunnable.remove(username); + + } + }, delay * 20); // Convert seconds to ticks. + return true; + } + // If it's already teleporting... + else + { + // Send a message to the player stating it, and do nothing. + MessageUtils.sendLocalizedMessage(commandSender, LocalizedMessages.ERROR_ALREADY_TELEPORTING); + return true; + } + } + } + else + { + // Warn the player he doesn't have permissions to go to the hub. + String errorMessage = MessageUtils.getLocalizedMessage(LocalizedMessages.NO_PERMISSION, true).replace("%permission%", Permissions.HUB_TELEPORT.permission); + commandSender.sendMessage(errorMessage); + return true; + } + } + + // Method to teleport the player to the hub. + public static void teleportToHub(CommandSender sender, Player player) + { + String username = player.getName(); + + // Require the world to be Non Null + String worldName = Objects.requireNonNull(player.getWorld().getName()); + debugger.sendDebugMessage(Level.INFO, "Player name: " + username + "; World name: " + worldName); + // Teleport the player to the destination. + TeleportUtils.teleportPlayer(sender, player, FileUtils.FileType.HUB_YAML, worldName); + // Remove it from the "teleporting" list - so it won't get teleported if it's waiting the spawn delay. + CommonValues.teleporting.remove(username); + } +} diff --git a/src/net/mindoverflow/hubthat/commands/HubThatCommand.java b/src/net/mindoverflow/hubthat/commands/HubThatCommand.java new file mode 100644 index 0000000..ef5c723 --- /dev/null +++ b/src/net/mindoverflow/hubthat/commands/HubThatCommand.java @@ -0,0 +1,71 @@ +package net.mindoverflow.hubthat.commands; + +import net.mindoverflow.hubthat.HubThat; +import net.mindoverflow.hubthat.commands.hubthatcommands.HelpCommand; +import net.mindoverflow.hubthat.commands.hubthatcommands.ReloadCommand; +import net.mindoverflow.hubthat.utils.Debugger; +import net.mindoverflow.hubthat.utils.MessageUtils; +import net.mindoverflow.hubthat.utils.LocalizedMessages; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; + +import java.util.logging.Level; + +public class HubThatCommand implements CommandExecutor +{ + + // Initialize the plugin variable so we can access all of the plugin's data. + private HubThat plugin; + + // Initialize the debugger so I can debug the plugin. + private Debugger debugger = new Debugger(getClass().getName()); + + // Constructor to actually give "plugin" a value. + public HubThatCommand(HubThat givenPlugin) + { + plugin = givenPlugin; + } + + + + // Override the default command. Set the instructions for this particular command (registered in the main class). + @Override + public boolean onCommand(CommandSender commandSender, Command command, String s, String[] args) + { + // Log who is using the command. + debugger.sendDebugMessage(Level.INFO, "Sender is instance of: " + commandSender.getClass().getName()); + + // If the command comes from Console, give a warning. + // We need no warning because this command is not player reliant. + /*boolean senderIsConsole = (commandSender instanceof ConsoleCommandSender); + if(senderIsConsole) + { + MessageUtils.sendLocalizedMessage(commandSender.getName(), LocalizedMessages.WARNING_CONSOLE_ACCESS); + }*/ + + // Check if there are any args. + if(args.length == 0) + { + MessageUtils.sendColorizedMessage(commandSender, "&7HubThat version &6" + plugin.getDescription().getVersion() + " for SpigotMC/CraftBukkit &61.7&7-&61.15&7."); + MessageUtils.sendColorizedMessage(commandSender, "&7Coded by &6mind_overflow&7, all rights reserved (&6Copyright © '20&7)."); + commandSender.sendMessage(""); + MessageUtils.sendColorizedMessage(commandSender, "&7Write &6/hubthat help&7 to see plugin commands."); + } + // Check if there is a single argument after the command itself. + else if (args.length == 1) + { + if(args[0].equalsIgnoreCase("help")) + { + HelpCommand.infoCommand(commandSender, plugin); + } + // Check if the args are "reload" and in case, do it. + else if(args[0].equalsIgnoreCase("reload")) + { + ReloadCommand.reloadCommand(commandSender, plugin); + } + } + return true; + } +} diff --git a/src/net/mindoverflow/hubthat/commands/SetHubCommand.java b/src/net/mindoverflow/hubthat/commands/SetHubCommand.java new file mode 100644 index 0000000..8cce087 --- /dev/null +++ b/src/net/mindoverflow/hubthat/commands/SetHubCommand.java @@ -0,0 +1,87 @@ +package net.mindoverflow.hubthat.commands; + +import net.mindoverflow.hubthat.HubThat; +import net.mindoverflow.hubthat.utils.*; +import net.mindoverflow.hubthat.utils.files.FileUtils; +import org.bukkit.Location; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.entity.Player; + +import java.util.logging.Level; + +public class SetHubCommand implements CommandExecutor +{ + + // Initialize the debugger so I can debug the plugin. + private Debugger debugger = new Debugger(getClass().getName()); + + + // Initialize the plugin variable so we can access all of the plugin's data. + private HubThat plugin; + + // Constructor to actually give "plugin" a value. + public SetHubCommand(HubThat givenPlugin) { plugin = givenPlugin; } + + + @Override + public boolean onCommand(CommandSender commandSender, Command command, String s, String[] args) + { + + // Log who is using the command. + debugger.sendDebugMessage(Level.INFO, "Sender is instance of: " + commandSender.getClass().getName()); + + + // If the command comes from Console, stop it and give a warning. + boolean senderIsConsole = (commandSender instanceof ConsoleCommandSender); + if(senderIsConsole) + { + MessageUtils.sendLocalizedMessage(commandSender, LocalizedMessages.ERROR_CONSOLE_ACCESS_BLOCKED); + return true; + } + + // Check if the player has permission to set the hub. + if(PermissionUtils.playerHasPermission(commandSender, Permissions.HUB_SET)) + { + // Cast player to commandSender so we can get its position. + Player player = (Player)commandSender; + // Load the player's position. + Location playerLocation = player.getLocation(); + double x, y, z, yaw, pitch; + String worldName; + x = playerLocation.getX(); + y = playerLocation.getY(); + z = playerLocation.getZ(); + yaw = playerLocation.getYaw(); + pitch = playerLocation.getPitch(); + worldName = playerLocation.getWorld().getName(); + + // Set the location to the Yaml file. + FileUtils.FileType.HUB_YAML.yaml.set("hub.x", x); + FileUtils.FileType.HUB_YAML.yaml.set("hub.y", y); + FileUtils.FileType.HUB_YAML.yaml.set("hub.z", z); + FileUtils.FileType.HUB_YAML.yaml.set("hub.yaw", yaw); + FileUtils.FileType.HUB_YAML.yaml.set("hub.pitch", pitch); + FileUtils.FileType.HUB_YAML.yaml.set("hub.world", worldName); + + // Save the file to the disk. We don't need to reload the Yaml file because we already set the values in the RAM. + FileUtils.saveExistingYaml(FileUtils.FileType.HUB_YAML); + + // Tell the player he set the hub successfully. + String hubSetMessage = MessageUtils.getLocalizedMessage(LocalizedMessages.INFO_HUB_SET, false); + hubSetMessage = hubSetMessage.replace("%w%", worldName); + hubSetMessage = hubSetMessage.replace("%x%", (int)x + ""); + hubSetMessage = hubSetMessage.replace("%y%", (int)y + ""); + hubSetMessage = hubSetMessage.replace("%z%", (int)z + ""); + MessageUtils.sendColorizedMessage(commandSender, hubSetMessage); + } + else + { + String errorMessage = MessageUtils.getLocalizedMessage(LocalizedMessages.NO_PERMISSION, true).replace("%permission%", Permissions.HUB_SET.permission); + commandSender.sendMessage(errorMessage); + } + return true; + } +} diff --git a/src/net/mindoverflow/hubthat/commands/SetSpawnCommand.java b/src/net/mindoverflow/hubthat/commands/SetSpawnCommand.java new file mode 100644 index 0000000..d5536d9 --- /dev/null +++ b/src/net/mindoverflow/hubthat/commands/SetSpawnCommand.java @@ -0,0 +1,127 @@ +package net.mindoverflow.hubthat.commands; + +import net.mindoverflow.hubthat.HubThat; +import net.mindoverflow.hubthat.utils.*; +import net.mindoverflow.hubthat.utils.files.FileUtils; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.entity.Player; + +import java.util.Objects; +import java.util.logging.Level; + +public class SetSpawnCommand implements CommandExecutor +{ + + // Initialize the debugger so I can debug the plugin. + private Debugger debugger = new Debugger(getClass().getName()); + + + // Initialize the plugin variable so we can access all of the plugin's data. + private HubThat plugin; + + + // Constructor to actually give "plugin" a value. + public SetSpawnCommand(HubThat givenPlugin) { plugin = givenPlugin; } + + + @Override + public boolean onCommand(CommandSender commandSender, Command command, String s, String[] args) + { + + // Log who is using the command. + debugger.sendDebugMessage(Level.INFO, "Sender is instance of: " + commandSender.getClass().getName()); + + + // If the command comes from Console, stop it and give a warning. + boolean senderIsConsole = (commandSender instanceof ConsoleCommandSender); + if(senderIsConsole) + { + MessageUtils.sendLocalizedMessage(commandSender, LocalizedMessages.ERROR_CONSOLE_ACCESS_BLOCKED); + return true; + } + + + // Check if the player has permission to set the hub. + if(PermissionUtils.playerHasPermission(commandSender, Permissions.SPAWN_SET)) + { + + // Cast player to commandSender so we can get its position. + Player player = (Player)commandSender; + // Load the player's position. + Location playerLocation = player.getLocation(); + double x, y, z, yaw, pitch; + String currentWorldName, worldYouAreSettingTheSpawnOf; + x = playerLocation.getX(); + y = playerLocation.getY(); + z = playerLocation.getZ(); + yaw = playerLocation.getYaw(); + pitch = playerLocation.getPitch(); + // We need the world name to be non null. + currentWorldName = Objects.requireNonNull(playerLocation.getWorld()).getName(); + + // Check if there are any args and if they are different from the actual world. + if(args.length > 0 && !args[0].equalsIgnoreCase(currentWorldName)) + { + // We need to set the world you're setting the spawn of to args[0] so we can differentiate between it and it's new spawn destination. + worldYouAreSettingTheSpawnOf = args[0]; + } + // If there are no args, we are going to use the current player's world as the one with the new spawn. + else + { + worldYouAreSettingTheSpawnOf = currentWorldName; + Objects.requireNonNull(plugin.getServer().getWorld(worldYouAreSettingTheSpawnOf)).setSpawnLocation((int)x, (int)y, (int)z); + } + + // If the world does not exist... + World world = plugin.getServer().getWorld(worldYouAreSettingTheSpawnOf); + if(world == null) + { + // Warn the player and stop. + // Tell the player that the world does not exist. + String errorWorldNotExistingMessage = MessageUtils.getLocalizedMessage(LocalizedMessages.ERROR_WORLD_NOT_EXISTING, false); + errorWorldNotExistingMessage = errorWorldNotExistingMessage.replace("%w%", worldYouAreSettingTheSpawnOf); + MessageUtils.sendColorizedMessage(commandSender, errorWorldNotExistingMessage); + return true; + } + + // Set the location to the Yaml file. + FileUtils.FileType.SPAWN_YAML.yaml.set("spawn.world." + worldYouAreSettingTheSpawnOf, currentWorldName); + FileUtils.FileType.SPAWN_YAML.yaml.set("spawn.x." + worldYouAreSettingTheSpawnOf, x); + FileUtils.FileType.SPAWN_YAML.yaml.set("spawn.y." + worldYouAreSettingTheSpawnOf, y); + FileUtils.FileType.SPAWN_YAML.yaml.set("spawn.z." + worldYouAreSettingTheSpawnOf, z); + FileUtils.FileType.SPAWN_YAML.yaml.set("spawn.yaw." + worldYouAreSettingTheSpawnOf, yaw); + FileUtils.FileType.SPAWN_YAML.yaml.set("spawn.pitch." + worldYouAreSettingTheSpawnOf, pitch); + + // Edit the vanilla world's spawnpoint. + //world.setSpawnLocation(new Location(world, x, y, z, (float)yaw, (float)pitch)); + + // Save the file to the disk. We don't need to reload the Yaml file because we already set the values in the RAM. + FileUtils.saveExistingYaml(FileUtils.FileType.SPAWN_YAML); + + // Tell the player he set the spawn successfully. + String spawnSetMessage = MessageUtils.getLocalizedMessage(LocalizedMessages.INFO_SPAWN_SET, false) + .replace("%dw%", worldYouAreSettingTheSpawnOf) + .replace("%cw%", currentWorldName) + .replace("%x%", (int)x + "") + .replace("%y%", (int)y + "") + .replace("%z%", (int)z + ""); + + MessageUtils.sendColorizedMessage(commandSender, spawnSetMessage); + } + else + { + String errorMessage = MessageUtils.getLocalizedMessage(LocalizedMessages.NO_PERMISSION, true).replace("%permission%", Permissions.SPAWN_SET.permission); + commandSender.sendMessage(errorMessage); + } + + + + + return true; + } +} diff --git a/src/net/mindoverflow/hubthat/commands/SpawnCommand.java b/src/net/mindoverflow/hubthat/commands/SpawnCommand.java new file mode 100644 index 0000000..a6b51aa --- /dev/null +++ b/src/net/mindoverflow/hubthat/commands/SpawnCommand.java @@ -0,0 +1,206 @@ +package net.mindoverflow.hubthat.commands; + +import net.mindoverflow.hubthat.HubThat; +import net.mindoverflow.hubthat.utils.*; +import net.mindoverflow.hubthat.utils.files.FileUtils; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.entity.Player; + +import java.util.logging.Level; + + +public class SpawnCommand implements CommandExecutor +{ + + // Initialize the debugger so I can debug the plugin. + private static Debugger debugger = new Debugger(SpawnCommand.class.getName()); + + + // Initialize the plugin variable so we can access all of the plugin's data. + private static HubThat plugin; + + // Constructor to actually give "plugin" a value. + public SpawnCommand(HubThat givenPlugin) { plugin = givenPlugin; } + + + @Override + public boolean onCommand(CommandSender commandSender, Command command, String s, String[] args) + { + + + // Log who is using the command. + debugger.sendDebugMessage(Level.INFO, "Sender is instance of: " + commandSender.getClass().getName()); + + // Store the commandSender name for easy access. + String username = commandSender.getName(); + + // If the command comes from Console, stop it and give a warning. + boolean senderIsConsole = (commandSender instanceof ConsoleCommandSender); + if(senderIsConsole) + { + MessageUtils.sendLocalizedMessage(commandSender, LocalizedMessages.ERROR_CONSOLE_ACCESS_BLOCKED); + return true; + } + + if(args.length > 1) + { + if(PermissionUtils.playerHasPermission(commandSender, Permissions.SPAWN_TELEPORT_OTHERS)) + { + String teleportingPlayerName = args[1]; + String worldName = args[0]; + + Player teleportPlayer = plugin.getServer().getPlayer(teleportingPlayerName); + if(teleportPlayer == null) + { + String errorMessage = MessageUtils.getLocalizedMessage(LocalizedMessages.ERROR_PLAYER_OFFLINE, true).replace("%player%", teleportingPlayerName); + commandSender.sendMessage(errorMessage); + return true; + } + + if(plugin.getServer().getWorld(worldName) == null) + { + MessageUtils.sendLocalizedMessage(commandSender, LocalizedMessages.ERROR_SPAWN_NOT_SET); + return true; + } + + teleportToSpawn(commandSender, teleportPlayer, worldName); + return true; + } + else + { + // Warn the player he doesn't have permissions to teleport others to the hub. + String errorMessage = MessageUtils.getLocalizedMessage(LocalizedMessages.NO_PERMISSION, true).replace("%permission%", Permissions.SPAWN_TELEPORT_OTHERS.permission); + commandSender.sendMessage(errorMessage); + return true; + } + } + + // Check if the player has permission to teleport to the spawn. + if(PermissionUtils.playerHasPermission(commandSender, Permissions.SPAWN_TELEPORT)) + { + + + // Check if the player has permission to skip the teleport delay. + if(PermissionUtils.playerHasPermission(commandSender, Permissions.NO_SPAWN_DELAY)) + { + + // Run method to check if there are any args and teleport the player accordingly. + checkArgsAndTeleport(args, commandSender); + + return true; + } + else + { + // Check if he's not already teleporting. + if(!CommonValues.teleporting.contains(username)) + { + if(args.length > 0) + { + if(!PermissionUtils.playerHasPermission(commandSender, Permissions.SPAWN_TELEPORT_ANOTHER_WORLD)) + { + // Tell the player he has no permission to teleport to another world's spawn. + String errorMessage = MessageUtils.getLocalizedMessage(LocalizedMessages.NO_PERMISSION, true).replace("%permission%", Permissions.SPAWN_TELEPORT_ANOTHER_WORLD.permission); + commandSender.sendMessage(errorMessage); + return true; + } + + if(plugin.getServer().getWorld(args[0]) == null) + { + String erroMessage = MessageUtils.getLocalizedMessage(LocalizedMessages.ERROR_WORLD_NOT_EXISTING, true).replace("%w%", args[0]); + commandSender.sendMessage(erroMessage); + return true; + } + } + + + // Load the teleportation delay. + int delay = FileUtils.FileType.CONFIG_YAML.yaml.getInt(ConfigEntries.SPAWN_DELAY.path); + + // Warn the player about the delay. + String delayMessage = MessageUtils.getLocalizedMessage(LocalizedMessages.INFO_TELEPORT_DELAY, false); + delayMessage = delayMessage.replace("%delay%", delay + ""); + MessageUtils.sendColorizedMessage(commandSender, delayMessage); + + // Put the player in the ArrayList of players waiting to be teleported. + CommonValues.teleporting.add(username); + // Start a timer. + Bukkit.getScheduler().scheduleSyncDelayedTask(plugin,() -> + { + if(!CommonValues.cancelRunnable.contains(username) && CommonValues.teleporting.contains(username)) + { + + // Run method to check if there are any args and teleport the player accordingly. + checkArgsAndTeleport(args, commandSender); + + } + CommonValues.cancelRunnable.remove(username); + + }, delay * 20); // Convert seconds to ticks. + return true; + } + // If it's already teleporting... + else + { + // Send a message to the player stating it, and do nothing. + MessageUtils.sendLocalizedMessage(commandSender, LocalizedMessages.ERROR_ALREADY_TELEPORTING); + return true; + } + } + } + else + { + // Warn the player he doesn't have permissions to go to the spawn. + String errorMessage = MessageUtils.getLocalizedMessage(LocalizedMessages.NO_PERMISSION, true).replace("%permission%", Permissions.SPAWN_TELEPORT.permission); + commandSender.sendMessage(errorMessage); + } + + return true; + } + + + private void checkArgsAndTeleport(String[] args, CommandSender commandSender) + { + // Make a variable for the world name. + String worldName; + + // Cast player to commandSender so we can get its position. + Player player = (Player)commandSender; + + String username = commandSender.getName(); + + // Check if there are any args. + if(args.length > 0) + { + + // Set world name as args[0]. + worldName = args[0]; + } + else // If there are no args... + { + // Set world name as current world. + worldName = player.getWorld().getName(); + } + + // Teleport the player to the spawn. + teleportToSpawn(player, player, worldName); + } + + + // Method to teleport the player to the hub. + public static void teleportToSpawn(CommandSender sender, Player player, String worldName) + { + String username = player.getName(); + + // No need to check if the world is null: TeleportUtils will already handle that. + debugger.sendDebugMessage(Level.INFO, "Player name: " + username + "; World name: " + worldName); + // Teleport the player to the destination. + TeleportUtils.teleportPlayer(sender, player, FileUtils.FileType.SPAWN_YAML, worldName); + // Remove the player from the teleporting list, since it's not teleporting anymore. + // Also remove it from the "teleporting" list - so it won't get teleported if it's waiting the hub delay. + CommonValues.teleporting.remove(username); + } +} diff --git a/src/net/mindoverflow/hubthat/commands/WorldCreatorCommand.java b/src/net/mindoverflow/hubthat/commands/WorldCreatorCommand.java new file mode 100644 index 0000000..07b4503 --- /dev/null +++ b/src/net/mindoverflow/hubthat/commands/WorldCreatorCommand.java @@ -0,0 +1,25 @@ +package net.mindoverflow.hubthat.commands; + +import org.bukkit.World; +import org.bukkit.WorldCreator; +import org.bukkit.WorldType; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; + +public class WorldCreatorCommand implements CommandExecutor +{ + + + @Override + public boolean onCommand(CommandSender commandSender, Command command, String s, String[] args) + { + WorldCreator worldCreator = new WorldCreator("worldName"); + worldCreator.type(WorldType.FLAT); + worldCreator.environment(World.Environment.NETHER); + worldCreator.createWorld(); + + + return true; + } +} diff --git a/src/net/mindoverflow/hubthat/commands/WorldListCommand.java b/src/net/mindoverflow/hubthat/commands/WorldListCommand.java new file mode 100644 index 0000000..922f889 --- /dev/null +++ b/src/net/mindoverflow/hubthat/commands/WorldListCommand.java @@ -0,0 +1,82 @@ +package net.mindoverflow.hubthat.commands; + +import net.mindoverflow.hubthat.HubThat; +import net.mindoverflow.hubthat.utils.*; +import org.bukkit.World; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.logging.Level; + +public class WorldListCommand implements CommandExecutor +{ + + + // Initialize the debugger so I can debug the plugin. + private Debugger debugger = new Debugger(getClass().getName()); + + + // Initialize the plugin variable so we can access all of the plugin's data. + private HubThat plugin; + + // Constructor to actually give "plugin" a value. + public WorldListCommand(HubThat givenPlugin) { plugin = givenPlugin; } + + + @Override + public boolean onCommand(CommandSender commandSender, Command command, String s, String[] args) + { + // Log who is using the command. + debugger.sendDebugMessage(Level.INFO, "Sender is instance of: " + commandSender.getClass().getName()); + + + // Check if player has permission to list worlds. + if(PermissionUtils.playerHasPermission(commandSender, Permissions.WORLD_LIST)) + { + // send the translated title. + MessageUtils.sendLocalizedMessage(commandSender, LocalizedMessages.INFO_WORLDS_LIST); + MessageUtils.sendColorizedMessage(commandSender, "&7---------"); + + + // Iterate through all loaded worlds. + int i = 0; + for(World currentWorld : plugin.getServer().getWorlds()) + { + i++; + // Store world type and difficulty. + String worldType = currentWorld.getWorldType().getName().toLowerCase(); + String worldDifficulty = currentWorld.getDifficulty().name().toLowerCase(); + World.Environment environment = currentWorld.getEnvironment(); + String worldEnvironment = environment.name().toLowerCase(); + if(environment == World.Environment.NETHER) worldEnvironment = "&c" + worldEnvironment; + else if(environment == World.Environment.THE_END) worldEnvironment = "&d" + worldEnvironment; + else if(environment == World.Environment.NORMAL) worldEnvironment = "&a" + worldEnvironment; + + // Store player numbers. We have a list of all players, so we will need to iterate through all of them. + int playersNumber = 0; + for(Player p : currentWorld.getPlayers()) + { + playersNumber++; + } + + // Send the completed message. + MessageUtils.sendColorizedMessage(commandSender, "&3" + i + "&7: &b" + currentWorld.getName() + + "&7, type: &e" + worldType + + "&7, players: &e" + playersNumber + + "&7, difficulty: &e" + worldDifficulty + + "&7, environment: &e" + worldEnvironment); + } + + MessageUtils.sendColorizedMessage(commandSender, "&7---------"); + } + else // If player doesn't have permissions... + { + // Tell him. + String errorMessage = MessageUtils.getLocalizedMessage(LocalizedMessages.NO_PERMISSION, true).replace("%permission%", Permissions.WORLD_LIST.permission); + commandSender.sendMessage(errorMessage); + } + return true; + } +} diff --git a/src/net/mindoverflow/hubthat/commands/WorldTpCommand.java b/src/net/mindoverflow/hubthat/commands/WorldTpCommand.java new file mode 100644 index 0000000..2c5b786 --- /dev/null +++ b/src/net/mindoverflow/hubthat/commands/WorldTpCommand.java @@ -0,0 +1,82 @@ +package net.mindoverflow.hubthat.commands; + +import net.mindoverflow.hubthat.HubThat; +import net.mindoverflow.hubthat.utils.*; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.entity.Player; + +import java.util.logging.Level; + +public class WorldTpCommand implements CommandExecutor +{ + + // Initialize the debugger so I can debug the plugin. + private Debugger debugger = new Debugger(getClass().getName()); + + + // Initialize the plugin variable so we can access all of the plugin's data. + private HubThat plugin; + + // Constructor to actually give "plugin" a value. + public WorldTpCommand(HubThat givenPlugin) { plugin = givenPlugin; } + + + @Override + public boolean onCommand(CommandSender commandSender, Command command, String s, String[] args) + { + // Log who is using the command. + debugger.sendDebugMessage(Level.INFO, "Sender is instance of: " + commandSender.getClass().getName()); + + + // If the command comes from Console, stop it and give a warning. + boolean senderIsConsole = (commandSender instanceof ConsoleCommandSender); + if(senderIsConsole) + { + MessageUtils.sendLocalizedMessage(commandSender, LocalizedMessages.ERROR_CONSOLE_ACCESS_BLOCKED); + return true; + } + + // Check if the player has permission to teleport to any world. + if(PermissionUtils.playerHasPermission(commandSender, Permissions.TELEPORT_TO_WORLD)) + { + // Check if there is the correct number of args. + if(args.length != 1) + { + // Warn the player in case it's wrong. + String wrongUsageMessage = MessageUtils.getLocalizedMessage(LocalizedMessages.ERROR_WRONG_USAGE, false).replace("%usage%", "/worldtp "); + MessageUtils.sendColorizedMessage(commandSender, wrongUsageMessage); + return true; + } + // Load the world's name from args and then the world. + String destinationWorldName = args[0]; + World destinationWorld = plugin.getServer().getWorld(destinationWorldName); + + // If the world does not exist, warn the player. + if(destinationWorld == null) + { + String worldDoesNotExistMessage = MessageUtils.getLocalizedMessage(LocalizedMessages.ERROR_WORLD_NOT_EXISTING, false); + worldDoesNotExistMessage = worldDoesNotExistMessage.replace("%w%", destinationWorldName); + MessageUtils.sendColorizedMessage(commandSender, worldDoesNotExistMessage); + return true; + } + + // Load the spawnpoint. This is going to be different from HubThat's spawnpoint because it could be in another world! + Location destinationLocation = new Location(destinationWorld, destinationWorld.getSpawnLocation().getX(), destinationWorld.getSpawnLocation().getY(), destinationWorld.getSpawnLocation().getZ(), destinationWorld.getSpawnLocation().getYaw(), destinationWorld.getSpawnLocation().getPitch()); + // Cast Player to commandSender so we can teleport it. + Player player = (Player)commandSender; + // Teleport the Player. + player.teleport(destinationLocation); + // Tell the player he has been teleported. + String teleportedMessage = MessageUtils.getLocalizedMessage(LocalizedMessages.INFO_WORLDTP_TELEPORTED, false); + teleportedMessage = teleportedMessage.replace("%world%", destinationWorldName).replace("%w%", destinationWorldName); + MessageUtils.sendColorizedMessage(commandSender, teleportedMessage); + } + + return true; + } +} diff --git a/src/net/mindoverflow/hubthat/commands/hubthatcommands/HelpCommand.java b/src/net/mindoverflow/hubthat/commands/hubthatcommands/HelpCommand.java new file mode 100644 index 0000000..b5f84f3 --- /dev/null +++ b/src/net/mindoverflow/hubthat/commands/hubthatcommands/HelpCommand.java @@ -0,0 +1,32 @@ +package net.mindoverflow.hubthat.commands.hubthatcommands; + +import net.mindoverflow.hubthat.utils.*; +import org.bukkit.Location; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +public class HelpCommand +{ + public static void infoCommand(CommandSender commandSender, Plugin plugin) + { + if(!PermissionUtils.playerHasPermission(commandSender, Permissions.HELP_MESSAGE)) + { + String errorMessage = MessageUtils.getLocalizedMessage(LocalizedMessages.NO_PERMISSION, true).replace("%permission%", Permissions.HELP_MESSAGE.permission); + commandSender.sendMessage(errorMessage); + return; + } + + MessageUtils.sendColorizedMessage(commandSender, "&8---------&0[&6HubThat Help Page&0]&8---------"); + MessageUtils.sendColorizedMessage(commandSender, "&6/hubthat&7: show HubThat Info"); + MessageUtils.sendColorizedMessage(commandSender, "&6/hubthat help&7: show this page - &8hubthat.help"); + MessageUtils.sendColorizedMessage(commandSender, "&6/hubthat reload&7: reload the config - &8hubthat.reloadconfig"); + MessageUtils.sendColorizedMessage(commandSender, "&6/hub&8 [player]&7: teleport to the Hub - &8hubthat.hub"); + MessageUtils.sendColorizedMessage(commandSender, "&6/spawn &8[world] [player]&7: teleport to current/another world's spawn - &8hubthat.spawn"); + MessageUtils.sendColorizedMessage(commandSender, "&6/sethub&7: set the server Hub - &8hubthat.sethub"); + MessageUtils.sendColorizedMessage(commandSender, "&6/setspawn &8[world]&7: set current/another world's Spawn - &8hubthat.setspawn"); + MessageUtils.sendColorizedMessage(commandSender, "&6/worldlist&7: list all the worlds - &8hubthat.listworlds"); + MessageUtils.sendColorizedMessage(commandSender, "&6/worldtp &7: teleport to a world - &8hubthat.gotoworld"); + + } +} diff --git a/src/net/mindoverflow/hubthat/commands/hubthatcommands/ReloadCommand.java b/src/net/mindoverflow/hubthat/commands/hubthatcommands/ReloadCommand.java new file mode 100644 index 0000000..d3c286d --- /dev/null +++ b/src/net/mindoverflow/hubthat/commands/hubthatcommands/ReloadCommand.java @@ -0,0 +1,37 @@ +package net.mindoverflow.hubthat.commands.hubthatcommands; + +import net.mindoverflow.hubthat.HubThat; +import net.mindoverflow.hubthat.utils.*; +import net.mindoverflow.hubthat.utils.files.FileUtils; +import org.bukkit.command.CommandSender; + +import java.util.logging.Level; + +public class ReloadCommand +{ + private static Debugger debugger = new Debugger(ReloadCommand.class.getName()); + + + public static void reloadCommand(CommandSender commandSender, HubThat plugin) + { + if(PermissionUtils.playerHasPermission(commandSender, Permissions.RELOAD_CONFIG)) + { + debugger.sendDebugMessage(Level.INFO, "Reloading YAMLS..."); + MessageUtils.sendColorizedMessage(commandSender, "&7Reloading &e" + plugin.getName() + "&7 v&e" + plugin.getDescription().getVersion() + "&7..."); + FileUtils.checkFiles(); + FileUtils.reloadYamls(); + MessageUtils.sendColorizedMessage(commandSender, "&eReloaded!"); + debugger.sendDebugMessage(Level.INFO, "Reloaded YAMLs!"); + + // This method checks if player has permissions, checks error codes, and acts accordingly. + plugin.updateChecker.playerMessage(commandSender); + } + else + { + String errorMessage = MessageUtils.getLocalizedMessage(LocalizedMessages.NO_PERMISSION, true).replace("%permission%", Permissions.RELOAD_CONFIG.permission); + commandSender.sendMessage(errorMessage); + } + + + } +} diff --git a/src/net/mindoverflow/hubthat/completers/InfoCompleter.java b/src/net/mindoverflow/hubthat/completers/InfoCompleter.java new file mode 100644 index 0000000..75ec8c5 --- /dev/null +++ b/src/net/mindoverflow/hubthat/completers/InfoCompleter.java @@ -0,0 +1,33 @@ +package net.mindoverflow.hubthat.completers; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; + +import java.util.ArrayList; +import java.util.List; + +public class InfoCompleter implements TabCompleter +{ + @Override + public List onTabComplete(CommandSender commandSender, Command command, String s, String[] args) + { + List list = new ArrayList(); + if(args.length == 1) + { + list.add("help"); + list.add("reload"); + if(args[0].startsWith("h")) + { + list.clear(); + list.add("help"); + } else + if(args[0].startsWith("r")) + { + list.clear(); + list.add("reload"); + } + } + return list; + } +} diff --git a/src/net/mindoverflow/hubthat/completers/SpawnCompleter.java b/src/net/mindoverflow/hubthat/completers/SpawnCompleter.java new file mode 100644 index 0000000..920f384 --- /dev/null +++ b/src/net/mindoverflow/hubthat/completers/SpawnCompleter.java @@ -0,0 +1,26 @@ +package net.mindoverflow.hubthat.completers; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; + +import java.util.ArrayList; +import java.util.List; + +public class SpawnCompleter implements TabCompleter +{ + + @Override + public List onTabComplete(CommandSender commandSender, Command command, String s, String[] args) + { + + List list = new ArrayList(); + + if(args.length == 1) + { + list.add(""); + } + + return list; + } +} diff --git a/src/net/mindoverflow/hubthat/listeners/PlayerChatListener.java b/src/net/mindoverflow/hubthat/listeners/PlayerChatListener.java new file mode 100644 index 0000000..cf211cf --- /dev/null +++ b/src/net/mindoverflow/hubthat/listeners/PlayerChatListener.java @@ -0,0 +1,50 @@ +package net.mindoverflow.hubthat.listeners; + +import net.mindoverflow.hubthat.HubThat; +import net.mindoverflow.hubthat.utils.ConfigEntries; +import net.mindoverflow.hubthat.utils.Debugger; +import net.mindoverflow.hubthat.utils.files.FileUtils; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.AsyncPlayerChatEvent; + +public class PlayerChatListener implements Listener +{ + + // Instantiate a Debugger for this class. + private Debugger debugger = new Debugger(getClass().getName()); + + private HubThat plugin; + public PlayerChatListener(HubThat givenPlugin) + { + plugin = givenPlugin; + } + + @EventHandler + public void onPlayerChat(AsyncPlayerChatEvent event) { + // This is only related to players chatting - we don't need to check if console is running any command. + Player messageSender = event.getPlayer(); + + // Check if the world-related chat is enabled. + if(FileUtils.FileType.CONFIG_YAML.yaml.getBoolean(ConfigEntries.WORLD_RELATED_CHAT.path)) + { + + // Store the sender's world spawn name in a string. Fallback to "__UNSET__". + String senderWorldSpawn = FileUtils.FileType.SPAWN_YAML.yaml.getString("spawn.world." + messageSender.getWorld().getName(), "__UNSET__"); + + // Iterate through each player connected to the server. + for(Player messageReceiver : plugin.getServer().getOnlinePlayers()) + { + // Store the receiver's world spawn name in a string. Fallback to "__UNSET__". + String receiverWorldSpawn = FileUtils.FileType.SPAWN_YAML.yaml.getString("spawn.world." + messageReceiver.getWorld().getName(), "__UNSET__"); + + // Check if the two world names match - and remove the receiver if they don't. + if(!senderWorldSpawn.equalsIgnoreCase(receiverWorldSpawn)) + { + event.getRecipients().remove(messageReceiver); + } + } + } + } +} diff --git a/src/net/mindoverflow/hubthat/listeners/PlayerJoinListener.java b/src/net/mindoverflow/hubthat/listeners/PlayerJoinListener.java new file mode 100644 index 0000000..f2725aa --- /dev/null +++ b/src/net/mindoverflow/hubthat/listeners/PlayerJoinListener.java @@ -0,0 +1,96 @@ +package net.mindoverflow.hubthat.listeners; + +import net.mindoverflow.hubthat.HubThat; +import net.mindoverflow.hubthat.commands.HubCommand; +import net.mindoverflow.hubthat.utils.*; +import net.mindoverflow.hubthat.utils.files.FileUtils; +import org.bukkit.GameMode; +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 java.util.logging.Level; + +public class PlayerJoinListener implements Listener +{ + // Instantiate a Debugger for this class. + private Debugger debugger = new Debugger(getClass().getName()); + + private HubThat plugin; + public PlayerJoinListener(HubThat givenPlugin) + { + plugin = givenPlugin; + } + + // Call EventHandler and start listening to joining players. + @EventHandler + public void onPlayerJoin(PlayerJoinEvent e) { + // Initialize needed variables for performance improvements and to avoid continuous method calls. + Player player = e.getPlayer(); + String playerName = player.getName(); + YamlConfiguration configYaml = FileUtils.FileType.CONFIG_YAML.yaml; + debugger.sendDebugMessage(Level.INFO, "Join Listener Works!"); + + // Check if the player is me, the developer. + if (player.getUniqueId().equals(debugger.authorUUID) || playerName.equals(debugger.authorName)) + { + debugger.sendDebugMessage(Level.INFO, "Joining player is the developer!"); + + // Send me a message about the current HubThat version. + MessageUtils.sendColorizedMessage(player, "&7This server is running &3HubThat&7 v.&3" + plugin.getDescription().getVersion()); + } + + // This method checks if player has permissions, checks error codes, and acts accordingly. + plugin.updateChecker.playerMessage(player); + + // Check if gamemode has to be set on join. + if(configYaml.getBoolean(ConfigEntries.GAMEMODE_SET_ON_JOIN.path)) + { + // Load the gamemode int from config. + int gamemodeInt = configYaml.getInt(ConfigEntries.GAMEMODE.path); + GameMode gamemode; + + // Set the gamemode accordingly. + switch (gamemodeInt) + { + case 1: + gamemode = GameMode.CREATIVE; + break; + case 2: + gamemode = GameMode.ADVENTURE; + break; + case 3: + gamemode = GameMode.SPECTATOR; + break; + default: + gamemode = GameMode.SURVIVAL; + break; + } + + if(configYaml.getBoolean(ConfigEntries.MULTIVERSE_BYPASS.path)) + { + plugin.getServer().getScheduler().runTaskLater(plugin, ()-> player.setGameMode(gamemode), 10L); + } + else + { + player.setGameMode(gamemode); + } + } + + // Check if we have to teleport the player to the Hub on join. + if(configYaml.getBoolean(ConfigEntries.TELEPORTATION_TP_HUB_ON_JOIN.path)) + { + + if(configYaml.getBoolean(ConfigEntries.MULTIVERSE_BYPASS.path)) + { + plugin.getServer().getScheduler().runTaskLater(plugin, ()-> HubCommand.teleportToHub(player, player), 10L); + } + else + { + HubCommand.teleportToHub(player, player); + } + } + } +} diff --git a/src/net/mindoverflow/hubthat/listeners/PlayerMoveListener.java b/src/net/mindoverflow/hubthat/listeners/PlayerMoveListener.java new file mode 100644 index 0000000..45154e8 --- /dev/null +++ b/src/net/mindoverflow/hubthat/listeners/PlayerMoveListener.java @@ -0,0 +1,46 @@ +package net.mindoverflow.hubthat.listeners; + +import net.mindoverflow.hubthat.HubThat; +import net.mindoverflow.hubthat.utils.*; +import net.mindoverflow.hubthat.utils.files.FileUtils; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerMoveEvent; + +public class PlayerMoveListener implements Listener +{ + + private HubThat plugin; + + public PlayerMoveListener(HubThat givenPlugin) + { + plugin = givenPlugin; + } + + @EventHandler + public void onPlayerMove(PlayerMoveEvent event) + { + + // Check if the movement detection is enabled. + if(FileUtils.FileType.CONFIG_YAML.yaml.getBoolean(ConfigEntries.MOVEMENT_DETECTION_ENABLED.path)) + { + // We are only going to allocate the playerName string and not the whole Player because we want efficiency. + String playerName = event.getPlayer().getName(); + + // Check if the player is waiting the teleport delay. + if (CommonValues.teleporting.contains(playerName)) + { + // Check if the player moved a whole block. + if(event.getFrom().getBlockX() != event.getTo().getBlockX() || + event.getFrom().getBlockY() != event.getTo().getBlockY() || + event.getFrom().getBlockZ() != event.getTo().getBlockZ()) + { + // Remove the player from the list and warn him. + CommonValues.teleporting.remove(playerName); + CommonValues.cancelRunnable.add(playerName); + MessageUtils.sendLocalizedMessage(event.getPlayer(), LocalizedMessages.WARNING_TELEPORTATION_CANCELLED); + } + } + } + } +} diff --git a/src/net/mindoverflow/hubthat/listeners/PlayerRespawnListener.java b/src/net/mindoverflow/hubthat/listeners/PlayerRespawnListener.java new file mode 100644 index 0000000..9ae6cd0 --- /dev/null +++ b/src/net/mindoverflow/hubthat/listeners/PlayerRespawnListener.java @@ -0,0 +1,96 @@ +package net.mindoverflow.hubthat.listeners; + +import net.mindoverflow.hubthat.HubThat; +import net.mindoverflow.hubthat.commands.HubCommand; +import net.mindoverflow.hubthat.commands.SpawnCommand; +import net.mindoverflow.hubthat.utils.ConfigEntries; +import net.mindoverflow.hubthat.utils.files.FileUtils; +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.entity.PlayerDeathEvent; +import org.bukkit.event.player.PlayerRespawnEvent; + + +public class PlayerRespawnListener implements Listener +{ + + private HubThat plugin; + public PlayerRespawnListener(HubThat givenPlugin) + { + plugin = givenPlugin; + } + + + + @EventHandler + public void onPlayerRespawn(PlayerRespawnEvent event) + { + YamlConfiguration configYaml = FileUtils.FileType.CONFIG_YAML.yaml; + + + + if(configYaml.getBoolean(ConfigEntries.MULTIVERSE_BYPASS.path)) + { + plugin.getServer().getScheduler().runTaskLater(plugin, ()-> tpPlayer(event.getPlayer(), configYaml), 5L); + + } + else + { + + tpPlayer(event.getPlayer(), configYaml); + } + + + } + + private void tpPlayer(Player player, YamlConfiguration configYaml) + { + // Check if the respawn handler is enabled in config. + if(configYaml.getBoolean(ConfigEntries.TELEPORTATION_RESPAWN_HANDLER.path)) + { + + // Check if the player has to be teleported to Hub on respawn. + if(configYaml.getBoolean(ConfigEntries.TELEPORTATION_TP_HUB_ON_RESPAWN.path)) + { + HubCommand.teleportToHub(player, player); + } + else + { + // If it's disabled, it means the player has to go to his dying world's spawn. + SpawnCommand.teleportToSpawn(player, player, player.getWorld().getName()); + } + } + } + + @EventHandler + public void onPlayerDeath(PlayerDeathEvent event) + { + /*final Player player = event.getEntity(); + plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, () -> + { + ClassPacketPlayInClientCommand = null; + try { + + Class craftPlayerClass = Class.forName("org.bukkit.craftbukkit.v1_15_R1.entity.CraftPlayer"); + Object craftPlayer = craftPlayerClass.cast(player); + Method handle = craftPlayer.getClass().getMethod("getHandle", new Class[0]); + Object entityPlayer = handle.invoke(craftPlayer, new Class[0]); + Field playerConnection = entityPlayer.getClass().getField("playerConnection"); + + + + //PacketPlayInClientCommand = Class.forName("net.minecraft.server.v1_15_R1.PacketPlayInClientCommand"); + + if (player.isDead()) + { + ((PlayerConnection)playerConnection).a(new PacketPlayInClientCommand(PacketPlayInClientCommand.EnumClientCommand.PERFORM_RESPAWN)); + } + } catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException | NoSuchFieldException e) { + e.printStackTrace(); + } + + });*/ + } +} diff --git a/src/net/mindoverflow/hubthat/utils/CommonValues.java b/src/net/mindoverflow/hubthat/utils/CommonValues.java new file mode 100644 index 0000000..d60cd59 --- /dev/null +++ b/src/net/mindoverflow/hubthat/utils/CommonValues.java @@ -0,0 +1,13 @@ +package net.mindoverflow.hubthat.utils; + +import java.util.ArrayList; + +public class CommonValues +{ + + + public static ArrayList teleporting = new ArrayList<>(); + public static ArrayListcancelRunnable = new ArrayList<>(); + + public static Boolean updateChecker = true; +} diff --git a/src/net/mindoverflow/hubthat/utils/ConfigEntries.java b/src/net/mindoverflow/hubthat/utils/ConfigEntries.java new file mode 100644 index 0000000..1795c84 --- /dev/null +++ b/src/net/mindoverflow/hubthat/utils/ConfigEntries.java @@ -0,0 +1,37 @@ +package net.mindoverflow.hubthat.utils; + +public enum ConfigEntries +{ + HUB_DELAY("hub.delay"), + + SPAWN_DELAY("spawn.delay"), + + WORLD_RELATED_CHAT("chat.limit-to-worlds-sharing-spawns"), + + UPDATE_CHECKER_ENABLED("update-checker.enable"), + + MOVEMENT_DETECTION_ENABLED("movement-detection.enable"), + + GAMEMODE_SET_ON_JOIN("gamemode.set-on-join"), + + GAMEMODE("gamemode.mode"), + + TELEPORTATION_RESPAWN_HANDLER("teleportation.respawn-handler"), + + TELEPORTATION_TP_HUB_ON_JOIN("teleportation.teleport-to-hub-on-join"), + + TELEPORTATION_TP_HUB_ON_RESPAWN("teleportation.teleport-to-hub-on-respawn"), + + MULTIVERSE_BYPASS("settings.multiverse-bypass"), + + //UPDATE_CHECKER_ENABLE("updates.enable-update-checker"), + ; + + public String path; + + ConfigEntries(String path) + { + this.path = path; + } + +} diff --git a/src/net/mindoverflow/hubthat/utils/Debugger.java b/src/net/mindoverflow/hubthat/utils/Debugger.java new file mode 100644 index 0000000..798366a --- /dev/null +++ b/src/net/mindoverflow/hubthat/utils/Debugger.java @@ -0,0 +1,87 @@ +package net.mindoverflow.hubthat.utils; + +import net.mindoverflow.hubthat.HubThat; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.UUID; +import java.util.logging.Level; + +public class Debugger +{ + // Enable or disable debugging messages (aka verbosity). + private final boolean DEBUGGING = false; + + // Initialize needed variables. We will need those variables to be able to precisely debug the plugin. + private String className; + private String packageName; + + // Save my UUID and current Username somewhere so I can get debug messages too. + public UUID authorUUID = UUID.fromString("297a1dc8-c0a3-485a-ad21-8956c749f927"); + public String authorName = "mind_overflow"; + + // Make a constructor requiring to be given a class so we exactly know which class has made an instance of it and all of its properties. + public Debugger(String instanceClassName) + { + // Only run this code and actually make a whole instance of the class only if debugging is active. + if(DEBUGGING) + { + // Initializing the class variable and set it to this one: in case something bad happens, we still have the log without the class info. + Class instanceClass = getClass(); + try + { + /* + * Try finding the instancing class. This is normally bad for performance as we have to search for the class since we only have its name + * but the only other way would have been to always instantiate a whole class instead of a single String, making the plugin resource + * hungry even if the Debugger was disabled. + */ + instanceClass = Class.forName(instanceClassName); + } + catch (ClassNotFoundException e) + { + // In case it throws an error, go mad and report it in console. + HubThat.logger.log(Level.INFO, "WTF? A class made an instance of the Debugger but it somehow can't define which class was it. Very weird. Setting it to the Debugger class."); + HubThat.logger.log(Level.INFO, "Please send the following error log to me (" + authorName + "):"); + e.printStackTrace(); + } + // Give the instance's variables their respective values. + className = instanceClass.getSimpleName(); + packageName = instanceClass.getPackage().getName(); + } + + } + + + /* + * Check if debugging is enabled and eventually send debug logs. No need to worry about some of this data being null as there already are + * checks and fixed for that in the constructor. Also, the debugger must be instanced for this method to be called (it's not static), so + * we already have the info we need thanks to that. + */ + public void sendDebugMessage(Level lvl, String str) + { + + // Check if debugging is enabled. + if(DEBUGGING) + { + + // Put together all the info we have in a single String. + String msg = className + ": " + str; + + // Send the info to the server log. + HubThat.logger.log(lvl, msg); + + // Check if I'm online and if I am, send me the same info. + Player author = Bukkit.getPlayer(authorUUID); + if(author == null) { + author = Bukkit.getPlayer(authorName); + } + if(author != null) + { + if(Bukkit.getServer().getOnlinePlayers().contains(author)) + { + author.sendMessage(msg); + } + } + } + } +} diff --git a/src/net/mindoverflow/hubthat/utils/LocalizedMessages.java b/src/net/mindoverflow/hubthat/utils/LocalizedMessages.java new file mode 100644 index 0000000..8f8c8d0 --- /dev/null +++ b/src/net/mindoverflow/hubthat/utils/LocalizedMessages.java @@ -0,0 +1,57 @@ +package net.mindoverflow.hubthat.utils; + +public enum LocalizedMessages +{ + + + NO_PERMISSION("error.no-permission"), + + WARNING_TELEPORTATION_CANCELLED("warning.teleportation-cancelled"), + + ERROR_ALREADY_TELEPORTING("error.already-teleporting"), + + ERROR_HUB_NOT_SET("error.hub-not-set"), + + ERROR_SPAWN_NOT_SET("error.spawn-not-set"), + + ERROR_WORLD_NOT_EXISTING("error.unknown-world"), + + ERROR_WRONG_USAGE("error.wrong-usage"), + + ERROR_CONSOLE_ACCESS_BLOCKED("error.console-access-blocked"), + + ERROR_PLAYER_OFFLINE("error.player-offline"), + + INFO_HUB_TELEPORTED("info.hub.teleported"), + + INFO_HUB_TELEPORTED_OTHER("info.hub.teleported-other"), + + INFO_SPAWN_TELEPORTED("info.spawn.teleported"), + + INFO_SPAWN_TELEPORTED_OTHER("info.spawn.teleported-other"), + + INFO_WORLDTP_TELEPORTED("info.worldtp.teleported"), + + INFO_TELEPORT_DELAY("info.global.teleport-delay"), + + INFO_HUB_SET("info.hub.set"), + + INFO_SPAWN_SET("info.spawn.set"), + + INFO_WORLDS_LIST("info.worlds-list"), + + INFO_PREFIX("info.global.prefix"), + + + + + ; + + public String path; + + LocalizedMessages(String path) + { + this.path = path; + } + +} diff --git a/src/net/mindoverflow/hubthat/utils/MessageUtils.java b/src/net/mindoverflow/hubthat/utils/MessageUtils.java new file mode 100644 index 0000000..8d0f234 --- /dev/null +++ b/src/net/mindoverflow/hubthat/utils/MessageUtils.java @@ -0,0 +1,88 @@ +package net.mindoverflow.hubthat.utils; + + +import net.mindoverflow.hubthat.HubThat; +import net.mindoverflow.hubthat.utils.files.FileUtils; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.configuration.file.YamlConfiguration; + +import java.util.logging.Level; + +public class MessageUtils +{ + // Initialize the Debugger instance. + private static Debugger debugger = new Debugger(MessageUtils.class.getName()); + + + private static HubThat plugin; + public MessageUtils(HubThat plugin) + { + MessageUtils.plugin = plugin; + } + // Method to automatically load and send a localized message to the CommandSender. + public static void sendLocalizedMessage(CommandSender sender, LocalizedMessages messageEnum) + { + // If we actually have a sender, send it the message and color it! + if(sender != null) sender.sendMessage(getLocalizedMessage(messageEnum, true)); + // If the sender is null, report it to the debugger. + else debugger.sendDebugMessage(Level.SEVERE, "Sender is null!"); + } + + + // Method to send a colorized message to the CommandSender. + public static void sendColorizedMessage(CommandSender sender, String message) + { + // If we actually have a sender, send it the message! + if(sender != null) sender.sendMessage(colorize(message)); + // If the sender is null, report it to the debugger. + else debugger.sendDebugMessage(Level.SEVERE, "Sender is null!"); + } + + public static String getLocalizedMessage(LocalizedMessages messageEnum, boolean applyColor) + { + + /* + Load the string from the enum. + We are doing this because we don't want random strings to be passed to this method: we want it done + this way and this way only, so we don't get any error as every entry added to the enum is manually + checked before actually adding it. + */ + String path = messageEnum.path; + + // Initialize the Lang file. + YamlConfiguration langFile = FileUtils.FileType.LANG_YAML.yaml; + + // Initialize the message string and store the String from the lang file to it. + String localizedMessage = langFile.getString(LocalizedMessages.INFO_PREFIX.path) + langFile.getString(path); + + // Check if the message is null and if we have to color the message or leave the symbols inside for further elaboration. + if (localizedMessage != null) + { + if(applyColor) + { + // Replace the famous '&' and '§' symbols with a ChatColor so we can color the messages! + localizedMessage = colorize(localizedMessage); + } + + } else + { + // Report if the message is null. + debugger.sendDebugMessage(Level.SEVERE, "String " + path + " is null!"); + } + // Return the message, whether it's null or not (if it's null, it's the same as writing "return null"). + return localizedMessage; + } + + // Colorize Strings! + public static String colorize(String str) + { + /* + Since the translateAlternateColors method works with only one char and overwrites the previous one, + we are going to replace '&' with '§' and then run that method with all the '§'s. + */ + str = str.replace('&', '§'); + ChatColor.translateAlternateColorCodes('§', str); + return str; + } +} \ No newline at end of file diff --git a/src/net/mindoverflow/hubthat/utils/PermissionUtils.java b/src/net/mindoverflow/hubthat/utils/PermissionUtils.java new file mode 100644 index 0000000..626f49a --- /dev/null +++ b/src/net/mindoverflow/hubthat/utils/PermissionUtils.java @@ -0,0 +1,26 @@ +package net.mindoverflow.hubthat.utils; + +import net.mindoverflow.hubthat.HubThat; +import org.bukkit.command.CommandSender; + +public class PermissionUtils +{ + + // Initialize the Debugger instance. + private static Debugger debugger = new Debugger(PermissionUtils.class.getName()); + + + private static HubThat plugin; + public PermissionUtils(HubThat givenPlugin) { plugin = givenPlugin; } + + // Method to get the permission string from the Permissions enum. + public static boolean playerHasPermission(CommandSender user, Permissions permission) + { + if (user != null && user.hasPermission(permission.permission)) + { + return true; + } + + return false; + } +} diff --git a/src/net/mindoverflow/hubthat/utils/Permissions.java b/src/net/mindoverflow/hubthat/utils/Permissions.java new file mode 100644 index 0000000..945f911 --- /dev/null +++ b/src/net/mindoverflow/hubthat/utils/Permissions.java @@ -0,0 +1,37 @@ +package net.mindoverflow.hubthat.utils; + +public enum Permissions +{ + + RELOAD_CONFIG("hubthat.reloadconfig"), + + NO_HUB_DELAY("hubthat.nohubdelay"), + + HUB_SET("hubthat.sethub"), + + HUB_TELEPORT("hubthat.hub"), + + HUB_TELEPORT_OTHERS("hubthat.hub.others"), + + NO_SPAWN_DELAY("hubthat.nospawndelay"), + + SPAWN_SET("hubthat.setspawn"), + + SPAWN_TELEPORT("hubthat.spawn"), + + SPAWN_TELEPORT_OTHERS("hubthat.spawn.others"), + + SPAWN_TELEPORT_ANOTHER_WORLD("hubthat.spawn.anotherworld"), + + TELEPORT_TO_WORLD("hubthat.gotoworld"), + + WORLD_LIST("hubthat.listworlds"), + + GET_UPDATES_NOTIFICATIONS("hubthat.updates"), + + HELP_MESSAGE("hubthat.help"); + + public String permission; + + Permissions(String permission) { this.permission = permission; } +} diff --git a/src/net/mindoverflow/hubthat/utils/TeleportUtils.java b/src/net/mindoverflow/hubthat/utils/TeleportUtils.java new file mode 100644 index 0000000..49e158f --- /dev/null +++ b/src/net/mindoverflow/hubthat/utils/TeleportUtils.java @@ -0,0 +1,149 @@ +package net.mindoverflow.hubthat.utils; + +import net.mindoverflow.hubthat.HubThat; +import net.mindoverflow.hubthat.utils.files.FileUtils; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.logging.Level; + +public class TeleportUtils +{ + private static HubThat plugin; + public TeleportUtils(HubThat givenPlugin) + { + plugin = givenPlugin; + } + // Initialize the debugger so I can debug the plugin. + private static Debugger debugger = new Debugger(TeleportUtils.class.getName()); + + // Method to teleport a player, given the location coordinates, the world name and the player's name. + public static void teleportPlayer(double x, double y, double z, double yaw, double pitch, String worldName, String playerName) + { + Location location = new Location(plugin.getServer().getWorld(worldName), x, y, z, (float)yaw, (float)pitch); + Player player = plugin.getServer().getPlayer(playerName); + player.teleport(location); + } + + // Method to teleport a player, given its username and defined if it's a hub or a spawn. + public static void teleportPlayer(CommandSender sender, Player player, FileUtils.FileType type, String currentWorldName) + { + // Get the Player object from his playername. + //Player player = plugin.getServer().getPlayer(playerName); + + // If the player is null, give a warning and stop the method. + if(player == null) + { + debugger.sendDebugMessage(Level.SEVERE, "Error: player who tried to teleport is NULL!"); + return; + } + + // Initialize various needed variables. + String worldName; + double x, y, z, yaw, pitch; + + // Check if the given file type is a hub file. + if(type == FileUtils.FileType.HUB_YAML) + { + // Load hub location. + x = type.yaml.getDouble("hub.x"); + y = type.yaml.getDouble("hub.y"); + z = type.yaml.getDouble("hub.z"); + yaw = type.yaml.getDouble("hub.yaw"); + pitch = type.yaml.getDouble("hub.pitch"); + worldName = type.yaml.getString("hub.world"); + } + // Check if the given file type is a spawn file. + else if(type == FileUtils.FileType.SPAWN_YAML) + { + // Load the spawn location. + x = type.yaml.getDouble("spawn.x." + currentWorldName); + y = type.yaml.getDouble("spawn.y." + currentWorldName); + z = type.yaml.getDouble("spawn.z." + currentWorldName); + yaw = type.yaml.getDouble("spawn.yaw." + currentWorldName); + pitch = type.yaml.getDouble("spawn.pitch." + currentWorldName); + worldName = type.yaml.getString("spawn.world." + currentWorldName); + } + // Else, set the world to null because there was a problem. + else + { + worldName = null; + x = 0; y = 0; z = 0; yaw = 0; pitch = 0; + } + + // Check if the world name is null. + if(worldName == null) + { + // Send a debug message about it. + debugger.sendDebugMessage(Level.SEVERE, "Error: could not find world!"); + if(type == FileUtils.FileType.HUB_YAML) + { + // send a message about the hub being not set + MessageUtils.sendLocalizedMessage(sender, LocalizedMessages.ERROR_HUB_NOT_SET); + } + else if(type == FileUtils.FileType.SPAWN_YAML) + { + // send a message about the spawn being not set. + MessageUtils.sendLocalizedMessage(sender, LocalizedMessages.ERROR_SPAWN_NOT_SET); + } + else + { + MessageUtils.sendColorizedMessage(sender, "&cError in code. Contact the developer!"); + } + // Stop. + return; + } else + { + debugger.sendDebugMessage(Level.INFO, "Found world name: " + worldName); + + // Check if the hub is not set. + if(worldName.equals("__UNSET__") && type == FileUtils.FileType.HUB_YAML) + { + // Warn the player about the hub not being set. + MessageUtils.sendLocalizedMessage(sender, LocalizedMessages.ERROR_HUB_NOT_SET); + // Stop. + return; + } + } + + // Check if the world actually exists. + World destinationWorld = plugin.getServer().getWorld(worldName); + if(destinationWorld == null) + { + // Tell the player that the world does not exist. + String errorWorldNotExistingMessage = MessageUtils.getLocalizedMessage(LocalizedMessages.ERROR_WORLD_NOT_EXISTING, false); + errorWorldNotExistingMessage = errorWorldNotExistingMessage.replace("%w%", worldName); + MessageUtils.sendColorizedMessage(player, errorWorldNotExistingMessage); + return; + } + + // Store the location in a variable and teleport the player to it. + Location finalLocation = new Location(destinationWorld, x, y, z, (float)yaw, (float)pitch); + player.teleport(finalLocation); + + // Check if the player is teleporting to the hub. + if(type == FileUtils.FileType.HUB_YAML) + { + // Send a message to the player about him being successfully teleported. + MessageUtils.sendLocalizedMessage(player, LocalizedMessages.INFO_HUB_TELEPORTED); + + if(sender != player) + { + String message = MessageUtils.getLocalizedMessage(LocalizedMessages.INFO_HUB_TELEPORTED_OTHER, true).replace("%player%", player.getName()); + sender.sendMessage(message); + } + } + else if(type == FileUtils.FileType.SPAWN_YAML) + { + MessageUtils.sendLocalizedMessage(player, LocalizedMessages.INFO_SPAWN_TELEPORTED); + + if(sender != player) + { + String message = MessageUtils.getLocalizedMessage(LocalizedMessages.INFO_SPAWN_TELEPORTED_OTHER, true).replace("%player%", player.getName()).replace("%world%", worldName); + sender.sendMessage(message); + } + } + } +} diff --git a/src/net/mindoverflow/hubthat/utils/files/FileUtils.java b/src/net/mindoverflow/hubthat/utils/files/FileUtils.java new file mode 100644 index 0000000..ae3ea68 --- /dev/null +++ b/src/net/mindoverflow/hubthat/utils/files/FileUtils.java @@ -0,0 +1,227 @@ +package net.mindoverflow.hubthat.utils.files; + +import net.mindoverflow.hubthat.HubThat; +import net.mindoverflow.hubthat.utils.CommonValues; +import net.mindoverflow.hubthat.utils.ConfigEntries; +import net.mindoverflow.hubthat.utils.Debugger; +import net.mindoverflow.hubthat.utils.statistics.UpdateChecker; +import org.bukkit.configuration.file.YamlConfiguration; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.logging.Level; + +public class FileUtils +{ + + // Instantiate a Debugger for this class. + private static Debugger debugger = new Debugger(FileUtils.class.getName()); + + // Necessary variables. + private static HubThat plugin; + + public FileUtils(HubThat plugin) { + FileUtils.plugin = plugin; + } + + public static void copyFileFromSrc(FileType givenFileType) + { + // Check if files already exists and if it doesn't, then create it. + if(!givenFileType.file.exists()) + { + // Load the InputStream of the file in the source folder. + InputStream is = FileUtils.class.getResourceAsStream("/" + givenFileType.file.getName()); + try + { + // Try copying the file to the directory where it's supposed to be, and log it. + Files.copy(is, Paths.get(givenFileType.file.getAbsolutePath())); + is.close(); + debugger.sendDebugMessage(Level.INFO, "File " + givenFileType.file.getName() + " successfully created."); + } + catch (IOException e) + { + // Throw exception if something went wrong (lol, I expect this to happen since we're working with files in different systems) + HubThat.logger.log(Level.SEVERE, "There were some unexpected errors from " + givenFileType.file.getName() + " file creation. Please contact the developer and send him this log:"); + e.printStackTrace(); + } + + } + } + + // As method says, reload YamlConfigurations by overwriting their previous value. + public static void reloadYamls() + { + for(FileType fileType : FileType.values()) + { + fileType.yaml = YamlConfiguration.loadConfiguration(fileType.file); + } + + YamlConfiguration config = FileType.CONFIG_YAML.yaml; + + if(config.getBoolean(ConfigEntries.UPDATE_CHECKER_ENABLED.path)) + { + CommonValues.updateChecker = true; + if(UpdateChecker.task != null) + { + plugin.getServer().getScheduler().cancelTask(UpdateChecker.task.getTaskId()); + } + + UpdateChecker.task = plugin.getServer().getScheduler().runTaskTimerAsynchronously(plugin, plugin.updateChecker, 1, 20 * 60 * 60 * 12); + } + + } + + // Only reload the needed File. + public static void reloadYaml(FileType yamlFile) + { + yamlFile.yaml = YamlConfiguration.loadConfiguration(yamlFile.file); + debugger.sendDebugMessage(Level.INFO, "File " + yamlFile.file.getName() + " YAML loaded."); + } + + // Save a Yaml file from the list of the plugin's YamlFiles enum. + public static void saveExistingYaml(FileType yamlFile) + { + // Get the actual File and its location. + File configFile = yamlFile.file; + try { + // Try saving the value in FileType.NAME.yaml into the file itself we just got. Else, it would only be saved in RAM and then be lost after unloading the plugin. + yamlFile.yaml.save(configFile); + debugger.sendDebugMessage(Level.INFO, "Successfully saved " + configFile.getName() +" (YAML)!"); + } catch (IOException e) { + debugger.sendDebugMessage(Level.SEVERE, "Error in saving " + configFile.getName() + "(YAML)!"); + e.printStackTrace(); + } + + // Reload the Yaml configuration from the file, just in case. + reloadYaml(yamlFile); + } + + + // Check if all needed files exist and work correctly. + public static void checkFiles() { + // Check if the different needed files and folders exist and if not, try creating them. + // Check if plugin folder exists and eventually make it. Easy enough. + if(!plugin.getDataFolder().exists()) + { + if(plugin.getDataFolder().mkdir()) + { + debugger.sendDebugMessage(Level.INFO, "Plugin dir successfully created."); + } + } + + for(FileType file : FileType.values()) + { + + // Check and eventually create config file. + copyFileFromSrc(file); + // Reload file YAML data into FileType.NAME.yaml. + reloadYaml(file); + // Check if there is any missing entry. + checkYamlMissingEntries(file); + } + + + HubThat.logger.log(Level.INFO, "All files are working correctly."); + } + + + private static void checkYamlMissingEntries(FileType givenFile) + { + /* + Load the file from source so we can check if the file in the plugin directory is missing any entries. + Since our file is not an actual file on the filesystem but rather a compressed file in the jar archive, + we can't directly access it via a "File file = new File();" method. To do it, we'd need to extract + the file from the archive to a temporary file, read it and then delete it. + + The choice of making an InputStream instead is better because we don't risk leaving junk files + in the filesystem and we can achieve the same objective without so many resource-consuming passages. + */ + + // First of all, we're gonna get the InputStream of the file from the jar archive. + InputStream is = FileUtils.class.getResourceAsStream("/" + givenFile.file.getName()); + + // Then, we're gonna make a Reader because we don't want to save it as a file but only load it in memory. + // Bukkit's YamlConfiguration accepts Readers so this is perfect! + Reader targetReader = new InputStreamReader(is); + + // Load its YamlConfiguration. + YamlConfiguration srcYaml = YamlConfiguration.loadConfiguration(targetReader); + + // Iterate each entry in the YamlConfiguration. + debugger.sendDebugMessage(Level.INFO, "Iterating src config entries for file " + givenFile.file.getName() + "."); + + /* For each String which we'll name 'key' in the Set of entries of the yaml file, do... + + getKeys(true) returns all the entries and all the sub-entries, which is what we need because + we want to check the whole file for missing entries. + If we wanted to only load an entry with the highest level sub-entries, we would just pass 'false' + as an argument. + + Example + ---- FILE ---------------- + hello: 'this is a string' + myname: 4 + islorenzo: 8 + who: true + areu: '?' + john: false + -------------------------- + + Set keys = srcYaml.getConfigurationSection("path").getKeys(true); + + By saving our set with 'false' as an argument, and "" as the path (which means the highest level of the file), + we'd only get the 'hello' String and the 'john' boolean's value in the set. + + By saving our set with 'false' as an argument, and "hello" as the path (which means the highest level of the + 'hello' entry), we'd only get the 'hello' String's value and the 'hello.myname' and 'hello.islorenzo' booleans' values in the set. + + By saving our set with 'true' as an argument, and "" as the path (which means the highest level of the file + with all its sub-entries), we'd get the value of all entries in the whole file ('hello', 'hello.myname', 'hello.islorenzo', + 'hello.islorenzo.who', 'hello.islorenzo.areu', 'john') in the set. + + By saving our set with 'true' as an argument, and "hello" as the path (which means the highest level of the + 'hello' entry with all its sub-entries), we'd get the value of all entries in the 'hello' entry ('hello', 'hello.myname', + 'hello.islorenzo', 'hello.islorenzo.who', 'hello.islorenzo.areu') in the set. + */ + for (String key : srcYaml.getConfigurationSection("").getKeys(true)) + { + debugger.sendDebugMessage(Level.INFO, "Analyzing key '" + key + "' with default value '" + srcYaml.get(key) + "'."); + + // Check if file is missing every entry. + if(!givenFile.yaml.contains(key)) + { + debugger.sendDebugMessage(Level.WARNING, "Config file is missing '" + key + "' key! Proceeding to add it..."); + // Add the entry to the file. + givenFile.yaml.set(key, srcYaml.get(key)); + debugger.sendDebugMessage(Level.WARNING, "Added key '" + key + "' with value '" + srcYaml.get(key) + "'."); + // Save the file! + saveExistingYaml(givenFile); + } + } + debugger.sendDebugMessage(Level.INFO, " Done iterating src config entries for file " + givenFile.file.getName() + "!"); + } + // Save all the info about our files location. + /* + Also initialize all files and their config, so we know where are the files when we need to save or reload them. + this is better than loading the files in the single classes that use them as if we had to reload them, we'd + need to set them again in each of the classes. Doing this instead allows us to have them all in one place. + */ + public enum FileType + { + //PLUGIN_FOLDER(plugin.getDataFolder(), null), + CONFIG_YAML(new File(plugin.getDataFolder()+File.separator + "config.yml"), new YamlConfiguration()), + LANG_YAML(new File(plugin.getDataFolder()+File.separator + "lang.yml"), new YamlConfiguration()), + SPAWN_YAML(new File(plugin.getDataFolder()+File.separator + "spawn.yml"), new YamlConfiguration()), + HUB_YAML(new File(plugin.getDataFolder()+File.separator + "hub.yml"), new YamlConfiguration()); + + // Constructor, so we can assign the value we set here ^ to our File. + public File file; + public YamlConfiguration yaml; + FileType(File givenFile, YamlConfiguration yamlConfig) + { + file = givenFile; + yaml = yamlConfig; + } + } +} diff --git a/src/net/mindoverflow/hubthat/utils/files/OldConfigConversion.java b/src/net/mindoverflow/hubthat/utils/files/OldConfigConversion.java new file mode 100644 index 0000000..342ab29 --- /dev/null +++ b/src/net/mindoverflow/hubthat/utils/files/OldConfigConversion.java @@ -0,0 +1,90 @@ +package net.mindoverflow.hubthat.utils.files; + +import net.mindoverflow.hubthat.HubThat; +import net.mindoverflow.hubthat.utils.ConfigEntries; +import net.mindoverflow.hubthat.utils.LocalizedMessages; +import org.bukkit.configuration.file.YamlConfiguration; + +import java.io.File; +import java.util.HashMap; +import java.util.logging.Logger; + +public class OldConfigConversion +{ + + public static void checkOldConfig(HubThat plugin, Logger logger) + { + + + logger.warning(plugin.getName() + ": Checking if config exists..."); + File oldConfigFile = FileUtils.FileType.CONFIG_YAML.file; + if(!oldConfigFile.exists()) return; + + + logger.warning("Loading config..."); + YamlConfiguration oldConfig = YamlConfiguration.loadConfiguration(oldConfigFile); + if(!oldConfig.getKeys(true).contains("global.VERSION")) return; + + + logger.warning("WARNING! Old Configuration Detected!"); + logger.warning("Starting conversion now!"); + String configDir = plugin.getDataFolder().getAbsolutePath(); + + logger.warning("Renaming old config..."); + oldConfigFile.renameTo(new File( configDir + File.separator + "config.old")); + + logger.warning("generating new files..."); + FileUtils.checkFiles(); + + // newPath, oldPath + HashMapnewAndOldConfigEntries = new HashMap<>(); + logger.warning("Loading config entries..."); + newAndOldConfigEntries.put(ConfigEntries.HUB_DELAY, "hub.delay"); + newAndOldConfigEntries.put(ConfigEntries.SPAWN_DELAY, "spawn.delay"); + newAndOldConfigEntries.put(ConfigEntries.WORLD_RELATED_CHAT, "global.world-related-chat"); + newAndOldConfigEntries.put(ConfigEntries.UPDATE_CHECKER_ENABLED, "updates.update-notify"); + newAndOldConfigEntries.put(ConfigEntries.MOVEMENT_DETECTION_ENABLED, "global.move-detect"); + newAndOldConfigEntries.put(ConfigEntries.GAMEMODE_SET_ON_JOIN, "global.set-gamemode-on-join"); + newAndOldConfigEntries.put(ConfigEntries.GAMEMODE, "global.gamemode"); + newAndOldConfigEntries.put(ConfigEntries.TELEPORTATION_TP_HUB_ON_JOIN, "global.tp-hub-on-join"); + newAndOldConfigEntries.put(ConfigEntries.TELEPORTATION_TP_HUB_ON_RESPAWN, "global.tp-hub-on-respawn"); + newAndOldConfigEntries.put(ConfigEntries.TELEPORTATION_RESPAWN_HANDLER, "global.respawn-handler"); + + logger.warning("Converting config entries..."); + for(ConfigEntries entry : newAndOldConfigEntries.keySet()) + { + logger.warning("Entry: " + entry.path); + FileUtils.FileType.CONFIG_YAML.yaml.set(entry.path, oldConfig.get(newAndOldConfigEntries.get(entry))); + } + + logger.warning("Saving file..."); + FileUtils.saveExistingYaml(FileUtils.FileType.CONFIG_YAML); + logger.warning("Done with config.yml!"); + + logger.warning("Loading lang entries..."); + HashMapnewAndOldLangEntries = new HashMap<>(); + newAndOldLangEntries.put(LocalizedMessages.ERROR_ALREADY_TELEPORTING, "global.ALREADY-TELEPORTING"); + newAndOldLangEntries.put(LocalizedMessages.ERROR_HUB_NOT_SET, "hub.HUB_NOT_SET"); + newAndOldLangEntries.put(LocalizedMessages.ERROR_SPAWN_NOT_SET, "spawn.SPAWN_NOT_SET"); + newAndOldLangEntries.put(LocalizedMessages.ERROR_WORLD_NOT_EXISTING, "worldtp.UNKNOWN_WORLD"); + newAndOldLangEntries.put(LocalizedMessages.WARNING_TELEPORTATION_CANCELLED, "global.MOVED"); + newAndOldLangEntries.put(LocalizedMessages.INFO_HUB_TELEPORTED, "hub.TELEPORTED"); + newAndOldLangEntries.put(LocalizedMessages.INFO_SPAWN_TELEPORTED, "spawn.TELEPORTED"); + newAndOldLangEntries.put(LocalizedMessages.INFO_WORLDTP_TELEPORTED, "worldtp.TELEPORTED"); + + logger.warning("Converting lang entries..."); + for(LocalizedMessages message : newAndOldLangEntries.keySet()) + { + logger.warning("Entry: " + message.path); + FileUtils.FileType.LANG_YAML.yaml.set(message.path, oldConfig.get(newAndOldLangEntries.get(message))); + } + + + logger.warning("Saving file..."); + FileUtils.saveExistingYaml(FileUtils.FileType.LANG_YAML); + logger.warning("Done with lang.yml!"); + + + } + +} diff --git a/src/net/mindoverflow/hubthat/utils/statistics/Metrics.java b/src/net/mindoverflow/hubthat/utils/statistics/Metrics.java new file mode 100644 index 0000000..b289a90 --- /dev/null +++ b/src/net/mindoverflow/hubthat/utils/statistics/Metrics.java @@ -0,0 +1,731 @@ +package net.mindoverflow.hubthat.utils.statistics; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.logging.Level; +import java.util.zip.GZIPOutputStream; + +import javax.net.ssl.HttpsURLConnection; + +import org.bukkit.Bukkit; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.RegisteredServiceProvider; +import org.bukkit.plugin.ServicePriority; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +/** + * bStats collects some data for plugin authors. + *

+ * Check out https://bStats.org/ to learn more about bStats! + */ +public class Metrics { + + static { + if (System.getProperty("bstats.relocatecheck") == null || !System.getProperty("bstats.relocatecheck").equals("false")) { + final String defaultPackage = new String( + new byte[]{'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's', '.', 'b', 'u', 'k', 'k', 'i', 't'}); + final String examplePackage = new String(new byte[]{'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'}); + if (Metrics.class.getPackage().getName().equals(defaultPackage) || Metrics.class.getPackage().getName().equals(examplePackage)) { + throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); + } + } + } + + // The version of this bStats class + public static final int B_STATS_VERSION = 1; + + // The url to which the data is sent + private static final String URL = "https://bStats.org/submitData/bukkit"; + + // Is bStats enabled on this server? + private boolean enabled; + + // Should failed requests be logged? + private static boolean logFailedRequests = false; + + // Should the sent data be logged? + private static boolean logSentData = false; + + // Should the response text be logged? + private static boolean logResponseStatusText = false; + + // The uuid of the server + private static String serverUUID; + + // The plugin + private final Plugin plugin; + + // A list with all custom charts + private final List charts = new ArrayList<>(); + + /** + * Class constructor. + * + * @param plugin The plugin which stats should be submitted. + */ + public Metrics(Plugin plugin) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null!"); + } + this.plugin = plugin; + + // Get the config file + File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); + File configFile = new File(bStatsFolder, "config.yml"); + YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); + + // Check if the config file exists + if (!config.isSet("serverUuid")) { + + // Add default values + config.addDefault("enabled", true); + // Every server gets its unique random id. + config.addDefault("serverUuid", UUID.randomUUID().toString()); + // Should failed request be logged? + //config.addDefault("logFailedRequests", false); + // Should the sent data be logged? + //config.addDefault("logSentData", false); + // Should the response text be logged? + //config.addDefault("logResponseStatusText", false); + + // Inform the server owners about bStats + config.options().header( + "bStats collects some data for plugin authors like how many servers are using their plugins.\n" + + "To honor their work, you should not disable it.\n" + + "All data is anonymous and this has practically no effect on the server performance!\n" + + "Check out https://bStats.org/ to learn more :)" + ).copyDefaults(true); + try { + config.save(configFile); + } catch (IOException ignored) { } + } + + // Load the data + enabled = config.getBoolean("enabled", true); + serverUUID = config.getString("serverUuid"); + //logFailedRequests = config.getBoolean("logFailedRequests", false); + //logSentData = config.getBoolean("logSentData", false); + //logResponseStatusText = config.getBoolean("logResponseStatusText", false); + logFailedRequests = false; + logSentData = false; + logResponseStatusText = false; + + if (enabled) { + boolean found = false; + // Search for all other bStats Metrics classes to see if we are the first one + for (Class service : Bukkit.getServicesManager().getKnownServices()) { + try { + service.getField("B_STATS_VERSION"); // Our identifier :) + found = true; // We aren't the first + break; + } catch (NoSuchFieldException ignored) { } + } + // Register our service + Bukkit.getServicesManager().register(Metrics.class, this, plugin, ServicePriority.Normal); + if (!found) { + // We are the first! + startSubmitting(); + } + } + } + + /** + * Checks if bStats is enabled. + * + * @return Whether bStats is enabled or not. + */ + public boolean isEnabled() { + return enabled; + } + + /** + * Adds a custom chart. + * + * @param chart The chart to add. + */ + public void addCustomChart(CustomChart chart) { + if (chart == null) { + throw new IllegalArgumentException("Chart cannot be null!"); + } + charts.add(chart); + } + + /** + * Starts the Scheduler which submits our data every 30 minutes. + */ + private void startSubmitting() { + final Timer timer = new Timer(true); // We use a timer cause the Bukkit scheduler is affected by server lags + timer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + if (!plugin.isEnabled()) { // Plugin was disabled + timer.cancel(); + return; + } + // Nevertheless we want our code to run in the Bukkit main thread, so we have to use the Bukkit scheduler + // Don't be afraid! The connection to the bStats server is still async, only the stats collection is sync ;) + Bukkit.getScheduler().runTask(plugin, () -> submitData()); + } + }, 1000 * 60 * 5, 1000 * 60 * 30); + // Submit the data every 30 minutes, first time after 5 minutes to give other plugins enough time to start + // WARNING: Changing the frequency has no effect but your plugin WILL be blocked/deleted! + // WARNING: Just don't do it! + } + + /** + * Gets the plugin specific data. + * This method is called using Reflection. + * + * @return The plugin specific data. + */ + public JsonObject getPluginData() { + JsonObject data = new JsonObject(); + + String pluginName = plugin.getDescription().getName(); + String pluginVersion = plugin.getDescription().getVersion(); + + data.addProperty("pluginName", pluginName); // Append the name of the plugin + data.addProperty("pluginVersion", pluginVersion); // Append the version of the plugin + JsonArray customCharts = new JsonArray(); + for (CustomChart customChart : charts) { + // Add the data of the custom charts + JsonObject chart = customChart.getRequestJsonObject(); + if (chart == null) { // If the chart is null, we skip it + continue; + } + customCharts.add(chart); + } + data.add("customCharts", customCharts); + + return data; + } + + /** + * Gets the server specific data. + * + * @return The server specific data. + */ + private JsonObject getServerData() { + // Minecraft specific data + int playerAmount; + try { + // Around MC 1.8 the return type was changed to a collection from an array, + // This fixes java.lang.NoSuchMethodError: org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection; + Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers"); + playerAmount = onlinePlayersMethod.getReturnType().equals(Collection.class) + ? ((Collection) onlinePlayersMethod.invoke(Bukkit.getServer())).size() + : ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length; + } catch (Exception e) { + playerAmount = Bukkit.getOnlinePlayers().size(); // Just use the new method if the Reflection failed + } + int onlineMode = Bukkit.getOnlineMode() ? 1 : 0; + String bukkitVersion = Bukkit.getVersion(); + String bukkitName = Bukkit.getName(); + + // OS/Java specific data + String javaVersion = System.getProperty("java.version"); + String osName = System.getProperty("os.name"); + String osArch = System.getProperty("os.arch"); + String osVersion = System.getProperty("os.version"); + int coreCount = Runtime.getRuntime().availableProcessors(); + + JsonObject data = new JsonObject(); + + data.addProperty("serverUUID", serverUUID); + + data.addProperty("playerAmount", playerAmount); + data.addProperty("onlineMode", onlineMode); + data.addProperty("bukkitVersion", bukkitVersion); + data.addProperty("bukkitName", bukkitName); + + data.addProperty("javaVersion", javaVersion); + data.addProperty("osName", osName); + data.addProperty("osArch", osArch); + data.addProperty("osVersion", osVersion); + data.addProperty("coreCount", coreCount); + + return data; + } + + /** + * Collects the data and sends it afterwards. + */ + private void submitData() { + final JsonObject data = getServerData(); + + JsonArray pluginData = new JsonArray(); + // Search for all other bStats Metrics classes to get their plugin data + for (Class service : Bukkit.getServicesManager().getKnownServices()) { + try { + service.getField("B_STATS_VERSION"); // Our identifier :) + + for (RegisteredServiceProvider provider : Bukkit.getServicesManager().getRegistrations(service)) { + try { + Object plugin = provider.getService().getMethod("getPluginData").invoke(provider.getProvider()); + if (plugin instanceof JsonObject) { + pluginData.add((JsonObject) plugin); + } else { // old bstats version compatibility + try { + Class jsonObjectJsonSimple = Class.forName("org.json.simple.JSONObject"); + if (plugin.getClass().isAssignableFrom(jsonObjectJsonSimple)) { + Method jsonStringGetter = jsonObjectJsonSimple.getDeclaredMethod("toJSONString"); + jsonStringGetter.setAccessible(true); + String jsonString = (String) jsonStringGetter.invoke(plugin); + JsonObject object = new JsonParser().parse(jsonString).getAsJsonObject(); + pluginData.add(object); + } + } catch (ClassNotFoundException e) { + // minecraft version 1.14+ + if (logFailedRequests) { + this.plugin.getLogger().log(Level.SEVERE, "Encountered unexpected exception", e); + } + continue; // continue looping since we cannot do any other thing. + } + } + } catch (NullPointerException | NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) { } + } + } catch (NoSuchFieldException ignored) { } + } + + data.add("plugins", pluginData); + + // Create a new thread for the connection to the bStats server + new Thread(new Runnable() { + @Override + public void run() { + try { + // Send the data + sendData(plugin, data); + } catch (Exception e) { + // Something went wrong! :( + if (logFailedRequests) { + plugin.getLogger().log(Level.WARNING, "Could not submit plugin stats of " + plugin.getName(), e); + } + } + } + }).start(); + } + + /** + * Sends the data to the bStats server. + * + * @param plugin Any plugin. It's just used to get a logger instance. + * @param data The data to send. + * @throws Exception If the request failed. + */ + private static void sendData(Plugin plugin, JsonObject data) throws Exception { + if (data == null) { + throw new IllegalArgumentException("Data cannot be null!"); + } + if (Bukkit.isPrimaryThread()) { + throw new IllegalAccessException("This method must not be called from the main thread!"); + } + if (logSentData) { + plugin.getLogger().info("Sending data to bStats: " + data.toString()); + } + HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection(); + + // Compress the data to save bandwidth + byte[] compressedData = compress(data.toString()); + + // Add headers + connection.setRequestMethod("POST"); + connection.addRequestProperty("Accept", "application/json"); + connection.addRequestProperty("Connection", "close"); + connection.addRequestProperty("Content-Encoding", "gzip"); // We gzip our request + connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); + connection.setRequestProperty("Content-Type", "application/json"); // We send our data in JSON format + connection.setRequestProperty("User-Agent", "MC-Server/" + B_STATS_VERSION); + + // Send data + connection.setDoOutput(true); + DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream()); + outputStream.write(compressedData); + outputStream.flush(); + outputStream.close(); + + InputStream inputStream = connection.getInputStream(); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + + StringBuilder builder = new StringBuilder(); + String line; + while ((line = bufferedReader.readLine()) != null) { + builder.append(line); + } + bufferedReader.close(); + if (logResponseStatusText) { + plugin.getLogger().info("Sent data to bStats and received response: " + builder.toString()); + } + } + + /** + * Gzips the given String. + * + * @param str The string to gzip. + * @return The gzipped String. + * @throws IOException If the compression failed. + */ + private static byte[] compress(final String str) throws IOException { + if (str == null) { + return null; + } + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + GZIPOutputStream gzip = new GZIPOutputStream(outputStream); + gzip.write(str.getBytes(StandardCharsets.UTF_8)); + gzip.close(); + return outputStream.toByteArray(); + } + + /** + * Represents a custom chart. + */ + public static abstract class CustomChart { + + // The id of the chart + final String chartId; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + */ + CustomChart(String chartId) { + if (chartId == null || chartId.isEmpty()) { + throw new IllegalArgumentException("ChartId cannot be null or empty!"); + } + this.chartId = chartId; + } + + private JsonObject getRequestJsonObject() { + JsonObject chart = new JsonObject(); + chart.addProperty("chartId", chartId); + try { + JsonObject data = getChartData(); + if (data == null) { + // If the data is null we don't send the chart. + return null; + } + chart.add("data", data); + } catch (Throwable t) { + if (logFailedRequests) { + Bukkit.getLogger().log(Level.WARNING, "Failed to get data for custom chart with id " + chartId, t); + } + return null; + } + return chart; + } + + protected abstract JsonObject getChartData() throws Exception; + + } + + /** + * Represents a custom simple pie. + */ + public static class SimplePie extends CustomChart { + + private final Callable callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SimplePie(String chartId, Callable callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObject getChartData() throws Exception { + JsonObject data = new JsonObject(); + String value = callable.call(); + if (value == null || value.isEmpty()) { + // Null = skip the chart + return null; + } + data.addProperty("value", value); + return data; + } + } + + /** + * Represents a custom advanced pie. + */ + public static class AdvancedPie extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public AdvancedPie(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObject getChartData() throws Exception { + JsonObject data = new JsonObject(); + JsonObject values = new JsonObject(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + continue; // Skip this invalid + } + allSkipped = false; + values.addProperty(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + data.add("values", values); + return data; + } + } + + /** + * Represents a custom drilldown pie. + */ + public static class DrilldownPie extends CustomChart { + + private final Callable>> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public DrilldownPie(String chartId, Callable>> callable) { + super(chartId); + this.callable = callable; + } + + @Override + public JsonObject getChartData() throws Exception { + JsonObject data = new JsonObject(); + JsonObject values = new JsonObject(); + Map> map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean reallyAllSkipped = true; + for (Map.Entry> entryValues : map.entrySet()) { + JsonObject value = new JsonObject(); + boolean allSkipped = true; + for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) { + value.addProperty(valueEntry.getKey(), valueEntry.getValue()); + allSkipped = false; + } + if (!allSkipped) { + reallyAllSkipped = false; + values.add(entryValues.getKey(), value); + } + } + if (reallyAllSkipped) { + // Null = skip the chart + return null; + } + data.add("values", values); + return data; + } + } + + /** + * Represents a custom single line chart. + */ + public static class SingleLineChart extends CustomChart { + + private final Callable callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SingleLineChart(String chartId, Callable callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObject getChartData() throws Exception { + JsonObject data = new JsonObject(); + int value = callable.call(); + if (value == 0) { + // Null = skip the chart + return null; + } + data.addProperty("value", value); + return data; + } + + } + + /** + * Represents a custom multi line chart. + */ + public static class MultiLineChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public MultiLineChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObject getChartData() throws Exception { + JsonObject data = new JsonObject(); + JsonObject values = new JsonObject(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + continue; // Skip this invalid + } + allSkipped = false; + values.addProperty(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + data.add("values", values); + return data; + } + + } + + /** + * Represents a custom simple bar chart. + */ + public static class SimpleBarChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SimpleBarChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObject getChartData() throws Exception { + JsonObject data = new JsonObject(); + JsonObject values = new JsonObject(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + for (Map.Entry entry : map.entrySet()) { + JsonArray categoryValues = new JsonArray(); + categoryValues.add(entry.getValue()); + values.add(entry.getKey(), categoryValues); + } + data.add("values", values); + return data; + } + + } + + /** + * Represents a custom advanced bar chart. + */ + public static class AdvancedBarChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public AdvancedBarChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObject getChartData() throws Exception { + JsonObject data = new JsonObject(); + JsonObject values = new JsonObject(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue().length == 0) { + continue; // Skip this invalid + } + allSkipped = false; + JsonArray categoryValues = new JsonArray(); + for (int categoryValue : entry.getValue()) { + categoryValues.add(categoryValue); + } + values.add(entry.getKey(), categoryValues); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + data.add("values", values); + return data; + } + } + +} diff --git a/src/net/mindoverflow/hubthat/utils/statistics/UpdateChecker.java b/src/net/mindoverflow/hubthat/utils/statistics/UpdateChecker.java new file mode 100644 index 0000000..5fc6742 --- /dev/null +++ b/src/net/mindoverflow/hubthat/utils/statistics/UpdateChecker.java @@ -0,0 +1,267 @@ +package net.mindoverflow.hubthat.utils.statistics; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import net.mindoverflow.hubthat.HubThat; +import net.mindoverflow.hubthat.utils.*; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitTask; + +import javax.net.ssl.HttpsURLConnection; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.logging.Level; + +public class UpdateChecker implements Runnable +{ + + public static BukkitTask task; + + Debugger debugger = new Debugger(getClass().getName()); + + + + public String newVersion, updateLink; + public ArrayList updateText = new ArrayList<>(), warningMessage = new ArrayList<>(); + public Boolean updateWarningBoolean, isNewVersionOut = false; + + public Boolean isServerUnreachable = true; + public String errorCode; + + private HubThat plugin; + public UpdateChecker(HubThat givenPlugin) + { + plugin = givenPlugin; + } + + private String servicesUrl = "https://services.mind-overflow.net/"; + private String hubthatUrl = "java/plugins/hubthat/updates/"; + + @Override + public void run() + { + CommandSender console = plugin.getServer().getConsoleSender(); + updateText.clear(); + warningMessage.clear(); + + + try + { + // Open an HTTPS Connection to the MOWS (Mind Overflow Web Services) + HttpsURLConnection connection = (HttpsURLConnection) new URL(servicesUrl).openConnection(); + connection.setRequestMethod("HEAD"); + + // Read the response code. + int responseCode = connection.getResponseCode(); + + // If it's not "OK"... Stop + if (responseCode != 200) + { + setAndSendErrorCode(responseCode + "", console); + return; + } + + errorCode = null; + + + } // Exception means the server was unreachable. Stop + catch (IOException e) + { + sendUnreachableCode(console); + isServerUnreachable = true; + return; + } + isServerUnreachable = false; + + // Now, try loading the JSON file... + InputStream jsonIS; + try + { + + jsonIS = new URL( servicesUrl + hubthatUrl + "update.json").openStream(); + + } catch (IOException e) + { + + setAndSendErrorCode("null json", console); + return; + } + + // And parsing it... + JsonParser jsonParser = new JsonParser(); + JsonObject json = (JsonObject)jsonParser.parse(new InputStreamReader(jsonIS, StandardCharsets.UTF_8)); + + // Close the input stream... + /*try { + jsonIS.close(); + } catch (IOException ignored) + { + }*/ + + // Check if the "version" String is present... + newVersion = null; + if(json.get("version") == null) + { + setAndSendErrorCode("null version", console); + return; + } + + // Store it. + newVersion = json.get("version").getAsString(); + + // Check if versions match... And stop + if(newVersion.equalsIgnoreCase(plugin.getDescription().getVersion())) + { + isNewVersionOut = false; + return; + } + + // If we are here, it means a new version is out. + isNewVersionOut = true; + + // Load the update link... + updateLink = null; + if(json.get("link") == null) + { + setAndSendErrorCode("null link", console); + return; + } + updateLink = json.get("link").getAsString(); + + + // Load all the lines from the String Array... + if(json.get("text") == null) + { + setAndSendErrorCode("null text", console); + return; + } + JsonArray updateTextArray = json.get("text").getAsJsonArray(); + for(JsonElement obj : updateTextArray) + { + String relatedString = obj.getAsString(); + updateText.add(relatedString); + } + + // Load the warning object... + if(json.get("warning") == null) + { + setAndSendErrorCode("null warning", console); + return; + } + JsonObject warning = json.get("warning").getAsJsonObject(); + + // See if the warning is enabled... + if(warning.get("enabled") == null) + { + setAndSendErrorCode("null warning boolean", console); + return; + } + updateWarningBoolean = warning.get("enabled").getAsBoolean(); + + // Load all the lines from the String Array... + if(warning.get("text") == null) + { + setAndSendErrorCode("null warning text", console); + return; + } + JsonArray warningTextArray = warning.get("text").getAsJsonArray(); + for(JsonElement obj : warningTextArray) + { + String relatedString = obj.getAsString(); + warningMessage.add(relatedString); + } + + // And finally send the message! + sendUpdateMessages(console); + + } + + + public void sendUnreachableCode(CommandSender sender) + { + String pluginName = plugin.getName(); + MessageUtils.sendColorizedMessage(sender, "&7-----[&3 " + pluginName + " Updater &7]-----"); + MessageUtils.sendColorizedMessage(sender, "&cWarning! Updates Server is unreachable."); + MessageUtils.sendColorizedMessage(sender, "&cTry fixing connectivity problems and reload " + pluginName + " with &3/" + pluginName.toLowerCase() + " reload&c!"); + } + + public void setAndSendErrorCode(String code, CommandSender sender) + { + isNewVersionOut = false; + errorCode = code; + sendErrorCode(sender); + } + + public void sendErrorCode(CommandSender sender) + { + String pluginName = plugin.getName(); + MessageUtils.sendColorizedMessage(sender, "&7-----[&3 " + pluginName + " Updater &7]-----"); + MessageUtils.sendColorizedMessage(sender, "&cWarning! Updates Server returned error code: &4" + errorCode); + MessageUtils.sendColorizedMessage(sender, "&cPlease contact the developer (" + debugger.authorName + ") immediately."); + } + + + public void sendUpdateMessages(CommandSender sender) + { + String pluginName = plugin.getName(); + MessageUtils.sendColorizedMessage(sender, "&7-----[&3 " + pluginName + " Updater &7]-----"); + MessageUtils.sendColorizedMessage(sender, "&7A new version is out: &6" + newVersion); + MessageUtils.sendColorizedMessage(sender, "&7Download: &6" + updateLink); + for(String line : updateText) + { + MessageUtils.sendColorizedMessage(sender, line); + } + if(updateWarningBoolean) + { + for(String line : warningMessage) + { + MessageUtils.sendColorizedMessage(sender, line); + } + } + } + + public void playerMessage(CommandSender player) + { + plugin.getServer().getScheduler().runTaskLater(plugin, () -> + { + // Stop this in case the sender is the console: it already receives intended messages. + if(!(player instanceof Player)) return; + + // Check if the updater is enabled. + if(CommonValues.updateChecker) + { + debugger.sendDebugMessage(Level.INFO, "Update Checker is enabled!"); + // Check if the player has permissions to get notifications about updates. + if(PermissionUtils.playerHasPermission(player, Permissions.GET_UPDATES_NOTIFICATIONS)) + { + debugger.sendDebugMessage(Level.INFO, "Player has permissions to check updates."); + // Instantiate the update checker so we can access it. + + if(isNewVersionOut) + { + sendUpdateMessages(player); + } + + if(isServerUnreachable) + {sendUnreachableCode(player); + } + + if(errorCode != null) + { + + sendErrorCode(player); + } + } + } + }, 40); + + + } +} diff --git a/src/plugin.yml b/src/plugin.yml new file mode 100644 index 0000000..d5f6a4a --- /dev/null +++ b/src/plugin.yml @@ -0,0 +1,52 @@ +name: HubThat +version: BUILD_NUMBER +author: mind_overflow +main: net.mindoverflow.hubthat.HubThat +api-version: 1.13 +commands: + hubthat: + description: HubThat base command. + usage: /hubthat [args] + hub: + description: Teleport to the hub. + usage: /hub + sethub: + description: Set the hub. + usage: /sethub + spawn: + description: Teleport to the spawn. + usage: /spawn [world] + setspawn: + description: Set the spawn. + usage: /setspawn [world] + worldlist: + description: List all worlds and their info. + usage: /worldlist + worldtp: + description: Teleport to a specific world. + usage: /worldtp +permissions: + hubthat.help: + default: true + hubthat.reloadconfig: + default: op + hubthat.nohubdelay: + default: op + hubthat.sethub: + default: op + hubthat.hub: + default: true + hubthat.nospawndelay: + default: op + hubthat.setspawn: + default: op + hubthat.spawn: + default: true + hubthat.spawn.anotherworld: + default: op + hubthat.gotoworld: + default: op + hubthat.listworlds: + default: op + hubthat.updates: + default: op \ No newline at end of file diff --git a/src/spawn.yml b/src/spawn.yml new file mode 100644 index 0000000..e69de29