commit 54b8a000a47050b0bdfd17ab7f50ce42255ad8e0 Author: Brettflan Date: Fri Apr 1 13:04:08 2011 -0500 Initial commit, version 0.9 diff --git a/README.md b/README.md new file mode 100644 index 0000000..3042383 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +WorldBorder +=========== + +A Bukkit plugin to provide borders for your worlds, to limit their sizes. \ No newline at end of file diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..51b370b --- /dev/null +++ b/build.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + Builds, tests, and runs the project WorldBorder. + + + diff --git a/lib/CopyLibs/org-netbeans-modules-java-j2seproject-copylibstask.jar b/lib/CopyLibs/org-netbeans-modules-java-j2seproject-copylibstask.jar new file mode 100644 index 0000000..4a59964 Binary files /dev/null and b/lib/CopyLibs/org-netbeans-modules-java-j2seproject-copylibstask.jar differ diff --git a/lib/junit/junit-3.8.2-api.zip b/lib/junit/junit-3.8.2-api.zip new file mode 100644 index 0000000..6d792fd Binary files /dev/null and b/lib/junit/junit-3.8.2-api.zip differ diff --git a/lib/junit/junit-3.8.2.jar b/lib/junit/junit-3.8.2.jar new file mode 100644 index 0000000..d835872 Binary files /dev/null and b/lib/junit/junit-3.8.2.jar differ diff --git a/lib/junit_4/junit-4.5-api.zip b/lib/junit_4/junit-4.5-api.zip new file mode 100644 index 0000000..5748c44 Binary files /dev/null and b/lib/junit_4/junit-4.5-api.zip differ diff --git a/lib/junit_4/junit-4.5-src.jar b/lib/junit_4/junit-4.5-src.jar new file mode 100644 index 0000000..18774a5 Binary files /dev/null and b/lib/junit_4/junit-4.5-src.jar differ diff --git a/lib/junit_4/junit-4.5.jar b/lib/junit_4/junit-4.5.jar new file mode 100644 index 0000000..83f8bc7 Binary files /dev/null and b/lib/junit_4/junit-4.5.jar differ diff --git a/lib/nblibraries.properties b/lib/nblibraries.properties new file mode 100644 index 0000000..9137b06 --- /dev/null +++ b/lib/nblibraries.properties @@ -0,0 +1,12 @@ +libs.CopyLibs.classpath=\ + ${base}/CopyLibs/org-netbeans-modules-java-j2seproject-copylibstask.jar +libs.junit.classpath=\ + ${base}/junit/junit-3.8.2.jar +libs.junit.javadoc=\ + ${base}/junit/junit-3.8.2-api.zip +libs.junit_4.classpath=\ + ${base}/junit_4/junit-4.5.jar +libs.junit_4.javadoc=\ + ${base}/junit_4/junit-4.5-api.zip +libs.junit_4.src=\ + ${base}/junit_4/junit-4.5-src.jar diff --git a/manifest.mf b/manifest.mf new file mode 100644 index 0000000..328e8e5 --- /dev/null +++ b/manifest.mf @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +X-COMMENT: Main-Class will be added automatically by build + diff --git a/nbproject/build-impl.xml b/nbproject/build-impl.xml new file mode 100644 index 0000000..3cf9c87 --- /dev/null +++ b/nbproject/build-impl.xml @@ -0,0 +1,908 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set src.src.dir + Must set build.dir + Must set dist.dir + Must set build.classes.dir + Must set dist.javadoc.dir + Must set build.test.classes.dir + Must set build.test.results.dir + Must set build.classes.excludes + Must set dist.jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + To run this application from the command line without Ant, try: + + + + + + + java -cp "${run.classpath.with.dist.jar}" ${main.class} + + + + + + + + + + + + To run this application from the command line without Ant, try: + + java -jar "${dist.jar.resolved}" + + + + + + + + To run this application from the command line without Ant, try: + + java -jar "${dist.jar.resolved}" + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + Must select one file in the IDE or set run.class + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set debug.class + + + + + Must select one file in the IDE or set debug.class + + + + + Must set fix.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + Some tests failed; see details above. + + + + + + + + + Must select some files in the IDE or set test.includes + + + + Some tests failed; see details above. + + + + + Must select one file in the IDE or set test.class + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nbproject/genfiles.properties b/nbproject/genfiles.properties new file mode 100644 index 0000000..5f2664b --- /dev/null +++ b/nbproject/genfiles.properties @@ -0,0 +1,8 @@ +build.xml.data.CRC32=7ce24f55 +build.xml.script.CRC32=cc9e7e41 +build.xml.stylesheet.CRC32=28e38971@1.38.3.45 +# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. +# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. +nbproject/build-impl.xml.data.CRC32=7ce24f55 +nbproject/build-impl.xml.script.CRC32=cf0abb2c +nbproject/build-impl.xml.stylesheet.CRC32=229523de@1.38.3.45 diff --git a/nbproject/private/config.properties b/nbproject/private/config.properties new file mode 100644 index 0000000..e69de29 diff --git a/nbproject/private/private.properties b/nbproject/private/private.properties new file mode 100644 index 0000000..452e38e --- /dev/null +++ b/nbproject/private/private.properties @@ -0,0 +1,7 @@ +compile.on.save=true +do.depend=false +do.jar=true +file.reference.CalcTest-src=C:\\Users\\Brett Flannigan\\Documents\\svn\\WorldBorder\\src +javac.debug=true +javadoc.preview=true +user.properties.file=C:\\Users\\Brett Flannigan\\.netbeans\\6.9\\build.properties diff --git a/nbproject/private/private.xml b/nbproject/private/private.xml new file mode 100644 index 0000000..c1f155a --- /dev/null +++ b/nbproject/private/private.xml @@ -0,0 +1,4 @@ + + + + diff --git a/nbproject/project.properties b/nbproject/project.properties new file mode 100644 index 0000000..1e83715 --- /dev/null +++ b/nbproject/project.properties @@ -0,0 +1,77 @@ +annotation.processing.enabled=true +annotation.processing.enabled.in.editor=false +annotation.processing.run.all.processors=true +annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output +application.title=WorldBorder +application.vendor=Brett Flannigan +build.classes.dir=${build.dir}/classes +build.classes.excludes=**/*.java,**/*.form +# This directory is removed when the project is cleaned: +build.dir=build +build.generated.dir=${build.dir}/generated +build.generated.sources.dir=${build.dir}/generated-sources +# Only compile against the classpath explicitly listed here: +build.sysclasspath=ignore +build.test.classes.dir=${build.dir}/test/classes +build.test.results.dir=${build.dir}/test/results +# Uncomment to specify the preferred debugger connection transport: +#debug.transport=dt_socket +debug.classpath=\ + ${run.classpath} +debug.test.classpath=\ + ${run.test.classpath} +# This directory is removed when the project is cleaned: +dist.dir=dist +dist.jar=${dist.dir}/WorldBorder.jar +dist.javadoc.dir=${dist.dir}/javadoc +endorsed.classpath= +excludes= +file.reference.CalcTest-src=src +file.reference.craftbukkit-0.0.1-SNAPSHOT.jar=lib/craftbukkit-0.0.1-SNAPSHOT.jar +file.reference.Permissions.jar=lib/Permissions.jar +includes=** +jar.compress=true +javac.classpath=\ + ${file.reference.craftbukkit-0.0.1-SNAPSHOT.jar}:\ + ${file.reference.Permissions.jar} +# Space-separated list of extra javac options +javac.compilerargs= +javac.deprecation=false +javac.processorpath=\ + ${javac.classpath} +javac.source=1.5 +javac.target=1.5 +javac.test.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir}:\ + ${libs.junit.classpath}:\ + ${libs.junit_4.classpath} +javac.test.processorpath=\ + ${javac.test.classpath} +javadoc.additionalparam= +javadoc.author=false +javadoc.encoding=${source.encoding} +javadoc.noindex=false +javadoc.nonavbar=false +javadoc.notree=false +javadoc.private=false +javadoc.splitindex=true +javadoc.use=true +javadoc.version=false +javadoc.windowtitle= +main.class= +manifest.file=manifest.mf +meta.inf.dir=${src.dir}/META-INF +platform.active=default_platform +run.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir} +# Space-separated list of JVM arguments used when running the project +# (you may also define separate properties like run-sys-prop.name=value instead of -Dname=value +# or test-sys-prop.name=value to set system properties for unit tests): +run.jvmargs= +run.test.classpath=\ + ${javac.test.classpath}:\ + ${build.test.classes.dir} +source.encoding=ISO-8859-1 +src.src.dir=src diff --git a/nbproject/project.xml b/nbproject/project.xml new file mode 100644 index 0000000..606574c --- /dev/null +++ b/nbproject/project.xml @@ -0,0 +1,16 @@ + + + org.netbeans.modules.java.j2seproject + + + WorldBorder + + + + + + + lib\nblibraries.properties + + + diff --git a/src/com/wimbli/WorldBorder/BorderData.java b/src/com/wimbli/WorldBorder/BorderData.java new file mode 100644 index 0000000..7fe760f --- /dev/null +++ b/src/com/wimbli/WorldBorder/BorderData.java @@ -0,0 +1,177 @@ +package com.wimbli.WorldBorder; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.bukkit.Location; +import org.bukkit.World; + +public class BorderData { + // the main data interacted with + private double x = 0; + private double z = 0; + private int radius = 0; + + // some extra data kept handy for faster border checks + private double maxX; + private double minX; + private double maxZ; + private double minZ; + private int radiusSquared; + private double DefiniteSquare; + + public BorderData(double x, double z, int radius) + { + this.x = x; + this.z = z; + this.radius = radius; + this.maxX = x + radius; + this.minX = x - radius; + this.maxZ = z + radius; + this.minZ = z - radius; + this.radiusSquared = radius * radius; + this.DefiniteSquare = Math.sqrt(.5 * this.radiusSquared); + } + + public double getX() + { + return x; + } + public void setX(double x) + { + this.x = x; + this.maxX = x + radius; + this.minX = x - radius; + } + public double getZ() + { + return z; + } + public void setZ(double z) + { + this.z = z; + this.maxZ = z + radius; + this.minZ = z - radius; + } + public int getRadius() + { + return radius; + } + public void setRadius(int radius) + { + this.radius = radius; + this.maxX = x + radius; + this.minX = x - radius; + this.maxZ = z + radius; + this.minZ = z - radius; + this.radiusSquared = radius * radius; + this.DefiniteSquare = Math.sqrt(.5 * this.radiusSquared); + } + + @Override + public String toString() + { + return "radius " + radius + " at X: " + Config.coord.format(x) + " Z: " + Config.coord.format(z); + } + + // This algorithm of course needs to be fast, since it will be run very frequently + public boolean insideBorder(double xLoc, double zLoc, boolean round) + { + if (!round) // square border + return (xLoc > minX && xLoc < maxX && zLoc > minZ && zLoc < maxZ); + else // round border + { + // round border checking algorithm is from rBorder by Reil with almost no changes, thanks + double X = Math.abs(x - xLoc); + double Z = Math.abs(z - zLoc); + + if (X < DefiniteSquare && Z < DefiniteSquare) + return true; // Definitely inside + else if (X >= radius || Z >= radius) + return false; // Definitely outside + else if (X * X + Z * Z < radiusSquared) + return true; // After much calculation, inside + else + return false; // Apparently outside, then + } + } + + public Location correctedPosition(Location loc, boolean round) + { + double xLoc = loc.getX(); + double zLoc = loc.getZ(); + double yLoc = loc.getY(); + + if (!round) // square border + { + if (xLoc <= minX) + xLoc = minX + 3; + else if (xLoc >= maxX) + xLoc = maxX - 3; + if (zLoc <= minZ) + zLoc = minZ + 3; + else if (zLoc >= maxZ) + zLoc = maxZ - 3; + } + else // round border + { + // algorithm from: http://stackoverflow.com/questions/300871/best-way-to-find-a-point-on-a-circle-closest-to-a-given-point + double vX = xLoc - x; + double vZ = zLoc - z; + double magV = Math.sqrt(vX*vX + vZ*vZ); + xLoc = x + vX / magV * (radius - 3); + zLoc = z + vZ / magV * (radius - 3); + } + + yLoc = getSafeY(loc.getWorld(), Location.locToBlock(xLoc), Location.locToBlock(yLoc), Location.locToBlock(zLoc)); + if (yLoc == -1) + return null; + + return new Location(loc.getWorld(), Math.floor(xLoc) + 0.5, yLoc, Math.floor(zLoc) + 0.5, loc.getYaw(), loc.getPitch()); + } + + //these material IDs are acceptable for places to teleport player; breathable blocks and water + private static Set acceptableBlocks = new HashSet(Arrays.asList( + new Integer[] {0, 6, 8, 9, 37, 38, 39, 40, 50, 55, 59, 63, 64, 65, 66, 68, 69, 70, 71, 72, 75, 76, 77, 83, 93, 94} + )); + + //these material IDs are ones we don't want to drop the player onto + private static Set painfulBlocks = new HashSet(Arrays.asList( + new Integer[] {10, 11, 81} + )); + + // check if a particular spot consists of 2 breathable blocks over something relatively solid + private boolean isSafeSpot(World world, int X, int Y, int Z) + { + Integer below = (Integer)world.getBlockAt(X, Y - 1, Z).getTypeId(); + return (acceptableBlocks.contains((Integer)world.getBlockAt(X, Y, Z).getTypeId()) // target block breatheable + && acceptableBlocks.contains((Integer)world.getBlockAt(X, Y + 1, Z).getTypeId()) // above target block breathable + && !acceptableBlocks.contains(below) // below target block not breathable (probably solid) + && !painfulBlocks.contains(below) // below target block not something painful + ); + } + + // find closest safe Y position from the starting position + private double getSafeY(World world, int X, int Y, int Z) + { + // Expanding Y search method adapted from Acru's code in the Nether plugin + int limTop = 120, limBot = 1; + + for(int y1 = Y, y2 = Y; (y1 > limBot) || (y2 < limTop); y1--, y2++){ + // Look below. + if(y1 > limBot){ + if (isSafeSpot(world, X, y1, Z)) + return (double)y1; + } + + // Look above. + if(y2 < limTop && y2 != y1){ + if (isSafeSpot(world, X, y2, Z)) + return (double)y2; + } + } + + return -1.0; // no safe Y location?!?!? Must be a rare spot in a Nether world or something + } +} \ No newline at end of file diff --git a/src/com/wimbli/WorldBorder/Config.java b/src/com/wimbli/WorldBorder/Config.java new file mode 100644 index 0000000..200b3a0 --- /dev/null +++ b/src/com/wimbli/WorldBorder/Config.java @@ -0,0 +1,223 @@ +package com.wimbli.WorldBorder; + +import java.text.DecimalFormat; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.bukkit.util.config.Configuration; +import org.bukkit.util.config.ConfigurationNode; + +import com.nijiko.permissions.PermissionHandler; +import com.nijikokun.bukkit.Permissions.Permissions; + +public class Config +{ + // private stuff used within this class + private static WorldBorder plugin; + private static Configuration cfg = null; + private static PermissionHandler Permissions = null; + private static final Logger mcLog = Logger.getLogger("Minecraft"); + public static DecimalFormat coord = new DecimalFormat("0.0"); + public static final boolean DEBUG = false; + + // actual configuration values which can be changed + private static boolean shapeRound = false; + private static Map borders = new HashMap(); + private static String message; + + public static void setBorder(String world, BorderData border) + { + borders.put(world, border); + Log("Border set. " + BorderDescription(world)); + save(true); + } + public static void setBorder(String world, int radius, double x, double z) + { + setBorder(world, new BorderData(x, z, radius)); + } + + public static void removeBorder(String world) + { + borders.remove(world); + Log("Removed border for world \"" + world + "\"."); + save(true); + } + + public static void removeAllBorders() + { + borders.clear(); + Log("Removed all borders for all worlds."); + save(true); + } + + public static String BorderDescription(String world) + { + BorderData border = borders.get(world); + if (border == null) + return "No border was found for the world \"" + world + "\"."; + else + return "World \"" + world + "\" has border " + border.toString(); + } + + public static Set BorderDescriptions() + { + Set output = new HashSet(); + + Iterator world = borders.keySet().iterator(); + while(world.hasNext()) + { + output.add( BorderDescription((String)world.next()) ); + } + + return output; + } + + public static BorderData Border(String world) + { + return borders.get(world); + } + + public static void setMessage(String msg) + { + message = msg; + Log("Border message is now set to: " + msg); + save(true); + } + + public static String Message() + { + return message; + } + + public static void setShape(boolean round) + { + shapeRound = round; + Log("Set border shape to " + (round ? "round" : "square") + "."); + save(true); + } + + public static boolean ShapeRound() + { + return shapeRound; + } + + public static void loadPermissions(WorldBorder plugin) + { + if (Permissions != null || plugin == null) + return; + + Plugin test = plugin.getServer().getPluginManager().getPlugin("Permissions"); + + if (test != null) + { + Permissions = ((Permissions)test).getHandler(); + LogConfig("Will use plugin for permissions: "+((Permissions)test).getDescription().getFullName()); + } else { + LogConfig("Permissions plugin not found. Only Ops will have access to this plugin's commands."); + } + } + + public static boolean HasPermission(Player player, String request) + { + if (player == null) // console, always permitted + return true; + else if (player.isOp()) // Op, always permitted + return true; + else if (Permissions == null // Permissions plugin not available, or doesn't have permission + || !Permissions.permission(player, "worldborder." + request)) + { + player.sendMessage("You do not have sufficient permissions to do that."); + return false; + } + else + return true; + } + + public static void Log(Level lvl, String text) + { + String name = (plugin == null) ? "WorldBorder" : plugin.getDescription().getName(); + mcLog.log(lvl, String.format("[%s] %s", name, text)); + } + public static void Log(String text) + { + Log(Level.INFO, text); + } + public static void LogWarn(String text) + { + Log(Level.WARNING, text); + } + public static void LogConfig(String text) + { + Log(Level.INFO, "[CONFIG] " + text); + } + + public static void load(WorldBorder master, boolean logIt) + { // load config from file + plugin = master; + cfg = plugin.getConfiguration(); + + message = cfg.getString("message"); + shapeRound = cfg.getBoolean("round-border", false); + LogConfig("Using " + (shapeRound ? "round" : "square") + " border shape."); + + borders.clear(); + + Map worlds = cfg.getNodes("worlds"); + if (worlds != null) + { + Iterator world = worlds.entrySet().iterator(); + while(world.hasNext()) + { + Entry wdata = (Entry)world.next(); + String name = (String)wdata.getKey(); + ConfigurationNode bord = (ConfigurationNode)wdata.getValue(); + BorderData border = new BorderData(bord.getDouble("x", 0), bord.getDouble("z", 0), bord.getInt("radius", 0)); + borders.put(name, border); + LogConfig(BorderDescription(name)); + } + } + + if (message == null || message.isEmpty()) + { // store defaults + LogConfig("Configuration not present, creating new file."); + message = "You have reached the edge of this world."; + shapeRound = false; + save(false); + } + else if (logIt) + LogConfig("Configuration loaded."); + } + + public static void save(boolean logIt) + { // save config to file + if (cfg == null) return; + + cfg.setProperty("message", message); + cfg.setProperty("round-border", shapeRound); + + cfg.removeProperty("worlds"); + Iterator world = borders.entrySet().iterator(); + while(world.hasNext()) + { + Entry wdata = (Entry)world.next(); + String name = (String)wdata.getKey(); + BorderData bord = (BorderData)wdata.getValue(); + cfg.setProperty("worlds." + name + ".x", bord.getX()); + cfg.setProperty("worlds." + name + ".z", bord.getZ()); + cfg.setProperty("worlds." + name + ".radius", bord.getRadius()); + } + + cfg.save(); + + if (logIt) + LogConfig("Configuration saved."); + } +} diff --git a/src/com/wimbli/WorldBorder/WBCommand.java b/src/com/wimbli/WorldBorder/WBCommand.java new file mode 100644 index 0000000..a1eab5a --- /dev/null +++ b/src/com/wimbli/WorldBorder/WBCommand.java @@ -0,0 +1,314 @@ +package com.wimbli.WorldBorder; + +import java.util.Iterator; +import java.util.Set; + +import org.bukkit.ChatColor; +import org.bukkit.command.*; +import org.bukkit.entity.Player; +import org.bukkit.World; + +public class WBCommand implements CommandExecutor +{ + private WorldBorder plugin; + + public WBCommand (WorldBorder plugin) + { + this.plugin = plugin; + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] split) + { + Player player = (sender instanceof Player) ? (Player)sender : null; + + String cmd = ChatColor.AQUA + ((player == null) ? "wborder" : "/wborder"); + String cmdW = ChatColor.AQUA + ((player == null) ? "wborder " + ChatColor.GREEN + "" : "/wborder " + ChatColor.DARK_AQUA + "[world]") + ChatColor.AQUA; + + // "set" command from player or console, world specified + if (split.length == 5 && split[1].equalsIgnoreCase("set")) + { + if (!Config.HasPermission(player, "set")) return true; + + World world = sender.getServer().getWorld(split[0]); + if (world == null) + sender.sendMessage("The world you specified (\"" + split[0] + "\") could not be found on the server, but data for it will be stored anyway."); + + if(cmdSet(sender, split[0], split, 2) && player != null) + sender.sendMessage("Border has been set. " + Config.BorderDescription(split[0])); + } + + // "set" command from player, using current world, X and Z specified + else if (split.length == 4 && split[0].equalsIgnoreCase("set") && player != null) + { + if (!Config.HasPermission(player, "set")) return true; + + String world = player.getWorld().getName(); + + if (cmdSet(sender, world, split, 1)) + sender.sendMessage("Border has been set. " + Config.BorderDescription(world)); + } + + // "set" command from player, using current world, X and Z NOT specified + else if (split.length == 2 && split[0].equalsIgnoreCase("set") && player != null) + { + if (!Config.HasPermission(player, "set")) return true; + + String world = player.getWorld().getName(); + + double x = player.getLocation().getX(); + double z = player.getLocation().getZ(); + int radius; + try + { + radius = Integer.parseInt(split[1]); + } + catch(NumberFormatException ex) + { + sender.sendMessage(ChatColor.RED + "The radius value must be an integer."); + return true; + } + + Config.setBorder(world, radius, x, z); + sender.sendMessage("Border has been set. " + Config.BorderDescription(world)); + } + + // "radius" command from player or console, world specified + else if (split.length == 3 && split[1].equalsIgnoreCase("radius")) + { + if (!Config.HasPermission(player, "radius")) return true; + + String world = split[0]; + + BorderData border = Config.Border(world); + if (border == null) + { + sender.sendMessage(ChatColor.RED + "That world (\"" + world + "\") must first have a border set normally."); + return true; + } + + double x = border.getX(); + double z = border.getZ(); + int radius; + try + { + radius = Integer.parseInt(split[1]); + } + catch(NumberFormatException ex) + { + sender.sendMessage(ChatColor.RED + "The radius value must be an integer."); + return true; + } + + Config.setBorder(world, radius, x, z); + sender.sendMessage("Radius has been set. " + Config.BorderDescription(world)); + } + + // "radius" command from player, using current world + else if (split.length == 2 && split[0].equalsIgnoreCase("radius") && player != null) + { + if (!Config.HasPermission(player, "radius")) return true; + + String world = player.getWorld().getName(); + + BorderData border = Config.Border(world); + if (border == null) + { + sender.sendMessage(ChatColor.RED + "This world (\"" + world + "\") must first have a border set normally."); + return true; + } + + double x = border.getX(); + double z = border.getZ(); + int radius; + try + { + radius = Integer.parseInt(split[1]); + } + catch(NumberFormatException ex) + { + sender.sendMessage(ChatColor.RED + "The radius value must be an integer."); + return true; + } + + Config.setBorder(world, radius, x, z); + sender.sendMessage("Radius has been set. " + Config.BorderDescription(world)); + } + + // "clear" command from player or console, world specified + else if (split.length == 2 && split[1].equalsIgnoreCase("clear")) + { + if (!Config.HasPermission(player, "clear")) return true; + + String world = split[0]; + BorderData border = Config.Border(world); + if (border == null) + { + sender.sendMessage("The world you specified (\"" + world + "\") does not have a border set."); + return true; + } + + Config.removeBorder(world); + + if (player != null) + sender.sendMessage("Border cleared for world \"" + world + "\"."); + } + + // "clear" command from player, using current world + else if (split.length == 1 && split[0].equalsIgnoreCase("clear") && player != null) + { + if (!Config.HasPermission(player, "clear")) return true; + + String world = player.getWorld().getName(); + BorderData border = Config.Border(world); + if (border == null) + { + sender.sendMessage(ChatColor.RED + "Your current world (\"" + world + "\") does not have a border set."); + return true; + } + + Config.removeBorder(world); + sender.sendMessage("Border cleared for world \"" + world + "\"."); + } + + // "clear all" command from player or console + else if (split.length == 2 && split[0].equalsIgnoreCase("clear") && split[1].equalsIgnoreCase("all")) + { + if (!Config.HasPermission(player, "clear")) return true; + + Config.removeAllBorders(); + + if (player != null) + sender.sendMessage("All borders cleared for all worlds."); + } + + // "list" command from player or console + else if (split.length == 1 && split[0].equalsIgnoreCase("list")) + { + if (!Config.HasPermission(player, "list")) return true; + + sender.sendMessage("Border shape for all worlds is \"" + (Config.ShapeRound() ? "round" : "square") + "\"."); + + Set list = Config.BorderDescriptions(); + + if (list.isEmpty()) + { + sender.sendMessage("There are no borders currently set."); + return true; + } + + Iterator listItem = list.iterator(); + while(listItem.hasNext()) + { + sender.sendMessage( (String)listItem.next() ); + } + } + + // "shape" command from player or console + else if (split.length == 2 && split[0].equalsIgnoreCase("shape")) + { + if (!Config.HasPermission(player, "shape")) return true; + + if (split[1].equalsIgnoreCase("square")) + Config.setShape(false); + else if (split[1].equalsIgnoreCase("round")) + Config.setShape(true); + else + { + sender.sendMessage("You must specify a shape of \"round\" or \"square\"."); + return true; + } + + if (player != null) + sender.sendMessage("Border shape for all worlds is now set to \"" + (Config.ShapeRound() ? "round" : "square") + "\"."); + } + + // "getmsg" command from player or console + else if (split.length == 1 && split[0].equalsIgnoreCase("getmsg")) + { + if (!Config.HasPermission(player, "getmsg")) return true; + + sender.sendMessage("Border message is currently set to:"); + sender.sendMessage(ChatColor.RED + Config.Message()); + } + + // "setmsg" command from player or console + else if (split.length >= 2 && split[0].equalsIgnoreCase("setmsg")) + { + if (!Config.HasPermission(player, "setmsg")) return true; + + String message = ""; + for(int i = 1; i < split.length; i++) + { + if (i != 1) + message += ' '; + message += split[i]; + } + + Config.setMessage(message); + + if (player != null) + { + sender.sendMessage("Border message is now set to:"); + sender.sendMessage(ChatColor.RED + Config.Message()); + } + } + + // "reload" command from player or console + else if (split.length == 1 && split[0].equalsIgnoreCase("reload")) + { + if (!Config.HasPermission(player, "reload")) return true; + + if (player != null) + Config.Log("Reloading config file at the command of player \"" + player.getName() + "\"."); + + Config.load(plugin, true); + + if (player != null) + sender.sendMessage("WorldBorder configuration reloaded."); + } + + // we couldn't decipher any known commands, so show help + else + { + if (!Config.HasPermission(player, "help")) return true; + + sender.sendMessage(ChatColor.WHITE + plugin.getDescription().getFullName() + " - commands (" + (player != null ? ChatColor.DARK_AQUA + "[optional] " : "") + ChatColor.GREEN + "" + ChatColor.WHITE + "):"); + if (player != null) + sender.sendMessage(cmd+" set " + ChatColor.GREEN + "" + ChatColor.WHITE + " - set world border, centered on you."); + sender.sendMessage(cmdW+" set " + ChatColor.GREEN + " " + ChatColor.WHITE + " - set world border."); + sender.sendMessage(cmdW+" radius " + ChatColor.GREEN + "" + ChatColor.WHITE + " - change border radius for this world."); + sender.sendMessage(cmdW+" clear" + ChatColor.WHITE + " - remove border for this world."); + sender.sendMessage(cmd+" clear all" + ChatColor.WHITE + " - remove border for all worlds."); + sender.sendMessage(cmd+" list" + ChatColor.WHITE + " - show border information for all worlds."); + sender.sendMessage(cmd+" shape " + ChatColor.GREEN + "" + ChatColor.WHITE + " - set the border shape."); + sender.sendMessage(cmd+" getmsg" + ChatColor.WHITE + " - display border message."); + sender.sendMessage(cmd+" setmsg " + ChatColor.GREEN + "" + ChatColor.WHITE + " - set border message."); + if (player != null) + sender.sendMessage(cmd+" reload" + ChatColor.WHITE + " - re-load data from config.yml."); + } + + return true; + } + + + private boolean cmdSet(CommandSender sender, String world, String[] data, int offset) + { + int radius; + double x, z; + try + { + radius = Integer.parseInt(data[offset]); + x = Double.parseDouble(data[offset+1]); + z = Double.parseDouble(data[offset+2]); + } + catch(NumberFormatException ex) + { + sender.sendMessage(ChatColor.RED + "The radius value must be an integer and the x and z values must be numerical."); + return false; + } + + Config.setBorder(world, radius, x, z); + return true; + } +} \ No newline at end of file diff --git a/src/com/wimbli/WorldBorder/WBPlayerListener.java b/src/com/wimbli/WorldBorder/WBPlayerListener.java new file mode 100644 index 0000000..15e9047 --- /dev/null +++ b/src/com/wimbli/WorldBorder/WBPlayerListener.java @@ -0,0 +1,147 @@ +package com.wimbli.WorldBorder; + +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.event.player.*; +import org.bukkit.Location; +import org.bukkit.util.Vector; +import org.bukkit.World; + +public class WBPlayerListener extends PlayerListener +{ + @Override + public void onPlayerJoin(PlayerJoinEvent event) + { + Player player = event.getPlayer(); + if (player == null) return; + Location loc = player.getLocation(); + if (loc == null) return; + World world = loc.getWorld(); + if (world == null) return; + BorderData border = Config.Border(world.getName()); + if (border == null) return; + + if (border.insideBorder(loc.getX(), loc.getZ(), Config.ShapeRound())) + return; + + if (Config.DEBUG) + { + Config.LogWarn("Border crossing. Border " + border.toString()); + Config.LogWarn("Player position X: " + Config.coord.format(loc.getX()) + " Y: " + Config.coord.format(loc.getY()) + " Z: " + Config.coord.format(loc.getZ())); + } + + Location newLoc = border.correctedPosition(loc, Config.ShapeRound()); + + // it's remotely possible (such as in the Nether) a suitable location isn't available, in which case... + if (newLoc == null) + { + if (Config.DEBUG) + Config.LogWarn("Target new location unviable, using spawn."); + newLoc = player.getServer().getWorlds().get(0).getSpawnLocation(); + } + + if (Config.DEBUG) + Config.LogWarn("New position X: " + Config.coord.format(newLoc.getX()) + " Y: " + Config.coord.format(newLoc.getY()) + " Z: " + Config.coord.format(newLoc.getZ())); + + player.sendMessage(ChatColor.RED + Config.Message()); + player.teleport(newLoc); + } + + @Override + public void onPlayerMove(PlayerMoveEvent event) + { + if (event.isCancelled()) return; + + Player player = event.getPlayer(); + if (player == null) return; + Location loc = event.getTo(); + if (loc == null) return; + World world = loc.getWorld(); + if (world == null) return; + BorderData border = Config.Border(world.getName()); + if (border == null) return; + + if (border.insideBorder(loc.getX(), loc.getZ(), Config.ShapeRound())) + return; + + if (Config.DEBUG) + { + Config.LogWarn("Border crossing. Border " + border.toString()); + Config.LogWarn("Player position X: " + Config.coord.format(loc.getX()) + " Y: " + Config.coord.format(loc.getY()) + " Z: " + Config.coord.format(loc.getZ())); + } + + Location newLoc = border.correctedPosition(loc, Config.ShapeRound()); + + if (newLoc == null) + { + if (Config.DEBUG) + Config.LogWarn("Target new location unviable, using spawn."); + newLoc = player.getServer().getWorlds().get(0).getSpawnLocation(); + } + + if (Config.DEBUG) + Config.LogWarn("New position X: " + Config.coord.format(newLoc.getX()) + " Y: " + Config.coord.format(newLoc.getY()) + " Z: " + Config.coord.format(newLoc.getZ())); + + player.sendMessage(ChatColor.RED + Config.Message()); + + if (!player.isInsideVehicle()) + player.teleport(newLoc); + else + { + newLoc.setY(newLoc.getY() + 1); + player.getVehicle().setVelocity(new Vector(0, 0, 0)); + player.getVehicle().teleport(newLoc); + } + + event.setTo(newLoc); + } + + @Override + public void onPlayerTeleport(PlayerTeleportEvent event) + { + if (event.isCancelled()) return; + + Player player = event.getPlayer(); + if (player == null) return; + Location loc = event.getTo(); + if (loc == null) return; + World world = loc.getWorld(); + if (world == null) return; + BorderData border = Config.Border(world.getName()); + if (border == null) return; + + if (border.insideBorder(loc.getX(), loc.getZ(), Config.ShapeRound())) + return; + + if (Config.DEBUG) + { + Config.LogWarn("Border crossing. Border " + border.toString()); + Config.LogWarn("Player position X: " + Config.coord.format(loc.getX()) + " Y: " + Config.coord.format(loc.getY()) + " Z: " + Config.coord.format(loc.getZ())); + } + + Location newLoc = border.correctedPosition(loc, Config.ShapeRound()); + + if (newLoc == null) + { + if (Config.DEBUG) + Config.LogWarn("Target new location unviable, using spawn."); + newLoc = player.getServer().getWorlds().get(0).getSpawnLocation(); + } + + if (Config.DEBUG) + Config.LogWarn("New position X: " + Config.coord.format(newLoc.getX()) + " Y: " + Config.coord.format(newLoc.getY()) + " Z: " + Config.coord.format(newLoc.getZ())); + + player.sendMessage(ChatColor.RED + Config.Message()); + + if (!player.isInsideVehicle()) + player.teleport(newLoc); + else + { + newLoc.setY(newLoc.getY() + 1); + player.getVehicle().setVelocity(new Vector(0, 0, 0)); + player.getVehicle().teleport(newLoc); + } + + event.setTo(newLoc); + } +} diff --git a/src/com/wimbli/WorldBorder/WorldBorder.java b/src/com/wimbli/WorldBorder/WorldBorder.java new file mode 100644 index 0000000..55af08e --- /dev/null +++ b/src/com/wimbli/WorldBorder/WorldBorder.java @@ -0,0 +1,44 @@ +package com.wimbli.WorldBorder; + +import org.bukkit.event.Event; +import org.bukkit.Location; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.plugin.PluginDescriptionFile; +import org.bukkit.plugin.PluginManager; + +/** + * + * @author Brett Flannigan + */ +public class WorldBorder extends JavaPlugin +{ + WBPlayerListener playerListener = new WBPlayerListener(); + + public void onEnable() + { + PluginDescriptionFile desc = this.getDescription(); + System.out.println( desc.getName() + " version " + desc.getVersion() + " loading" ); + + // Load (or create new) config file, and connect to Permissions if it's available + Config.load(this, false); + Config.loadPermissions(this); + + // Well I for one find this info useful, so... + Location spawn = getServer().getWorlds().get(0).getSpawnLocation(); + System.out.println("For reference, the main world's spawn location is at X: " + Config.coord.format(spawn.getX()) + " Y: " + Config.coord.format(spawn.getY()) + " Z: " + Config.coord.format(spawn.getZ())); + + PluginManager pm = this.getServer().getPluginManager(); + pm.registerEvent(Event.Type.PLAYER_MOVE, this.playerListener, Event.Priority.High, this); + pm.registerEvent(Event.Type.PLAYER_TELEPORT, this.playerListener, Event.Priority.High, this); + pm.registerEvent(Event.Type.PLAYER_JOIN, this.playerListener, Event.Priority.High, this); + + // our one real command, though it does also have aliases "wb" and "worldborder" + getCommand("wborder").setExecutor(new WBCommand(this)); + } + + public void onDisable() + { + PluginDescriptionFile desc = this.getDescription(); + System.out.println( desc.getName() + " version " + desc.getVersion() + " shutting down" ); + } +} diff --git a/src/plugin.yml b/src/plugin.yml new file mode 100644 index 0000000..aa05bcb --- /dev/null +++ b/src/plugin.yml @@ -0,0 +1,20 @@ +name: WorldBorder +author: Brettflan +description: Limit the size of your worlds with a border, round or square. +version: 0.9 +main: com.wimbli.WorldBorder.WorldBorder +commands: + wborder: + description: Primary command for WorldBorder. + aliases: [worldborder, wb] + usage: | + / - list available commands (show help). + / set - set world border, centered on you. + / [world] set - set world border. + / radius - change border radius for this world. + / [world] clear - remove border for this world. + / clear all - remove border for all worlds. + / list - show border information for all worlds. + / shape - set the border shape. + / getmsg - display border message. + / setmsg - set border message. \ No newline at end of file