diff --git a/src/main/java/com/wimbli/WorldBorder/cmd/CmdBypass.java b/src/main/java/com/wimbli/WorldBorder/cmd/CmdBypass.java new file mode 100644 index 0000000..6174ee3 --- /dev/null +++ b/src/main/java/com/wimbli/WorldBorder/cmd/CmdBypass.java @@ -0,0 +1,85 @@ +package com.wimbli.WorldBorder.cmd; + +import com.wimbli.WorldBorder.Config; +import com.wimbli.WorldBorder.UUID.UUIDFetcher; +import com.wimbli.WorldBorder.WorldBorder; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; +import java.util.UUID; + + +public class CmdBypass extends WBCmd { + public CmdBypass() { + name = permission = "bypass"; + minParams = 0; + maxParams = 2; + + addCmdExample(nameEmphasized() + "{player} [on|off] - let player go beyond border."); + helpText = "If [player] isn't specified, command sender is used. If [on|off] isn't specified, the value will " + + "be toggled. Once bypass is enabled, the player will not be stopped by any borders until bypass is " + + "disabled for them again. Use the " + commandEmphasized("bypasslist") + C_DESC + "command to list all " + + "players with bypass enabled."; + } + + @Override + public void cmdStatus(CommandSender sender) { + if (!(sender instanceof Player)) + return; + + boolean bypass = Config.isPlayerBypassing(((Player) sender).getUniqueId()); + sender.sendMessage(C_HEAD + "Border bypass is currently " + enabledColored(bypass) + C_HEAD + " for you."); + } + + @Override + public void execute(final CommandSender sender, final Player player, final List params, String worldName) { + if (player == null && params.isEmpty()) { + sendErrorAndHelp(sender, "When running this command from console, you must specify a player."); + return; + } + + Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(WorldBorder.plugin, new Runnable() { + @Override + public void run() { + final String sPlayer = (params.isEmpty()) ? player.getName() : params.get(0); + UUID uPlayer = (params.isEmpty()) ? player.getUniqueId() : null; + + if (uPlayer == null) { + Player p = Bukkit.getPlayer(sPlayer); + if (p != null) { + uPlayer = p.getUniqueId(); + } else { + // only do UUID lookup using Mojang server if specified player isn't online + try { + uPlayer = UUIDFetcher.getUUID(sPlayer); + } catch (Exception ex) { + sendErrorAndHelp(sender, "Failed to look up UUID for the player name you specified. " + ex.getLocalizedMessage()); + return; + } + } + } + if (uPlayer == null) { + sendErrorAndHelp(sender, "Failed to look up UUID for the player name you specified; null value returned."); + return; + } + + boolean bypassing = !Config.isPlayerBypassing(uPlayer); + if (params.size() > 1) + bypassing = strAsBool(params.get(1)); + + Config.setPlayerBypass(uPlayer, bypassing); + + Player target = Bukkit.getPlayer(sPlayer); + if (target != null && target.isOnline()) + target.sendMessage("Border bypass is now " + enabledColored(bypassing) + "."); + + Config.log("Border bypass for player \"" + sPlayer + "\" is " + (bypassing ? "enabled" : "disabled") + + (player != null ? " at the command of player \"" + player.getName() + "\"" : "") + "."); + if (player != null && player != target) + sender.sendMessage("Border bypass for player \"" + sPlayer + "\" is " + enabledColored(bypassing) + "."); + } + }); + } +} diff --git a/src/main/java/com/wimbli/WorldBorder/cmd/CmdBypasslist.java b/src/main/java/com/wimbli/WorldBorder/cmd/CmdBypasslist.java new file mode 100644 index 0000000..0ba844c --- /dev/null +++ b/src/main/java/com/wimbli/WorldBorder/cmd/CmdBypasslist.java @@ -0,0 +1,49 @@ +package com.wimbli.WorldBorder.cmd; + +import com.wimbli.WorldBorder.Config; +import com.wimbli.WorldBorder.UUID.UUIDFetcher; +import com.wimbli.WorldBorder.WorldBorder; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; + + +public class CmdBypasslist extends WBCmd { + public CmdBypasslist() { + name = permission = "bypasslist"; + minParams = maxParams = 0; + + addCmdExample(nameEmphasized() + "- list players with border bypass enabled."); + helpText = "The bypass list will persist between server restarts, and applies to all worlds. Use the " + + commandEmphasized("bypass") + C_DESC + "command to add or remove players."; + } + + @Override + public void execute(final CommandSender sender, Player player, List params, String worldName) { + final ArrayList uuids = Config.getPlayerBypassList(); + if (uuids == null || uuids.isEmpty()) { + sender.sendMessage("Players with border bypass enabled: "); + return; + } + + Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(WorldBorder.plugin, new Runnable() { + @Override + public void run() { + try { + Map names = UUIDFetcher.getNameList(uuids); + String nameString = names.values().toString(); + + sender.sendMessage("Players with border bypass enabled: " + nameString.substring(1, nameString.length() - 1)); + } catch (Exception ex) { + sendErrorAndHelp(sender, "Failed to look up names for the UUIDs in the border bypass list. " + ex.getLocalizedMessage()); + return; + } + } + }); + } +} diff --git a/src/main/java/com/wimbli/WorldBorder/cmd/CmdClear.java b/src/main/java/com/wimbli/WorldBorder/cmd/CmdClear.java new file mode 100644 index 0000000..996461a --- /dev/null +++ b/src/main/java/com/wimbli/WorldBorder/cmd/CmdClear.java @@ -0,0 +1,60 @@ +package com.wimbli.WorldBorder.cmd; + +import com.wimbli.WorldBorder.BorderData; +import com.wimbli.WorldBorder.Config; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; + + +public class CmdClear extends WBCmd { + public CmdClear() { + name = permission = "clear"; + hasWorldNameInput = true; + consoleRequiresWorldName = false; + minParams = 0; + maxParams = 1; + + addCmdExample(nameEmphasizedW() + "- remove border for this world."); + addCmdExample(nameEmphasized() + "^all - remove border for all worlds."); + helpText = "If run by an in-game player and [world] or \"all\" isn't specified, the world you are currently " + + "in is used."; + } + + @Override + public void execute(CommandSender sender, Player player, List params, String worldName) { + // handle "clear all" command separately + if (params.size() == 1 && params.get(0).equalsIgnoreCase("all")) { + if (worldName != null) { + sendErrorAndHelp(sender, "You should not specify a world with \"clear all\"."); + return; + } + + Config.removeAllBorders(); + + if (player != null) + sender.sendMessage("All borders have been cleared for all worlds."); + return; + } + + if (worldName == null) { + if (player == null) { + sendErrorAndHelp(sender, "You must specify a world name from console if not using \"clear all\"."); + return; + } + worldName = player.getWorld().getName(); + } + + BorderData border = Config.Border(worldName); + if (border == null) { + sendErrorAndHelp(sender, "This world (\"" + worldName + "\") does not have a border set."); + return; + } + + Config.removeBorder(worldName); + + if (player != null) + sender.sendMessage("Border cleared for world \"" + worldName + "\"."); + } +} diff --git a/src/main/java/com/wimbli/WorldBorder/cmd/CmdCommands.java b/src/main/java/com/wimbli/WorldBorder/cmd/CmdCommands.java new file mode 100644 index 0000000..7bb3262 --- /dev/null +++ b/src/main/java/com/wimbli/WorldBorder/cmd/CmdCommands.java @@ -0,0 +1,63 @@ +package com.wimbli.WorldBorder.cmd; + +import com.wimbli.WorldBorder.WorldBorder; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; + + +public class CmdCommands extends WBCmd { + private static final int pageSize = 8; // examples to list per page; 10 lines available, 1 for header, 1 for footer + + public CmdCommands() { + name = "commands"; + permission = "help"; + hasWorldNameInput = false; + } + + @Override + public void execute(CommandSender sender, Player player, List params, String worldName) { + // determine which page we're viewing + int page = (player == null) ? 0 : 1; + if (!params.isEmpty()) { + try { + page = Integer.parseInt(params.get(0)); + } catch (NumberFormatException ignored) { + } + } + + // see whether we're showing examples to player or to console, and determine number of pages available + List examples = (player == null) ? cmdExamplesConsole : cmdExamplesPlayer; + int pageCount = (int) Math.ceil(examples.size() / (double) pageSize); + + // if specified page number is negative or higher than we have available, default back to first page + if (page < 0 || page > pageCount) + page = (player == null) ? 0 : 1; + + // send command example header + sender.sendMessage(C_HEAD + WorldBorder.plugin.getDescription().getFullName() + " - key: " + + commandEmphasized("command") + C_REQ + " " + C_OPT + "[optional]"); + + if (page > 0) { + // send examples for this page + int first = ((page - 1) * pageSize); + int count = Math.min(pageSize, examples.size() - first); + for (int i = first; i < first + count; i++) { + sender.sendMessage(examples.get(i)); + } + + // send page footer, if relevant; manual spacing to get right side lined up near edge is crude, but sufficient + String footer = C_HEAD + " (Page " + page + "/" + pageCount + ") " + cmd(sender); + if (page < pageCount) + sender.sendMessage(footer + (page + 1) + C_DESC + " - view next page of commands."); + else if (page > 1) + sender.sendMessage(footer + C_DESC + "- view first page of commands."); + } else { + // if page "0" is specified, send all examples; done by default for console but can be specified by player + for (String example : examples) { + sender.sendMessage(example); + } + } + } +} diff --git a/src/main/java/com/wimbli/WorldBorder/cmd/CmdDebug.java b/src/main/java/com/wimbli/WorldBorder/cmd/CmdDebug.java new file mode 100644 index 0000000..26794e7 --- /dev/null +++ b/src/main/java/com/wimbli/WorldBorder/cmd/CmdDebug.java @@ -0,0 +1,34 @@ +package com.wimbli.WorldBorder.cmd; + +import com.wimbli.WorldBorder.Config; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; + + +public class CmdDebug extends WBCmd { + public CmdDebug() { + name = permission = "debug"; + minParams = maxParams = 1; + + addCmdExample(nameEmphasized() + " - turn console debug output on or off."); + helpText = "Default value: off. Debug mode will show some extra debugging data in the server console/log when " + + "players are knocked back from the border or are teleported."; + } + + @Override + public void cmdStatus(CommandSender sender) { + sender.sendMessage(C_HEAD + "Debug mode is " + enabledColored(Config.Debug()) + C_HEAD + "."); + } + + @Override + public void execute(CommandSender sender, Player player, List params, String worldName) { + Config.setDebug(strAsBool(params.get(0))); + + if (player != null) { + Config.log((Config.Debug() ? "Enabled" : "Disabled") + " debug output at the command of player \"" + player.getName() + "\"."); + cmdStatus(sender); + } + } +} diff --git a/src/main/java/com/wimbli/WorldBorder/cmd/CmdDelay.java b/src/main/java/com/wimbli/WorldBorder/cmd/CmdDelay.java new file mode 100644 index 0000000..461417f --- /dev/null +++ b/src/main/java/com/wimbli/WorldBorder/cmd/CmdDelay.java @@ -0,0 +1,43 @@ +package com.wimbli.WorldBorder.cmd; + +import com.wimbli.WorldBorder.Config; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; + + +public class CmdDelay extends WBCmd { + public CmdDelay() { + name = permission = "delay"; + minParams = maxParams = 1; + + addCmdExample(nameEmphasized() + " - time between border checks."); + helpText = "Default value: 5. The is in server ticks, of which there are roughly 20 every second, each " + + "tick taking ~50ms. The default value therefore has border checks run about 4 times per second."; + } + + @Override + public void cmdStatus(CommandSender sender) { + int delay = Config.TimerTicks(); + sender.sendMessage(C_HEAD + "Timer delay is set to " + delay + " tick(s). That is roughly " + (delay * 50) + "ms."); + } + + @Override + public void execute(CommandSender sender, Player player, List params, String worldName) { + int delay = 0; + try { + delay = Integer.parseInt(params.get(0)); + if (delay < 1) + throw new NumberFormatException(); + } catch (NumberFormatException ex) { + sendErrorAndHelp(sender, "The timer delay must be an integer of 1 or higher."); + return; + } + + Config.setTimerTicks(delay); + + if (player != null) + cmdStatus(sender); + } +} diff --git a/src/main/java/com/wimbli/WorldBorder/cmd/CmdDenypearl.java b/src/main/java/com/wimbli/WorldBorder/cmd/CmdDenypearl.java new file mode 100644 index 0000000..25f1c59 --- /dev/null +++ b/src/main/java/com/wimbli/WorldBorder/cmd/CmdDenypearl.java @@ -0,0 +1,36 @@ +package com.wimbli.WorldBorder.cmd; + +import com.wimbli.WorldBorder.Config; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; + + +public class CmdDenypearl extends WBCmd { + public CmdDenypearl() { + name = permission = "denypearl"; + minParams = maxParams = 1; + + addCmdExample(nameEmphasized() + " - stop ender pearls past the border."); + helpText = "Default value: on. When enabled, this setting will directly cancel attempts to use an ender pearl to " + + "get past the border rather than just knocking the player back. This should prevent usage of ender " + + "pearls to glitch into areas otherwise inaccessible at the border edge."; + } + + @Override + public void cmdStatus(CommandSender sender) { + sender.sendMessage(C_HEAD + "Direct cancellation of ender pearls thrown past the border is " + + enabledColored(Config.getDenyEnderpearl()) + C_HEAD + "."); + } + + @Override + public void execute(CommandSender sender, Player player, List params, String worldName) { + Config.setDenyEnderpearl(strAsBool(params.get(0))); + + if (player != null) { + Config.log((Config.getDenyEnderpearl() ? "Enabled" : "Disabled") + " direct cancellation of ender pearls thrown past the border at the command of player \"" + player.getName() + "\"."); + cmdStatus(sender); + } + } +} diff --git a/src/main/java/com/wimbli/WorldBorder/cmd/CmdDynmap.java b/src/main/java/com/wimbli/WorldBorder/cmd/CmdDynmap.java new file mode 100644 index 0000000..88574c5 --- /dev/null +++ b/src/main/java/com/wimbli/WorldBorder/cmd/CmdDynmap.java @@ -0,0 +1,34 @@ +package com.wimbli.WorldBorder.cmd; + +import com.wimbli.WorldBorder.Config; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; + + +public class CmdDynmap extends WBCmd { + public CmdDynmap() { + name = permission = "dynmap"; + minParams = maxParams = 1; + + addCmdExample(nameEmphasized() + " - turn DynMap border display on or off."); + helpText = "Default value: on. If you are running the DynMap plugin and this setting is enabled, all borders will " + + "be visually shown in DynMap."; + } + + @Override + public void cmdStatus(CommandSender sender) { + sender.sendMessage(C_HEAD + "DynMap border display is " + enabledColored(Config.DynmapBorderEnabled()) + C_HEAD + "."); + } + + @Override + public void execute(CommandSender sender, Player player, List params, String worldName) { + Config.setDynmapBorderEnabled(strAsBool(params.get(0))); + + if (player != null) { + cmdStatus(sender); + Config.log((Config.DynmapBorderEnabled() ? "Enabled" : "Disabled") + " DynMap border display at the command of player \"" + player.getName() + "\"."); + } + } +} diff --git a/src/main/java/com/wimbli/WorldBorder/cmd/CmdDynmaplabel.java b/src/main/java/com/wimbli/WorldBorder/cmd/CmdDynmaplabel.java new file mode 100644 index 0000000..2e5a602 --- /dev/null +++ b/src/main/java/com/wimbli/WorldBorder/cmd/CmdDynmaplabel.java @@ -0,0 +1,42 @@ +package com.wimbli.WorldBorder.cmd; + +import com.wimbli.WorldBorder.Config; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; + + +public class CmdDynmaplabel extends WBCmd { + public CmdDynmaplabel() { + name = permission = "dynmaplabel"; + minParams = 1; + + addCmdExample(nameEmphasized() + " - DynMap border layer labels will show this."); + helpText = "Default value: \"WorldBorder.\". If you are running the DynMap plugin and the " + + commandEmphasized("dynmap") + C_DESC + "command setting is enabled, the border layer shown in DynMap will " + + "be labelled with this text."; + } + + @Override + public void cmdStatus(CommandSender sender) { + sender.sendMessage(C_HEAD + "DynMap border layer label is set to: " + C_ERR + Config.DynmapLayerLabel()); + } + + @Override + public void execute(CommandSender sender, Player player, List params, String worldName) { + StringBuilder message = new StringBuilder(); + boolean first = true; + for (String param : params) { + if (!first) + message.append(" "); + message.append(param); + first = false; + } + + Config.setDynmapLayerLabel(message.toString()); + + if (player != null) + cmdStatus(sender); + } +} diff --git a/src/main/java/com/wimbli/WorldBorder/cmd/CmdDynmapmsg.java b/src/main/java/com/wimbli/WorldBorder/cmd/CmdDynmapmsg.java new file mode 100644 index 0000000..d284169 --- /dev/null +++ b/src/main/java/com/wimbli/WorldBorder/cmd/CmdDynmapmsg.java @@ -0,0 +1,42 @@ +package com.wimbli.WorldBorder.cmd; + +import com.wimbli.WorldBorder.Config; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; + + +public class CmdDynmapmsg extends WBCmd { + public CmdDynmapmsg() { + name = permission = "dynmapmsg"; + minParams = 1; + + addCmdExample(nameEmphasized() + " - DynMap border labels will show this."); + helpText = "Default value: \"The border of the world.\". If you are running the DynMap plugin and the " + + commandEmphasized("dynmap") + C_DESC + "command setting is enabled, the borders shown in DynMap will " + + "be labelled with this text."; + } + + @Override + public void cmdStatus(CommandSender sender) { + sender.sendMessage(C_HEAD + "DynMap border label is set to: " + C_ERR + Config.DynmapMessage()); + } + + @Override + public void execute(CommandSender sender, Player player, List params, String worldName) { + StringBuilder message = new StringBuilder(); + boolean first = true; + for (String param : params) { + if (!first) + message.append(" "); + message.append(param); + first = false; + } + + Config.setDynmapMessage(message.toString()); + + if (player != null) + cmdStatus(sender); + } +} diff --git a/src/main/java/com/wimbli/WorldBorder/cmd/CmdFill.java b/src/main/java/com/wimbli/WorldBorder/cmd/CmdFill.java new file mode 100644 index 0000000..8d0b0d8 --- /dev/null +++ b/src/main/java/com/wimbli/WorldBorder/cmd/CmdFill.java @@ -0,0 +1,161 @@ +package com.wimbli.WorldBorder.cmd; + +import com.wimbli.WorldBorder.Config; +import com.wimbli.WorldBorder.CoordXZ; +import com.wimbli.WorldBorder.WorldBorder; +import com.wimbli.WorldBorder.WorldFillTask; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; + + +public class CmdFill extends WBCmd { + /* with "view-distance=10" in server.properties on a fast VM test server and "Render Distance: Far" in client, + * hitting border during testing was loading 11+ chunks beyond the border in a couple of directions (10 chunks in + * the other two directions). This could be worse on a more loaded or worse server, so: + */ + private final int defaultPadding = CoordXZ.chunkToBlock(13); + private String fillWorld = ""; + private int fillFrequency = 20; + private int fillPadding = defaultPadding; + private boolean fillForceLoad = false; + + public CmdFill() { + name = permission = "fill"; + hasWorldNameInput = true; + consoleRequiresWorldName = false; + minParams = 0; + maxParams = 3; + + addCmdExample(nameEmphasizedW() + "[freq] [pad] [force] - fill world to border."); + helpText = "This command will generate missing world chunks inside your border. [freq] is the frequency " + + "of chunks per second that will be checked (default 20). [pad] is the number of blocks padding added " + + "beyond the border itself (default 208, to cover player visual range). [force] can be specified as true " + + "to force all chunks to be loaded even if they seem to be fully generated (default false)."; + } + + @Override + public void execute(CommandSender sender, Player player, List params, String worldName) { + boolean confirm = false; + // check for "cancel", "pause", or "confirm" + if (params.size() >= 1) { + String check = params.get(0).toLowerCase(); + + if (check.equals("cancel") || check.equals("stop")) { + if (!makeSureFillIsRunning(sender)) + return; + sender.sendMessage(C_HEAD + "Cancelling the world map generation task."); + fillDefaults(); + Config.StopFillTask(true); + return; + } else if (check.equals("pause")) { + if (!makeSureFillIsRunning(sender)) + return; + Config.fillTask.pause(); + sender.sendMessage(C_HEAD + "The world map generation task is now " + (Config.fillTask.isPaused() ? "" : "un") + "paused."); + return; + } + + confirm = check.equals("confirm"); + } + + // if not just confirming, make sure a world name is available + if (worldName == null && !confirm) { + if (player != null) + worldName = player.getWorld().getName(); + else { + sendErrorAndHelp(sender, "You must specify a world!"); + return; + } + } + + // colorized "/wb fill " + String cmd = cmd(sender) + nameEmphasized() + C_CMD; + + // make sure Fill isn't already running + if (Config.fillTask != null && Config.fillTask.valid()) { + sender.sendMessage(C_ERR + "The world map generation task is already running."); + sender.sendMessage(C_DESC + "You can cancel at any time with " + cmd + "cancel" + C_DESC + ", or pause/unpause with " + cmd + "pause" + C_DESC + "."); + return; + } + + // set frequency and/or padding if those were specified + try { + if (params.size() >= 1 && !confirm) + fillFrequency = Math.abs(Integer.parseInt(params.get(0))); + if (params.size() >= 2 && !confirm) + fillPadding = Math.abs(Integer.parseInt(params.get(1))); + } catch (NumberFormatException ex) { + sendErrorAndHelp(sender, "The frequency and padding values must be integers."); + fillDefaults(); + return; + } + if (fillFrequency <= 0) { + sendErrorAndHelp(sender, "The frequency value must be greater than zero."); + fillDefaults(); + return; + } + + // see if the command specifies to load even chunks which should already be fully generated + if (params.size() == 3) + fillForceLoad = strAsBool(params.get(2)); + + // set world if it was specified + if (worldName != null) + fillWorld = worldName; + + if (confirm) { // command confirmed, go ahead with it + if (fillWorld.isEmpty()) { + sendErrorAndHelp(sender, "You must first use this command successfully without confirming."); + return; + } + + if (player != null) + Config.log("Filling out world to border at the command of player \"" + player.getName() + "\"."); + + int ticks = 1, repeats = 1; + if (fillFrequency > 20) + repeats = fillFrequency / 20; + else + ticks = 20 / fillFrequency; + + /* */ + Config.log("world: " + fillWorld + " padding: " + fillPadding + " repeats: " + repeats + " ticks: " + ticks); + Config.fillTask = new WorldFillTask(Bukkit.getServer(), player, fillWorld, fillPadding, repeats, ticks, fillForceLoad); + if (Config.fillTask.valid()) { + int task = Bukkit.getServer().getScheduler().scheduleSyncRepeatingTask(WorldBorder.plugin, Config.fillTask, ticks, ticks); + Config.fillTask.setTaskID(task); + sender.sendMessage("WorldBorder map generation task for world \"" + fillWorld + "\" started."); + } else + sender.sendMessage(C_ERR + "The world map generation task failed to start."); + + fillDefaults(); + } else { + if (fillWorld.isEmpty()) { + sendErrorAndHelp(sender, "You must first specify a valid world."); + return; + } + + sender.sendMessage(C_HEAD + "World generation task is ready for world \"" + fillWorld + "\", attempting to process up to " + fillFrequency + " chunks per second (default 20). The map will be padded out " + fillPadding + " blocks beyond the border (default " + defaultPadding + "). Parts of the world which are already fully generated will be " + (fillForceLoad ? "loaded anyway." : "skipped.")); + sender.sendMessage(C_HEAD + "This process can take a very long time depending on the world's border size. Also, depending on the chunk processing rate, players will likely experience severe lag for the duration."); + sender.sendMessage(C_DESC + "You should now use " + cmd + "confirm" + C_DESC + " to start the process."); + sender.sendMessage(C_DESC + "You can cancel at any time with " + cmd + "cancel" + C_DESC + ", or pause/unpause with " + cmd + "pause" + C_DESC + "."); + } + } + + private void fillDefaults() { + fillWorld = ""; + fillFrequency = 20; + fillPadding = defaultPadding; + fillForceLoad = false; + } + + private boolean makeSureFillIsRunning(CommandSender sender) { + if (Config.fillTask != null && Config.fillTask.valid()) + return true; + sendErrorAndHelp(sender, "The world map generation task is not currently running."); + return false; + } +} diff --git a/src/main/java/com/wimbli/WorldBorder/cmd/CmdFillautosave.java b/src/main/java/com/wimbli/WorldBorder/cmd/CmdFillautosave.java new file mode 100644 index 0000000..dfa7448 --- /dev/null +++ b/src/main/java/com/wimbli/WorldBorder/cmd/CmdFillautosave.java @@ -0,0 +1,50 @@ +package com.wimbli.WorldBorder.cmd; + +import com.wimbli.WorldBorder.Config; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; + + +public class CmdFillautosave extends WBCmd { + public CmdFillautosave() { + name = permission = "fillautosave"; + minParams = maxParams = 1; + + addCmdExample(nameEmphasized() + " - world save interval for Fill."); + helpText = "Default value: 30 seconds."; + } + + @Override + public void cmdStatus(CommandSender sender) { + int seconds = Config.FillAutosaveFrequency(); + if (seconds == 0) { + sender.sendMessage(C_HEAD + "World autosave frequency during Fill process is set to 0, disabling it."); + sender.sendMessage(C_HEAD + "Note that much progress can be lost this way if there is a bug or crash in " + + "the world generation process from Bukkit or any world generation plugin you use."); + } else { + sender.sendMessage(C_HEAD + "World autosave frequency during Fill process is set to " + seconds + " seconds (rounded to a multiple of 5)."); + sender.sendMessage(C_HEAD + "New chunks generated by the Fill process will be forcibly saved to disk " + + "this often to prevent loss of progress due to bugs or crashes in the world generation process."); + } + } + + @Override + public void execute(CommandSender sender, Player player, List params, String worldName) { + int seconds = 0; + try { + seconds = Integer.parseInt(params.get(0)); + if (seconds < 0) + throw new NumberFormatException(); + } catch (NumberFormatException ex) { + sendErrorAndHelp(sender, "The world autosave frequency must be an integer of 0 or higher. Setting to 0 will disable autosaving of the world during the Fill process."); + return; + } + + Config.setFillAutosaveFrequency(seconds); + + if (player != null) + cmdStatus(sender); + } +} diff --git a/src/main/java/com/wimbli/WorldBorder/cmd/CmdGetmsg.java b/src/main/java/com/wimbli/WorldBorder/cmd/CmdGetmsg.java new file mode 100644 index 0000000..34ba416 --- /dev/null +++ b/src/main/java/com/wimbli/WorldBorder/cmd/CmdGetmsg.java @@ -0,0 +1,26 @@ +package com.wimbli.WorldBorder.cmd; + +import com.wimbli.WorldBorder.Config; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; + + +public class CmdGetmsg extends WBCmd { + public CmdGetmsg() { + name = permission = "getmsg"; + minParams = maxParams = 0; + + addCmdExample(nameEmphasized() + "- display border message."); + helpText = "This command simply displays the message shown to players knocked back from the border."; + } + + @Override + public void execute(CommandSender sender, Player player, List params, String worldName) { + sender.sendMessage("Border message is currently set to:"); + sender.sendMessage(Config.MessageRaw()); + sender.sendMessage("Formatted border message:"); + sender.sendMessage(Config.Message()); + } +} diff --git a/src/main/java/com/wimbli/WorldBorder/cmd/CmdHelp.java b/src/main/java/com/wimbli/WorldBorder/cmd/CmdHelp.java new file mode 100644 index 0000000..cee2130 --- /dev/null +++ b/src/main/java/com/wimbli/WorldBorder/cmd/CmdHelp.java @@ -0,0 +1,45 @@ +package com.wimbli.WorldBorder.cmd; + +import com.wimbli.WorldBorder.WorldBorder; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; +import java.util.Set; + + +public class CmdHelp extends WBCmd { + public CmdHelp() { + name = permission = "help"; + minParams = 0; + maxParams = 10; + + addCmdExample(nameEmphasized() + "[command] - get help on command usage."); +// helpText = "If [command] is specified, info for that particular command will be provided."; + } + + @Override + public void cmdStatus(CommandSender sender) { + String commands = WorldBorder.wbCommand.getCommandNames().toString().replace(", ", C_DESC + ", " + C_CMD); + sender.sendMessage(C_HEAD + "Commands: " + C_CMD + commands.substring(1, commands.length() - 1)); + sender.sendMessage("Example, for info on \"set\" command: " + cmd(sender) + nameEmphasized() + C_CMD + "set"); + sender.sendMessage(C_HEAD + "For a full command example list, simply run the root " + cmd(sender) + C_HEAD + "command by itself with nothing specified."); + } + + @Override + public void execute(CommandSender sender, Player player, List params, String worldName) { + if (params.isEmpty()) { + sendCmdHelp(sender); + return; + } + + Set commands = WorldBorder.wbCommand.getCommandNames(); + for (String param : params) { + if (commands.contains(param.toLowerCase())) { + WorldBorder.wbCommand.subCommands.get(param.toLowerCase()).sendCmdHelp(sender); + return; + } + } + sendErrorAndHelp(sender, "No command recognized."); + } +} diff --git a/src/main/java/com/wimbli/WorldBorder/cmd/CmdKnockback.java b/src/main/java/com/wimbli/WorldBorder/cmd/CmdKnockback.java new file mode 100644 index 0000000..7d7d8ab --- /dev/null +++ b/src/main/java/com/wimbli/WorldBorder/cmd/CmdKnockback.java @@ -0,0 +1,45 @@ +package com.wimbli.WorldBorder.cmd; + +import com.wimbli.WorldBorder.Config; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; + + +public class CmdKnockback extends WBCmd { + public CmdKnockback() { + name = permission = "knockback"; + minParams = maxParams = 1; + + addCmdExample(nameEmphasized() + " - how far to move the player back."); + helpText = "Default value: 3.0 (blocks). Players who cross the border will be knocked back to this distance inside."; + } + + @Override + public void cmdStatus(CommandSender sender) { + double kb = Config.KnockBack(); + if (kb < 1) + sender.sendMessage(C_HEAD + "Knockback is set to 0, disabling border enforcement."); + else + sender.sendMessage(C_HEAD + "Knockback is set to " + kb + " blocks inside the border."); + } + + @Override + public void execute(CommandSender sender, Player player, List params, String worldName) { + double numBlocks = 0.0; + try { + numBlocks = Double.parseDouble(params.get(0)); + if (numBlocks < 0.0 || (numBlocks > 0.0 && numBlocks < 1.0)) + throw new NumberFormatException(); + } catch (NumberFormatException ex) { + sendErrorAndHelp(sender, "The knockback must be a decimal value of at least 1.0, or it can be 0."); + return; + } + + Config.setKnockBack(numBlocks); + + if (player != null) + cmdStatus(sender); + } +} diff --git a/src/main/java/com/wimbli/WorldBorder/cmd/CmdList.java b/src/main/java/com/wimbli/WorldBorder/cmd/CmdList.java new file mode 100644 index 0000000..e0f71e9 --- /dev/null +++ b/src/main/java/com/wimbli/WorldBorder/cmd/CmdList.java @@ -0,0 +1,36 @@ +package com.wimbli.WorldBorder.cmd; + +import com.wimbli.WorldBorder.Config; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; +import java.util.Set; + + +public class CmdList extends WBCmd { + public CmdList() { + name = permission = "list"; + minParams = maxParams = 0; + + addCmdExample(nameEmphasized() + "- show border information for all worlds."); + helpText = "This command will list full information for every border you have set including position, " + + "radius, and shape. The default border shape will also be indicated."; + } + + @Override + public void execute(CommandSender sender, Player player, List params, String worldName) { + sender.sendMessage("Default border shape for all worlds is \"" + Config.ShapeName() + "\"."); + + Set list = Config.BorderDescriptions(); + + if (list.isEmpty()) { + sender.sendMessage("There are no borders currently set."); + return; + } + + for (String borderDesc : list) { + sender.sendMessage(borderDesc); + } + } +} diff --git a/src/main/java/com/wimbli/WorldBorder/cmd/CmdPortal.java b/src/main/java/com/wimbli/WorldBorder/cmd/CmdPortal.java new file mode 100644 index 0000000..b671387 --- /dev/null +++ b/src/main/java/com/wimbli/WorldBorder/cmd/CmdPortal.java @@ -0,0 +1,35 @@ +package com.wimbli.WorldBorder.cmd; + +import com.wimbli.WorldBorder.Config; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; + + +public class CmdPortal extends WBCmd { + public CmdPortal() { + name = permission = "portal"; + minParams = maxParams = 1; + + addCmdExample(nameEmphasized() + " - turn portal redirection on or off."); + helpText = "Default value: on. This feature monitors new portal creation and changes the target new portal " + + "location if it is outside of the border. Try disabling this if you have problems with other plugins " + + "related to portals."; + } + + @Override + public void cmdStatus(CommandSender sender) { + sender.sendMessage(C_HEAD + "Portal redirection is " + enabledColored(Config.portalRedirection()) + C_HEAD + "."); + } + + @Override + public void execute(CommandSender sender, Player player, List params, String worldName) { + Config.setPortalRedirection(strAsBool(params.get(0))); + + if (player != null) { + Config.log((Config.portalRedirection() ? "Enabled" : "Disabled") + " portal redirection at the command of player \"" + player.getName() + "\"."); + cmdStatus(sender); + } + } +} diff --git a/src/main/java/com/wimbli/WorldBorder/cmd/CmdPreventPlace.java b/src/main/java/com/wimbli/WorldBorder/cmd/CmdPreventPlace.java new file mode 100644 index 0000000..d25dc7c --- /dev/null +++ b/src/main/java/com/wimbli/WorldBorder/cmd/CmdPreventPlace.java @@ -0,0 +1,33 @@ +package com.wimbli.WorldBorder.cmd; + +import com.wimbli.WorldBorder.Config; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; + +public class CmdPreventPlace extends WBCmd { + + public CmdPreventPlace() { + name = permission = "preventblockplace"; + minParams = maxParams = 1; + + addCmdExample(nameEmphasized() + " - stop block placement past border."); + helpText = "Default value: off. When enabled, this setting will prevent players from placing blocks outside the world's border."; + } + + @Override + public void cmdStatus(CommandSender sender) { + sender.sendMessage(C_HEAD + "Prevention of block placement outside the border is " + enabledColored(Config.preventBlockPlace()) + C_HEAD + "."); + } + + @Override + public void execute(CommandSender sender, Player player, List params, String worldName) { + Config.setPreventBlockPlace(strAsBool(params.get(0))); + + if (player != null) { + Config.log((Config.preventBlockPlace() ? "Enabled" : "Disabled") + " preventblockplace at the command of player \"" + player.getName() + "\"."); + cmdStatus(sender); + } + } +} diff --git a/src/main/java/com/wimbli/WorldBorder/cmd/CmdPreventSpawn.java b/src/main/java/com/wimbli/WorldBorder/cmd/CmdPreventSpawn.java new file mode 100644 index 0000000..bd04327 --- /dev/null +++ b/src/main/java/com/wimbli/WorldBorder/cmd/CmdPreventSpawn.java @@ -0,0 +1,33 @@ +package com.wimbli.WorldBorder.cmd; + +import com.wimbli.WorldBorder.Config; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; + +public class CmdPreventSpawn extends WBCmd { + + public CmdPreventSpawn() { + name = permission = "preventmobspawn"; + minParams = maxParams = 1; + + addCmdExample(nameEmphasized() + " - stop mob spawning past border."); + helpText = "Default value: off. When enabled, this setting will prevent mobs from naturally spawning outside the world's border."; + } + + @Override + public void cmdStatus(CommandSender sender) { + sender.sendMessage(C_HEAD + "Prevention of mob spawning outside the border is " + enabledColored(Config.preventMobSpawn()) + C_HEAD + "."); + } + + @Override + public void execute(CommandSender sender, Player player, List params, String worldName) { + Config.setPreventMobSpawn(strAsBool(params.get(0))); + + if (player != null) { + Config.log((Config.preventMobSpawn() ? "Enabled" : "Disabled") + " preventmobspawn at the command of player \"" + player.getName() + "\"."); + cmdStatus(sender); + } + } +} diff --git a/src/main/java/com/wimbli/WorldBorder/cmd/CmdRadius.java b/src/main/java/com/wimbli/WorldBorder/cmd/CmdRadius.java new file mode 100644 index 0000000..5956de0 --- /dev/null +++ b/src/main/java/com/wimbli/WorldBorder/cmd/CmdRadius.java @@ -0,0 +1,74 @@ +package com.wimbli.WorldBorder.cmd; + +import com.wimbli.WorldBorder.BorderData; +import com.wimbli.WorldBorder.Config; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; + + +public class CmdRadius extends WBCmd { + public CmdRadius() { + name = permission = "radius"; + hasWorldNameInput = true; + minParams = 1; + maxParams = 2; + + addCmdExample(nameEmphasizedW() + " [radiusZ] - change radius."); + helpText = "Using this command you can adjust the radius of an existing border. If [radiusZ] is not " + + "specified, the radiusX value will be used for both. You can also optionally specify + or - at the start " + + "of and [radiusZ] to increase or decrease the existing radius rather than setting a new value."; + } + + @Override + public void execute(CommandSender sender, Player player, List params, String worldName) { + if (worldName == null) + worldName = player.getWorld().getName(); + + BorderData border = Config.Border(worldName); + if (border == null) { + sendErrorAndHelp(sender, "This world (\"" + worldName + "\") must first have a border set normally."); + return; + } + + double x = border.getX(); + double z = border.getZ(); + double radiusX; + double radiusZ; + try { + if (params.get(0).startsWith("+")) { + // Add to the current radius + radiusX = border.getRadiusX(); + radiusX += Integer.parseInt(params.get(0).substring(1)); + } else if (params.get(0).startsWith("-")) { + // Subtract from the current radius + radiusX = border.getRadiusX(); + radiusX -= Integer.parseInt(params.get(0).substring(1)); + } else + radiusX = Integer.parseInt(params.get(0)); + + if (params.size() == 2) { + if (params.get(1).startsWith("+")) { + // Add to the current radius + radiusZ = border.getRadiusZ(); + radiusZ += Integer.parseInt(params.get(1).substring(1)); + } else if (params.get(1).startsWith("-")) { + // Subtract from the current radius + radiusZ = border.getRadiusZ(); + radiusZ -= Integer.parseInt(params.get(1).substring(1)); + } else + radiusZ = Integer.parseInt(params.get(1)); + } else + radiusZ = radiusX; + } catch (NumberFormatException ex) { + sendErrorAndHelp(sender, "The radius value(s) must be integers."); + return; + } + + Config.setBorder(worldName, radiusX, radiusZ, x, z); + + if (player != null) + sender.sendMessage("Radius has been set. " + Config.BorderDescription(worldName)); + } +} diff --git a/src/main/java/com/wimbli/WorldBorder/cmd/CmdReload.java b/src/main/java/com/wimbli/WorldBorder/cmd/CmdReload.java new file mode 100644 index 0000000..eff2679 --- /dev/null +++ b/src/main/java/com/wimbli/WorldBorder/cmd/CmdReload.java @@ -0,0 +1,31 @@ +package com.wimbli.WorldBorder.cmd; + +import com.wimbli.WorldBorder.Config; +import com.wimbli.WorldBorder.WorldBorder; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; + + +public class CmdReload extends WBCmd { + public CmdReload() { + name = permission = "reload"; + minParams = maxParams = 0; + + addCmdExample(nameEmphasized() + "- re-load data from config.yml."); + helpText = "If you make manual changes to config.yml while the server is running, you can use this command " + + "to make WorldBorder load the changes without needing to restart the server."; + } + + @Override + public void execute(CommandSender sender, Player player, List params, String worldName) { + if (player != null) + Config.log("Reloading config file at the command of player \"" + player.getName() + "\"."); + + Config.load(WorldBorder.plugin, true); + + if (player != null) + sender.sendMessage("WorldBorder configuration reloaded."); + } +} diff --git a/src/main/java/com/wimbli/WorldBorder/cmd/CmdRemount.java b/src/main/java/com/wimbli/WorldBorder/cmd/CmdRemount.java new file mode 100644 index 0000000..deb55e8 --- /dev/null +++ b/src/main/java/com/wimbli/WorldBorder/cmd/CmdRemount.java @@ -0,0 +1,50 @@ +package com.wimbli.WorldBorder.cmd; + +import com.wimbli.WorldBorder.Config; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; + + +public class CmdRemount extends WBCmd { + public CmdRemount() { + name = permission = "remount"; + minParams = maxParams = 1; + + addCmdExample(nameEmphasized() + " - player remount delay after knockback."); + helpText = "Default value: 0 (disabled). If set higher than 0, WorldBorder will attempt to re-mount players who " + + "are knocked back from the border while riding something after this many server ticks. This setting can " + + "cause really nasty glitches if enabled and set too low due to CraftBukkit teleportation problems."; + } + + @Override + public void cmdStatus(CommandSender sender) { + int delay = Config.RemountTicks(); + if (delay == 0) + sender.sendMessage(C_HEAD + "Remount delay set to 0. Players will be left dismounted when knocked back from the border while on a vehicle."); + else { + sender.sendMessage(C_HEAD + "Remount delay set to " + delay + " tick(s). That is roughly " + (delay * 50) + "ms / " + (((double) delay * 50.0) / 1000.0) + " seconds. Setting to 0 would disable remounting."); + if (delay < 10) + sender.sendMessage(C_ERR + "WARNING:" + C_DESC + " setting this to less than 10 (and greater than 0) is not recommended. This can lead to nasty client glitches."); + } + } + + @Override + public void execute(CommandSender sender, Player player, List params, String worldName) { + int delay = 0; + try { + delay = Integer.parseInt(params.get(0)); + if (delay < 0) + throw new NumberFormatException(); + } catch (NumberFormatException ex) { + sendErrorAndHelp(sender, "The remount delay must be an integer of 0 or higher. Setting to 0 will disable remounting."); + return; + } + + Config.setRemountTicks(delay); + + if (player != null) + cmdStatus(sender); + } +} diff --git a/src/main/java/com/wimbli/WorldBorder/cmd/CmdSet.java b/src/main/java/com/wimbli/WorldBorder/cmd/CmdSet.java new file mode 100644 index 0000000..f2d5807 --- /dev/null +++ b/src/main/java/com/wimbli/WorldBorder/cmd/CmdSet.java @@ -0,0 +1,119 @@ +package com.wimbli.WorldBorder.cmd; + +import com.wimbli.WorldBorder.Config; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; + + +public class CmdSet extends WBCmd { + public CmdSet() { + name = permission = "set"; + hasWorldNameInput = true; + consoleRequiresWorldName = false; + minParams = 1; + maxParams = 4; + + addCmdExample(nameEmphasizedW() + " [radiusZ] - use x/z coords."); + addCmdExample(nameEmphasizedW() + " [radiusZ] ^spawn - use spawn point."); + addCmdExample(nameEmphasized() + " [radiusZ] - set border, centered on you.", true, false, true); + addCmdExample(nameEmphasized() + " [radiusZ] ^player - center on player."); + helpText = "Set a border for a world, with several options for defining the center location. [world] is " + + "optional for players and defaults to the world the player is in. If [radiusZ] is not specified, the " + + "radiusX value will be used for both. The and coordinates can be decimal values (ex. 1.234)."; + } + + @Override + public void execute(CommandSender sender, Player player, List params, String worldName) { + // passsing a single parameter (radiusX) is only acceptable from player + if ((params.size() == 1) && player == null) { + sendErrorAndHelp(sender, "You have not provided a sufficient number of parameters."); + return; + } + + // "set" command from player or console, world specified + if (worldName != null) { + if (params.size() == 2 && !params.get(params.size() - 1).equalsIgnoreCase("spawn")) { // command can only be this short if "spawn" is specified rather than x + z or player name + sendErrorAndHelp(sender, "You have not provided a sufficient number of arguments."); + return; + } + + World world = sender.getServer().getWorld(worldName); + if (world == null) { + if (params.get(params.size() - 1).equalsIgnoreCase("spawn")) { + sendErrorAndHelp(sender, "The world you specified (\"" + worldName + "\") could not be found on the server, so the spawn point cannot be determined."); + return; + } + sender.sendMessage("The world you specified (\"" + worldName + "\") could not be found on the server, but data for it will be stored anyway."); + } + } + // "set" command from player using current world since it isn't specified, or allowed from console only if player name is specified + else { + if (player == null) { + if (!params.get(params.size() - 2).equalsIgnoreCase("player")) { // command can only be called by console without world specified if player is specified instead + sendErrorAndHelp(sender, "You must specify a world name from console if not specifying a player name."); + return; + } + player = Bukkit.getPlayer(params.get(params.size() - 1)); + if (player == null || !player.isOnline()) { + sendErrorAndHelp(sender, "The player you specified (\"" + params.get(params.size() - 1) + "\") does not appear to be online."); + return; + } + } + worldName = player.getWorld().getName(); + } + + int radiusX, radiusZ; + double x, z; + int radiusCount = params.size(); + + try { + if (params.get(params.size() - 1).equalsIgnoreCase("spawn")) { // "spawn" specified for x/z coordinates + Location loc = sender.getServer().getWorld(worldName).getSpawnLocation(); + x = loc.getX(); + z = loc.getZ(); + radiusCount -= 1; + } else if (params.size() > 2 && params.get(params.size() - 2).equalsIgnoreCase("player")) { // player name specified for x/z coordinates + Player playerT = Bukkit.getPlayer(params.get(params.size() - 1)); + if (playerT == null || !playerT.isOnline()) { + sendErrorAndHelp(sender, "The player you specified (\"" + params.get(params.size() - 1) + "\") does not appear to be online."); + return; + } + worldName = playerT.getWorld().getName(); + x = playerT.getLocation().getX(); + z = playerT.getLocation().getZ(); + radiusCount -= 2; + } else { + if (player == null || radiusCount > 2) { // x and z specified + x = Double.parseDouble(params.get(params.size() - 2)); + z = Double.parseDouble(params.get(params.size() - 1)); + radiusCount -= 2; + } else { // using coordinates of command sender (player) + x = player.getLocation().getX(); + z = player.getLocation().getZ(); + } + } + + radiusX = Integer.parseInt(params.get(0)); + if (radiusCount < 2) + radiusZ = radiusX; + else + radiusZ = Integer.parseInt(params.get(1)); + + if (radiusX < Config.KnockBack() || radiusZ < Config.KnockBack()) { + sendErrorAndHelp(sender, "Radius value(s) must be more than the knockback distance."); + return; + } + } catch (NumberFormatException ex) { + sendErrorAndHelp(sender, "Radius value(s) must be integers and x and z values must be numerical."); + return; + } + + Config.setBorder(worldName, radiusX, radiusZ, x, z); + sender.sendMessage("Border has been set. " + Config.BorderDescription(worldName)); + } +} diff --git a/src/main/java/com/wimbli/WorldBorder/cmd/CmdSetcorners.java b/src/main/java/com/wimbli/WorldBorder/cmd/CmdSetcorners.java new file mode 100644 index 0000000..7894e82 --- /dev/null +++ b/src/main/java/com/wimbli/WorldBorder/cmd/CmdSetcorners.java @@ -0,0 +1,48 @@ +package com.wimbli.WorldBorder.cmd; + +import com.wimbli.WorldBorder.Config; +import org.bukkit.World; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; + + +public class CmdSetcorners extends WBCmd { + public CmdSetcorners() { + name = "setcorners"; + permission = "set"; + hasWorldNameInput = true; + minParams = maxParams = 4; + + addCmdExample(nameEmphasizedW() + " - corner coords."); + helpText = "This is an alternate way to set a border, by specifying the X and Z coordinates of two opposite " + + "corners of the border area ((x1, z1) to (x2, z2)). [world] is optional for players and defaults to the " + + "world the player is in."; + } + + @Override + public void execute(CommandSender sender, Player player, List params, String worldName) { + if (worldName == null) { + worldName = player.getWorld().getName(); + } else { + World worldTest = sender.getServer().getWorld(worldName); + if (worldTest == null) + sender.sendMessage("The world you specified (\"" + worldName + "\") could not be found on the server, but data for it will be stored anyway."); + } + + try { + double x1 = Double.parseDouble(params.get(0)); + double z1 = Double.parseDouble(params.get(1)); + double x2 = Double.parseDouble(params.get(2)); + double z2 = Double.parseDouble(params.get(3)); + Config.setBorderCorners(worldName, x1, z1, x2, z2); + } catch (NumberFormatException ex) { + sendErrorAndHelp(sender, "The x1, z1, x2, and z2 coordinate values must be numerical."); + return; + } + + if (player != null) + sender.sendMessage("Border has been set. " + Config.BorderDescription(worldName)); + } +} diff --git a/src/main/java/com/wimbli/WorldBorder/cmd/CmdSetmsg.java b/src/main/java/com/wimbli/WorldBorder/cmd/CmdSetmsg.java new file mode 100644 index 0000000..6dda926 --- /dev/null +++ b/src/main/java/com/wimbli/WorldBorder/cmd/CmdSetmsg.java @@ -0,0 +1,42 @@ +package com.wimbli.WorldBorder.cmd; + +import com.wimbli.WorldBorder.Config; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; + + +public class CmdSetmsg extends WBCmd { + public CmdSetmsg() { + name = permission = "setmsg"; + minParams = 1; + + addCmdExample(nameEmphasized() + " - set border message."); + helpText = "Default value: \"&cYou have reached the edge of this world.\". This command lets you set the message shown to players who are knocked back from the border."; + } + + @Override + public void cmdStatus(CommandSender sender) { + sender.sendMessage(C_HEAD + "Border message is set to:"); + sender.sendMessage(Config.MessageRaw()); + sender.sendMessage(C_HEAD + "Formatted border message:"); + sender.sendMessage(Config.Message()); + } + + @Override + public void execute(CommandSender sender, Player player, List params, String worldName) { + StringBuilder message = new StringBuilder(); + boolean first = true; + for (String param : params) { + if (!first) + message.append(" "); + message.append(param); + first = false; + } + + Config.setMessage(message.toString()); + + cmdStatus(sender); + } +} diff --git a/src/main/java/com/wimbli/WorldBorder/cmd/CmdShape.java b/src/main/java/com/wimbli/WorldBorder/cmd/CmdShape.java new file mode 100644 index 0000000..77ab166 --- /dev/null +++ b/src/main/java/com/wimbli/WorldBorder/cmd/CmdShape.java @@ -0,0 +1,43 @@ +package com.wimbli.WorldBorder.cmd; + +import com.wimbli.WorldBorder.Config; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; + + +public class CmdShape extends WBCmd { + public CmdShape() { + name = permission = "shape"; + minParams = maxParams = 1; + + addCmdExample(nameEmphasized() + " - set the default border shape."); + addCmdExample(nameEmphasized() + " - same as above."); + helpText = "Default value: square/rectangular. The default border shape will be used on all worlds which don't " + + "have an individual shape set using the " + commandEmphasized("wshape") + C_DESC + "command. Elliptic " + + "and round work the same, as rectangular and square do. The difference is down to whether the X and Z " + + "radius are the same."; + } + + @Override + public void cmdStatus(CommandSender sender) { + sender.sendMessage(C_HEAD + "The default border shape for all worlds is currently set to \"" + Config.ShapeName() + "\"."); + } + + @Override + public void execute(CommandSender sender, Player player, List params, String worldName) { + String shape = params.get(0).toLowerCase(); + if (shape.equals("rectangular") || shape.equals("square")) + Config.setShape(false); + else if (shape.equals("elliptic") || shape.equals("round")) + Config.setShape(true); + else { + sendErrorAndHelp(sender, "You must specify one of the 4 valid shape names below."); + return; + } + + if (player != null) + cmdStatus(sender); + } +} diff --git a/src/main/java/com/wimbli/WorldBorder/cmd/CmdTrim.java b/src/main/java/com/wimbli/WorldBorder/cmd/CmdTrim.java new file mode 100644 index 0000000..7700e65 --- /dev/null +++ b/src/main/java/com/wimbli/WorldBorder/cmd/CmdTrim.java @@ -0,0 +1,152 @@ +package com.wimbli.WorldBorder.cmd; + +import com.wimbli.WorldBorder.Config; +import com.wimbli.WorldBorder.CoordXZ; +import com.wimbli.WorldBorder.WorldBorder; +import com.wimbli.WorldBorder.WorldTrimTask; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; + + +public class CmdTrim extends WBCmd { + /* with "view-distance=10" in server.properties on a fast VM test server and "Render Distance: Far" in client, + * hitting border during testing was loading 11+ chunks beyond the border in a couple of directions (10 chunks in + * the other two directions). This could be worse on a more loaded or worse server, so: + */ + private final int defaultPadding = CoordXZ.chunkToBlock(13); + private String trimWorld = ""; + private int trimFrequency = 5000; + private int trimPadding = defaultPadding; + + public CmdTrim() { + name = permission = "trim"; + hasWorldNameInput = true; + consoleRequiresWorldName = false; + minParams = 0; + maxParams = 2; + + addCmdExample(nameEmphasizedW() + "[freq] [pad] - trim world outside of border."); + helpText = "This command will remove chunks which are outside the world's border (even modified chunks!). [freq] is the frequency " + + "of chunks per second that will be checked (default 5000). [pad] is the number of blocks padding kept " + + "beyond the border itself (default 208, to cover player visual range)."; + } + + @Override + public void execute(CommandSender sender, Player player, List params, String worldName) { + boolean confirm = false; + // check for "cancel", "pause", or "confirm" + if (params.size() >= 1) { + String check = params.get(0).toLowerCase(); + + if (check.equals("cancel") || check.equals("stop")) { + if (!makeSureTrimIsRunning(sender)) + return; + sender.sendMessage(C_HEAD + "Cancelling the world map trimming task."); + trimDefaults(); + Config.StopTrimTask(); + return; + } else if (check.equals("pause")) { + if (!makeSureTrimIsRunning(sender)) + return; + Config.trimTask.pause(); + sender.sendMessage(C_HEAD + "The world map trimming task is now " + (Config.trimTask.isPaused() ? "" : "un") + "paused."); + return; + } + + confirm = check.equals("confirm"); + } + + // if not just confirming, make sure a world name is available + if (worldName == null && !confirm) { + if (player != null) + worldName = player.getWorld().getName(); + else { + sendErrorAndHelp(sender, "You must specify a world!"); + return; + } + } + + // colorized "/wb trim " + String cmd = cmd(sender) + nameEmphasized() + C_CMD; + + // make sure Trim isn't already running + if (Config.trimTask != null && Config.trimTask.valid()) { + sender.sendMessage(C_ERR + "The world map trimming task is already running."); + sender.sendMessage(C_DESC + "You can cancel at any time with " + cmd + "cancel" + C_DESC + ", or pause/unpause with " + cmd + "pause" + C_DESC + "."); + return; + } + + // set frequency and/or padding if those were specified + try { + if (params.size() >= 1 && !confirm) + trimFrequency = Math.abs(Integer.parseInt(params.get(0))); + if (params.size() >= 2 && !confirm) + trimPadding = Math.abs(Integer.parseInt(params.get(1))); + } catch (NumberFormatException ex) { + sendErrorAndHelp(sender, "The frequency and padding values must be integers."); + trimDefaults(); + return; + } + if (trimFrequency <= 0) { + sendErrorAndHelp(sender, "The frequency value must be greater than zero."); + trimDefaults(); + return; + } + + // set world if it was specified + if (worldName != null) + trimWorld = worldName; + + if (confirm) { // command confirmed, go ahead with it + if (trimWorld.isEmpty()) { + sendErrorAndHelp(sender, "You must first use this command successfully without confirming."); + return; + } + + if (player != null) + Config.log("Trimming world beyond border at the command of player \"" + player.getName() + "\"."); + + int ticks = 1, repeats = 1; + if (trimFrequency > 20) + repeats = trimFrequency / 20; + else + ticks = 20 / trimFrequency; + + Config.trimTask = new WorldTrimTask(Bukkit.getServer(), player, trimWorld, trimPadding, repeats); + if (Config.trimTask.valid()) { + int task = Bukkit.getServer().getScheduler().scheduleSyncRepeatingTask(WorldBorder.plugin, Config.trimTask, ticks, ticks); + Config.trimTask.setTaskID(task); + sender.sendMessage("WorldBorder map trimming task for world \"" + trimWorld + "\" started."); + } else + sender.sendMessage(C_ERR + "The world map trimming task failed to start."); + + trimDefaults(); + } else { + if (trimWorld.isEmpty()) { + sendErrorAndHelp(sender, "You must first specify a valid world."); + return; + } + + sender.sendMessage(C_HEAD + "World trimming task is ready for world \"" + trimWorld + "\", attempting to process up to " + trimFrequency + " chunks per second (default 20). The map will be trimmed past " + trimPadding + " blocks beyond the border (default " + defaultPadding + ")."); + sender.sendMessage(C_HEAD + "This process can take a very long time depending on the world's overall size. Also, depending on the chunk processing rate, players may experience lag for the duration."); + sender.sendMessage(C_DESC + "You should now use " + cmd + "confirm" + C_DESC + " to start the process."); + sender.sendMessage(C_DESC + "You can cancel at any time with " + cmd + "cancel" + C_DESC + ", or pause/unpause with " + cmd + "pause" + C_DESC + "."); + } + } + + private void trimDefaults() { + trimWorld = ""; + trimFrequency = 5000; + trimPadding = defaultPadding; + } + + private boolean makeSureTrimIsRunning(CommandSender sender) { + if (Config.trimTask != null && Config.trimTask.valid()) + return true; + sendErrorAndHelp(sender, "The world map trimming task is not currently running."); + return false; + } +} diff --git a/src/main/java/com/wimbli/WorldBorder/cmd/CmdWhoosh.java b/src/main/java/com/wimbli/WorldBorder/cmd/CmdWhoosh.java new file mode 100644 index 0000000..250af7b --- /dev/null +++ b/src/main/java/com/wimbli/WorldBorder/cmd/CmdWhoosh.java @@ -0,0 +1,34 @@ +package com.wimbli.WorldBorder.cmd; + +import com.wimbli.WorldBorder.Config; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; + + +public class CmdWhoosh extends WBCmd { + public CmdWhoosh() { + name = permission = "whoosh"; + minParams = maxParams = 1; + + addCmdExample(nameEmphasized() + " - turn knockback effect on or off."); + helpText = "Default value: on. This will show a particle effect and play a sound where a player is knocked " + + "back from the border."; + } + + @Override + public void cmdStatus(CommandSender sender) { + sender.sendMessage(C_HEAD + "\"Whoosh\" knockback effect is " + enabledColored(Config.whooshEffect()) + C_HEAD + "."); + } + + @Override + public void execute(CommandSender sender, Player player, List params, String worldName) { + Config.setWhooshEffect(strAsBool(params.get(0))); + + if (player != null) { + Config.log((Config.whooshEffect() ? "Enabled" : "Disabled") + " \"whoosh\" knockback effect at the command of player \"" + player.getName() + "\"."); + cmdStatus(sender); + } + } +} diff --git a/src/main/java/com/wimbli/WorldBorder/cmd/CmdWrap.java b/src/main/java/com/wimbli/WorldBorder/cmd/CmdWrap.java new file mode 100644 index 0000000..a2e4d12 --- /dev/null +++ b/src/main/java/com/wimbli/WorldBorder/cmd/CmdWrap.java @@ -0,0 +1,54 @@ +package com.wimbli.WorldBorder.cmd; + +import com.wimbli.WorldBorder.BorderData; +import com.wimbli.WorldBorder.Config; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; + + +public class CmdWrap extends WBCmd { + public CmdWrap() { + name = permission = "wrap"; + minParams = 1; + maxParams = 2; + + addCmdExample(nameEmphasized() + "{world} - can make border crossings wrap."); + helpText = "When border wrapping is enabled for a world, players will be sent around to the opposite edge " + + "of the border when they cross it instead of being knocked back. [world] is optional for players and " + + "defaults to the world the player is in."; + } + + @Override + public void execute(CommandSender sender, Player player, List params, String worldName) { + if (player == null && params.size() == 1) { + sendErrorAndHelp(sender, "When running this command from console, you must specify a world."); + return; + } + + boolean wrap = false; + + // world and wrap on/off specified + if (params.size() == 2) { + worldName = params.get(0); + wrap = strAsBool(params.get(1)); + } + // no world specified, just wrap on/off + else { + worldName = player.getWorld().getName(); + wrap = strAsBool(params.get(0)); + } + + BorderData border = Config.Border(worldName); + if (border == null) { + sendErrorAndHelp(sender, "This world (\"" + worldName + "\") does not have a border set."); + return; + } + + border.setWrapping(wrap); + Config.setBorder(worldName, border, false); + + sender.sendMessage("Border for world \"" + worldName + "\" is now set to " + (wrap ? "" : "not ") + "wrap around."); + } +} diff --git a/src/main/java/com/wimbli/WorldBorder/cmd/CmdWshape.java b/src/main/java/com/wimbli/WorldBorder/cmd/CmdWshape.java new file mode 100644 index 0000000..a34e21d --- /dev/null +++ b/src/main/java/com/wimbli/WorldBorder/cmd/CmdWshape.java @@ -0,0 +1,62 @@ +package com.wimbli.WorldBorder.cmd; + +import com.wimbli.WorldBorder.BorderData; +import com.wimbli.WorldBorder.Config; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; + + +public class CmdWshape extends WBCmd { + public CmdWshape() { + name = permission = "wshape"; + minParams = 1; + maxParams = 2; + + addCmdExample(nameEmphasized() + "{world} - shape"); + addCmdExample(C_DESC + " override for a single world.", true, true, false); + addCmdExample(nameEmphasized() + "{world} - same as above."); + helpText = "This will override the default border shape for a single world. The value \"default\" implies " + + "a world is just using the default border shape. See the " + commandEmphasized("shape") + C_DESC + + "command for more info and to set the default border shape."; + } + + @Override + public void execute(CommandSender sender, Player player, List params, String worldName) { + if (player == null && params.size() == 1) { + sendErrorAndHelp(sender, "When running this command from console, you must specify a world."); + return; + } + + String shapeName = ""; + + // world and shape specified + if (params.size() == 2) { + worldName = params.get(0); + shapeName = params.get(1).toLowerCase(); + } + // no world specified, just shape + else { + worldName = player.getWorld().getName(); + shapeName = params.get(0).toLowerCase(); + } + + BorderData border = Config.Border(worldName); + if (border == null) { + sendErrorAndHelp(sender, "This world (\"" + worldName + "\") does not have a border set."); + return; + } + + Boolean shape = null; + if (shapeName.equals("rectangular") || shapeName.equals("square")) + shape = false; + else if (shapeName.equals("elliptic") || shapeName.equals("round")) + shape = true; + + border.setShape(shape); + Config.setBorder(worldName, border, false); + + sender.sendMessage("Border shape for world \"" + worldName + "\" is now set to \"" + Config.ShapeName(shape) + "\"."); + } +} diff --git a/src/main/java/com/wimbli/WorldBorder/cmd/WBCmd.java b/src/main/java/com/wimbli/WorldBorder/cmd/WBCmd.java new file mode 100644 index 0000000..d5a9e18 --- /dev/null +++ b/src/main/java/com/wimbli/WorldBorder/cmd/WBCmd.java @@ -0,0 +1,128 @@ +package com.wimbli.WorldBorder.cmd; + +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; + + +public abstract class WBCmd { + /* + * Primary variables, should be set as needed in constructors for the subclassed commands + */ + + // color values for strings + public final static String C_CMD = ChatColor.AQUA.toString(); // main commands + public final static String C_DESC = ChatColor.WHITE.toString(); // command descriptions + public final static String C_ERR = ChatColor.RED.toString(); // errors / notices + public final static String C_HEAD = ChatColor.YELLOW.toString(); // command listing header + public final static String C_OPT = ChatColor.DARK_GREEN.toString(); // optional values + public final static String C_REQ = ChatColor.GREEN.toString(); // required values + // colorized root command, for console and for player + public final static String CMD_C = C_CMD + "wb "; + public final static String CMD_P = C_CMD + "/wb "; + // much like the above, but used for displaying command list from root /wb command, listing all commands + public final static List cmdExamplesConsole = new ArrayList(48); // 48 command capacity, 6 full pages + + + /* + * Helper variables and methods + */ + public final static List cmdExamplesPlayer = new ArrayList(48); // still, could need to increase later + // list of command examples for this command to be displayed as usage reference, separate between players and console + // ... these generally should be set indirectly using addCmdExample() within the constructor for each command class + public final List cmdExamplePlayer = new ArrayList(); + public final List cmdExampleConsole = new ArrayList(); + // command name, command permission; normally the same thing + public String name = ""; + public String permission = null; + // whether command can accept a world name before itself + public boolean hasWorldNameInput = false; + public boolean consoleRequiresWorldName = true; + // minimum and maximum number of accepted parameters + public int minParams = 0; + public int maxParams = 9999; + // help/explanation text to be shown after command example(s) for this command + public String helpText = null; + + /* + * The guts of the command run in here; needs to be overriden in the subclassed commands + */ + public abstract void execute(CommandSender sender, Player player, List params, String worldName); + + /* + * This is an optional override, used to provide some extra command status info, like the currently set value + */ + public void cmdStatus(CommandSender sender) { + } + + // add command examples for use the default "/wb" command list and for internal usage reference, formatted and colorized + public void addCmdExample(String example) { + addCmdExample(example, true, true, true); + } + + public void addCmdExample(String example, boolean forPlayer, boolean forConsole, boolean prefix) { + // go ahead and colorize required "<>" and optional "[]" parameters, extra command words, and description + example = example.replace("<", C_REQ + "<").replace("[", C_OPT + "[").replace("^", C_CMD).replace("- ", C_DESC + "- "); + + // all "{}" are replaced by "[]" (optional) for player, "<>" (required) for console + if (forPlayer) { + String exampleP = (prefix ? CMD_P : "") + example.replace("{", C_OPT + "[").replace("}", "]"); + cmdExamplePlayer.add(exampleP); + cmdExamplesPlayer.add(exampleP); + } + if (forConsole) { + String exampleC = (prefix ? CMD_C : "") + example.replace("{", C_REQ + "<").replace("}", ">"); + cmdExampleConsole.add(exampleC); + cmdExamplesConsole.add(exampleC); + } + } + + // return root command formatted for player or console, based on sender + public String cmd(CommandSender sender) { + return (sender instanceof Player) ? CMD_P : CMD_C; + } + + // formatted and colorized text, intended for marking command name + public String commandEmphasized(String text) { + return C_CMD + ChatColor.UNDERLINE + text + ChatColor.RESET + " "; + } + + // returns green "enabled" or red "disabled" text + public String enabledColored(boolean enabled) { + return enabled ? C_REQ + "enabled" : C_ERR + "disabled"; + } + + // formatted and colorized command name, optionally prefixed with "[world]" (for player) / "" (for console) + public String nameEmphasized() { + return commandEmphasized(name); + } + + public String nameEmphasizedW() { + return "{world} " + nameEmphasized(); + } + + // send command example message(s) and other helpful info + public void sendCmdHelp(CommandSender sender) { + for (String example : ((sender instanceof Player) ? cmdExamplePlayer : cmdExampleConsole)) { + sender.sendMessage(example); + } + cmdStatus(sender); + if (helpText != null && !helpText.isEmpty()) + sender.sendMessage(C_DESC + helpText); + } + + // send error message followed by command example message(s) + public void sendErrorAndHelp(CommandSender sender, String error) { + sender.sendMessage(C_ERR + error); + sendCmdHelp(sender); + } + + // interpret string as boolean value (yes/no, true/false, on/off, +/-, 1/0) + public boolean strAsBool(String str) { + str = str.toLowerCase(); + return str.startsWith("y") || str.startsWith("t") || str.startsWith("on") || str.startsWith("+") || str.startsWith("1"); + } +}