New fill command, which generates all missing chunks within a world's border along with a configurable buffer beyond the border; can be run at different speeds as well, to allow for running on a server with players on it or to have it finish as quickly as possible

Support for newly available built-in Bukkit "superperms" permission system; uses the same nodes as for the Permissions plugin
Support for colors in console messages
Knockback distance is now required to be at least 1.0
A couple of other tweaks
This commit is contained in:
Brettflan 2011-07-19 00:12:14 -05:00
parent c45c48be5c
commit e2e1b60e2a
5 changed files with 334 additions and 24 deletions

View File

@ -45,6 +45,11 @@ public class BorderData
this.DefiniteSquare = Math.sqrt(.5 * this.radiusSquared);
}
public BorderData copy()
{
return new BorderData(x, z, radius, shapeRound);
}
public double getX()
{
return x;
@ -178,12 +183,12 @@ public class BorderData
}
//these material IDs are acceptable for places to teleport player; breathable blocks and water
private static LinkedHashSet<Integer> safeOpenBlocks = new LinkedHashSet<Integer>(Arrays.asList(
private static final LinkedHashSet<Integer> safeOpenBlocks = new LinkedHashSet<Integer>(Arrays.asList(
new Integer[] {0, 6, 8, 9, 27, 28, 31, 32, 37, 38, 39, 40, 50, 55, 59, 63, 64, 65, 66, 68, 69, 70, 71, 72, 75, 76, 77, 78, 83, 90, 93, 94}
));
//these material IDs are ones we don't want to drop the player onto, like cactus or lava or fire
private static LinkedHashSet<Integer> painfulBlocks = new LinkedHashSet<Integer>(Arrays.asList(
private static final LinkedHashSet<Integer> painfulBlocks = new LinkedHashSet<Integer>(Arrays.asList(
new Integer[] {10, 11, 51, 81}
));
@ -198,7 +203,7 @@ public class BorderData
);
}
static final private int limTop = 120, limBot = 1;
private static final int limTop = 120, limBot = 1;
// find closest safe Y position from the starting position
private double getSafeY(World world, int X, int Y, int Z)

View File

@ -16,6 +16,9 @@ import org.bukkit.plugin.Plugin;
import org.bukkit.util.config.Configuration;
import org.bukkit.util.config.ConfigurationNode;
import org.bukkit.craftbukkit.command.ColouredConsoleSender;
import org.bukkit.craftbukkit.CraftServer;
import org.anjocaido.groupmanager.GroupManager;
import com.nijiko.permissions.PermissionHandler;
import com.nijikokun.bukkit.Permissions.Permissions;
@ -31,8 +34,11 @@ public class Config
private static final Logger mcLog = Logger.getLogger("Minecraft");
public static DecimalFormat coord = new DecimalFormat("0.0");
private static int borderTask = -1;
public static WorldFillTask fillTask = null;
public static Set<String> movedPlayers = Collections.synchronizedSet(new HashSet<String>());
private static Runtime rt = Runtime.getRuntime();
private static ColouredConsoleSender console = null;
// actual configuration values which can be changed
private static boolean shapeRound = false;
private static Map<String, BorderData> borders = Collections.synchronizedMap(new LinkedHashMap<String, BorderData>());
@ -41,13 +47,13 @@ public class Config
private static double knockBack = 3.0;
private static int timerTicks = 4;
/* // for monitoring plugin efficiency
public static long timeUsed = 0;
// for monitoring plugin efficiency
// public static long timeUsed = 0;
public static long Now()
{
return Calendar.getInstance().getTimeInMillis();
return System.currentTimeMillis();
}
*/
public static void setBorder(String world, BorderData border)
{
@ -191,6 +197,39 @@ public class Config
}
public static void StopFillTask()
{
if (fillTask != null && fillTask.valid())
fillTask.cancel();
}
public static void StoreFillTask()
{
save(false, true);
}
public static void UnStoreFillTask()
{
save(false);
}
public static void RestoreFillTask(String world, int fillDistance, int chunksPerRun, int tickFrequency, int x, int z, int length, int total)
{
fillTask = new WorldFillTask(plugin.getServer(), null, world, fillDistance, chunksPerRun, tickFrequency);
if (fillTask.valid())
{
fillTask.continueProgress(x, z, length, total);
int task = plugin.getServer().getScheduler().scheduleSyncRepeatingTask(plugin, fillTask, 20, tickFrequency);
fillTask.setTaskID(task);
}
}
public static int AvailableMemory()
{
return (int)((rt.maxMemory() - rt.totalMemory() + rt.freeMemory()) / 1024L / 1024L);
}
public static void loadPermissions(WorldBorder plugin)
{
if (GroupPlugin != null || Permissions != null || plugin == null)
@ -224,29 +263,36 @@ public class Config
return true;
else if (player.isOp()) // Op, always permitted
return true;
else if (GroupPlugin != null) // GroupManager plugin available
if (GroupPlugin != null) // GroupManager plugin available
{
if (GroupPlugin.getWorldsHolder().getWorldPermissions(player).has(player, "worldborder." + request))
return true;
player.sendMessage("You do not have sufficient permissions to do that.");
return false;
}
else if (Permissions != null) // Permissions plugin available
{
if (Permissions.permission(player, "worldborder." + request))
return true;
player.sendMessage("You do not have sufficient permissions to do that.");
return false;
}
else
if (player.hasPermission("worldborder." + request)) // built-in Bukkit superperms
return true;
player.sendMessage("You do not have sufficient permissions.");
return false;
}
private static final String logName = "WorldBorder";
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));
if (console != null)
{
if (lvl != Level.INFO)
text = "[" + lvl.getLocalizedName() + "] " + text;
console.sendMessage(String.format("[%s] %s", logName, text));
}
else
mcLog.log(lvl, String.format("[%s] %s", logName, text));
}
public static void Log(String text)
{
@ -265,7 +311,8 @@ public class Config
public static void load(WorldBorder master, boolean logIt)
{ // load config from file
plugin = master;
cfg = plugin.getConfiguration();
console = new ColouredConsoleSender((CraftServer)plugin.getServer());
cfg = plugin.getConfiguration();
int cfgVersion = cfg.getInt("cfg-version", 1);
@ -311,6 +358,22 @@ public class Config
}
}
// if we have an unfinished fill task stored from a previous run, load it up
ConfigurationNode storedFillTask = cfg.getNode("fillTask");
if (storedFillTask != null)
{
String worldName = storedFillTask.getString("world");
int fillDistance = storedFillTask.getInt("fillDistance", 176);
int chunksPerRun = storedFillTask.getInt("chunksPerRun", 5);
int tickFrequency = storedFillTask.getInt("tickFrequency", 20);
int fillX = storedFillTask.getInt("x", 0);
int fillZ = storedFillTask.getInt("z", 0);
int fillLength = storedFillTask.getInt("length", 0);
int fillTotal = storedFillTask.getInt("total", 0);
RestoreFillTask(worldName, fillDistance, chunksPerRun, tickFrequency, fillX, fillZ, fillLength, fillTotal);
save(false);
}
if (logIt)
LogConfig("Configuration loaded.");
@ -319,6 +382,10 @@ public class Config
}
public static void save(boolean logIt)
{
save(logIt, false);
}
public static void save(boolean logIt, boolean storeFillTask)
{ // save config to file
if (cfg == null) return;
@ -345,6 +412,20 @@ public class Config
cfg.setProperty("worlds." + name.replace(".", "¨") + ".shape-round", bord.getShape());
}
if (storeFillTask && fillTask != null && fillTask.valid())
{
cfg.setProperty("fillTask.world", fillTask.refWorld());
cfg.setProperty("fillTask.fillDistance", fillTask.refFillDistance());
cfg.setProperty("fillTask.chunksPerRun", fillTask.refChunksPerRun());
cfg.setProperty("fillTask.tickFrequency", fillTask.refTickFrequency());
cfg.setProperty("fillTask.x", fillTask.refX());
cfg.setProperty("fillTask.z", fillTask.refZ());
cfg.setProperty("fillTask.length", fillTask.refLength());
cfg.setProperty("fillTask.total", fillTask.refTotal());
}
else
cfg.removeProperty("fillTask");
cfg.save();
if (logIt)

View File

@ -32,7 +32,7 @@ public class WBCommand implements CommandExecutor
Player player = (sender instanceof Player) ? (Player)sender : null;
String cmd = clrCmd + ((player == null) ? "wb" : "/wb");
String cmdW = clrCmd + ((player == null) ? "wb " + clrReq + "<world>" : "/wb " + clrOpt + "[world]") + clrCmd;
String cmdW = clrCmd + ((player == null) ? "wb " + clrReq + "<world>" : "/wb " + clrOpt + "[world]") + clrCmd;
// "set" command from player or console, world specified
if (split.length == 5 && split[1].equalsIgnoreCase("set"))
@ -305,13 +305,13 @@ public class WBCommand implements CommandExecutor
}
catch(NumberFormatException ex)
{
sender.sendMessage(clrErr + "The knockback must be a decimal value above 0.");
sender.sendMessage(clrErr + "The knockback must be a decimal value of at least 1.0.");
return true;
}
if (numBlocks <= 0.0)
if (numBlocks < 1.0)
{
sender.sendMessage(clrErr + "The knockback must be a decimal value above 0.");
sender.sendMessage(clrErr + "The knockback must be a decimal value of at least 1.0.");
return true;
}
@ -399,6 +399,60 @@ public class WBCommand implements CommandExecutor
sender.sendMessage("Border shape for world \"" + world + "\" is now set to \"" + (shape == null ? "default" : (shape.booleanValue() ? "round" : "square")) + "\".");
}
// "fill" command from player or console, world specified
else if (split.length >= 2 && split[1].equalsIgnoreCase("fill"))
{
if (!Config.HasPermission(player, "fill")) return true;
boolean cancel = false, confirm = false, pause = false;
String pad = "", frequency = "";
if (split.length >= 3)
{
cancel = split[2].equalsIgnoreCase("cancel");
confirm = split[2].equalsIgnoreCase("confirm");
pause = split[2].equalsIgnoreCase("pause");
if (!cancel && !confirm && !pause)
frequency = split[2];
}
if (split.length >= 4)
pad = split[3];
String world = split[0];
cmdFill(sender, player, world, confirm, cancel, pause, pad, frequency);
}
// "fill" command from player (or from console solely if using cancel or confirm), using current world
else if (split.length >= 1 && split[0].equalsIgnoreCase("fill"))
{
if (!Config.HasPermission(player, "fill")) return true;
boolean cancel = false, confirm = false, pause = false;
String pad = "", frequency = "";
if (split.length >= 2)
{
cancel = split[1].equalsIgnoreCase("cancel");
confirm = split[1].equalsIgnoreCase("confirm");
pause = split[1].equalsIgnoreCase("pause");
if (!cancel && !confirm && !pause)
frequency = split[1];
}
if (split.length >= 3)
pad = split[2];
String world = "";
if (player != null)
world = player.getWorld().getName();
if (!cancel && !confirm && !pause && world.isEmpty())
{
sender.sendMessage("You must specify a world! Example: " + cmdW+" fill " + clrOpt + "[freq] [pad]");
return true;
}
cmdFill(sender, player, world, confirm, cancel, pause, pad, frequency);
}
// we couldn't decipher any known commands, so show help
else
{
@ -418,7 +472,7 @@ public class WBCommand implements CommandExecutor
page = 1;
}
sender.sendMessage(clrHead + plugin.getDescription().getFullName() + " - commands (" + (player != null ? clrOpt + "[optional] " : "") + clrReq + "<required>" + clrHead + ")" + (page > 0 ? " " + page + "/2" : "") + ":");
sender.sendMessage(clrHead + plugin.getDescription().getFullName() + " - commands (" + clrReq + "<required> " + clrOpt + "[optional]" + clrHead + ")" + (page > 0 ? " " + page + "/2" : "") + ":");
if (page == 0 || page == 1)
{
@ -437,6 +491,7 @@ public class WBCommand implements CommandExecutor
if (page == 0 || page == 2)
{
sender.sendMessage(cmdW+" fill " + clrOpt + "[freq] [pad]" + clrDesc + " - generate world out to border.");
sender.sendMessage(cmd+" wshape " + ((player == null) ? clrReq + "<world>" : clrOpt + "[world]") + clrReq + " <round|square|default>" + clrDesc + " - shape override.");
sender.sendMessage(cmd+" getmsg" + clrDesc + " - display border message.");
sender.sendMessage(cmd+" setmsg " + clrReq + "<text>" + clrDesc + " - set border message.");
@ -471,4 +526,110 @@ public class WBCommand implements CommandExecutor
Config.setBorder(world, radius, x, z);
return true;
}
private String fillWorld = "";
private int fillPadding = 16 * 11;
private int fillFrequency = 20;
private void fillDefaults()
{
fillWorld = "";
fillFrequency = 20;
// with "view-distance=10" in server.properties 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); thus:
fillPadding = 16 * 11;
}
private boolean cmdFill(CommandSender sender, Player player, String world, boolean confirm, boolean cancel, boolean pause, String pad, String frequency)
{
if (cancel)
{
sender.sendMessage(clrHead + "Cancelling the world map generation task.");
fillDefaults();
Config.StopFillTask();
return true;
}
if (pause)
{
if (Config.fillTask == null || !Config.fillTask.valid())
{
sender.sendMessage(clrHead + "The world map generation task is not currently running.");
return true;
}
Config.fillTask.pause();
sender.sendMessage(clrHead + "The world map generation task is now " + (Config.fillTask.isPaused() ? "" : "un") + "paused.");
return true;
}
if (Config.fillTask != null && Config.fillTask.valid())
{
sender.sendMessage(clrHead + "The world map generation task is already running.");
return true;
}
// set padding and/or delay if those were specified
try
{
if (!pad.isEmpty())
fillPadding = Math.abs(Integer.parseInt(pad));
if (!frequency.isEmpty())
fillFrequency = Math.abs(Integer.parseInt(frequency));
}
catch(NumberFormatException ex)
{
sender.sendMessage(clrErr + "The frequency and padding values must be integers.");
return false;
}
// set world if it was specified
if (!world.isEmpty())
fillWorld = world;
if (confirm)
{ // command confirmed, go ahead with it
if (fillWorld.isEmpty())
{
sender.sendMessage(clrErr + "You must first use this command successfully without confirming.");
return false;
}
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.fillTask = new WorldFillTask(plugin.getServer(), player, fillWorld, fillPadding, repeats, ticks);
if (Config.fillTask.valid())
{
int task = plugin.getServer().getScheduler().scheduleSyncRepeatingTask(plugin, Config.fillTask, ticks, ticks);
Config.fillTask.setTaskID(task);
sender.sendMessage("WorldBorder map generation task started.");
}
else
sender.sendMessage(clrErr + "The world map generation task failed to start.");
fillDefaults();
}
else
{
if (fillWorld.isEmpty())
{
sender.sendMessage(clrErr + "You must first specify a valid world.");
return false;
}
String cmd = clrCmd + ((player == null) ? "wb" : "/wb");
sender.sendMessage(clrHead + "World generation task is ready for world \"" + fillWorld + "\", padding the map out to " + fillPadding + " blocks beyond the border (default " + (16 * 11) + "), and the task will try to process up to " + fillFrequency + " chunks per second (default 20).");
sender.sendMessage(clrHead + "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(clrDesc + "You should now use " + cmd + " fill confirm" + clrDesc + " to start the process.");
sender.sendMessage(clrDesc + "You can cancel at any time with " + cmd + " fill cancel" + clrDesc + ", or pause/unpause with " + cmd + " fill pause" + clrDesc + ".");
}
return true;
}
}

View File

@ -40,6 +40,8 @@ public class WorldBorder extends JavaPlugin
PluginDescriptionFile desc = this.getDescription();
System.out.println( desc.getName() + " version " + desc.getVersion() + " shutting down" );
Config.StopBorderTimer();
Config.StoreFillTask();
Config.StopFillTask();
}
// for other plugins to hook into

View File

@ -1,7 +1,7 @@
name: WorldBorder
author: Brettflan
description: Efficient, feature-rich plugin for limiting the size of your worlds.
version: 1.2.3
version: 1.3.0
main: com.wimbli.WorldBorder.WorldBorder
commands:
wborder:
@ -20,4 +20,65 @@ commands:
/<command> setmsg <text> - set border message.
/<command> knockback <distance> - how far to move the player back.
/<command> delay <amount> - time between border checks.
/<command> wshape [world] <round|square|default> - override shape.
/<command> wshape [world] <round|square|default> - override shape.
/<command> [world] fill [freq] [pad] - generate world out to border.
permissions:
worldborder.*:
description: Grants all WorldBorder permissions
children:
worldborder.set: true
worldborder.radius: true
worldborder.clear: true
worldborder.list: true
worldborder.shape: true
worldborder.getmsg: true
worldborder.setmsg: true
worldborder.reload: true
worldborder.debug: true
worldborder.knockback: true
worldborder.delay: true
worldborder.wshape: true
worldborder.fill: true
worldborder.help: true
worldborder.set:
description: Can set borders for any world
default: op
worldborder.radius:
description: Can set the radius of an existing border
default: op
worldborder.clear:
description: Can remove any border
default: op
worldborder.list:
description: Can view a list of all borders
default: op
worldborder.shape:
description: Can set the default shape (round or square) for all borders
default: op
worldborder.getmsg:
description: Can view the border crossing message
default: op
worldborder.setmsg:
description: Can set the border crossing message
default: op
worldborder.reload:
description: Can force the plugin to reload from the config file
default: op
worldborder.debug:
description: Can enable/disable debug output to console
default: op
worldborder.knockback:
description: Can set the knockback distance for border crossings
default: op
worldborder.delay:
description: Can set the frequency at which the plugin checks for border crossings
default: op
worldborder.wshape:
description: Can set an overriding border shape for a single world
default: op
worldborder.fill:
description: Can fill in (generate) any missing map chunks out to the border
default: op
worldborder.help:
description: Can view the command reference help pages
default: op