Reformated + Update to support 1.15+

This commit is contained in:
PryPurity 2020-06-21 04:44:35 -05:00
parent 44f388f3ba
commit 35f4af67c2
51 changed files with 4520 additions and 5227 deletions

191
pom.xml
View File

@ -1,100 +1,101 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.wimbli.WorldBorder</groupId>
<artifactId>WorldBorder</artifactId>
<version>1.9.10 (beta)</version>
<name>WorldBorder</name>
<url>https://github.com/Brettflan/WorldBorder</url>
<issueManagement>
<system>GitHub</system>
<url>https://github.com/Brettflan/WorldBorder/issues</url>
</issueManagement>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <properties>
<modelVersion>4.0.0</modelVersion> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<groupId>com.wimbli.WorldBorder</groupId> </properties>
<artifactId>WorldBorder</artifactId>
<version>1.9.10 (beta)</version>
<name>WorldBorder</name>
<url>https://github.com/Brettflan/WorldBorder</url>
<issueManagement>
<system>GitHub</system>
<url>https://github.com/Brettflan/WorldBorder/issues</url>
</issueManagement>
<properties> <repositories>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <repository>
</properties> <id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/groups/public/</url>
</repository>
<repository>
<id>dynmap-repo</id>
<url>https://repo.mikeprimm.com/</url>
</repository>
<repository>
<id>papermc</id>
<url>https://papermc.io/repo/repository/maven-public/</url>
</repository>
</repositories>
<repositories> <dependencies>
<repository> <!--Spigot-API-->
<id>spigot-repo</id> <dependency>
<url>https://hub.spigotmc.org/nexus/content/groups/public/</url> <groupId>org.spigotmc</groupId>
</repository> <artifactId>spigot-api</artifactId>
<repository> <version>1.15.2-R0.1-SNAPSHOT</version>
<id>dynmap-repo</id> <scope>provided</scope>
<url>https://repo.mikeprimm.com/</url> </dependency>
</repository> <!--Bukkit API-->
<repository> <dependency>
<id>papermc</id> <groupId>org.bukkit</groupId>
<url>https://papermc.io/repo/repository/maven-public/</url> <artifactId>bukkit</artifactId>
</repository> <version>1.15.2-R0.1-SNAPSHOT</version>
</repositories> <scope>provided</scope>
</dependency>
<!--Dynmap API-->
<dependency>
<groupId>us.dynmap</groupId>
<artifactId>dynmap-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.papermc</groupId>
<artifactId>paperlib</artifactId>
<version>1.0.2</version>
<scope>compile</scope>
</dependency>
</dependencies>
<dependencies> <build>
<!--Spigot-API--> <defaultGoal>clean install</defaultGoal>
<dependency> <finalName>${project.artifactId}</finalName>
<groupId>org.spigotmc</groupId> <plugins>
<artifactId>spigot-api</artifactId> <plugin>
<version>1.14-R0.1-SNAPSHOT</version> <groupId>org.apache.maven.plugins</groupId>
<scope>provided</scope> <artifactId>maven-compiler-plugin</artifactId>
</dependency> <version>3.8.0</version>
<!--Bukkit API--> <configuration>
<dependency> <source>1.8</source>
<groupId>org.bukkit</groupId> <target>1.8</target>
<artifactId>bukkit</artifactId> </configuration>
<version>1.14-R0.1-SNAPSHOT</version> </plugin>
<scope>provided</scope> <plugin>
</dependency> <groupId>org.apache.maven.plugins</groupId>
<!--Dynmap API--> <artifactId>maven-shade-plugin</artifactId>
<dependency> <version>3.1.1</version>
<groupId>us.dynmap</groupId> <configuration>
<artifactId>dynmap-api</artifactId> <dependencyReducedPomLocation>${project.build.directory}/dependency-reduced-pom.xml
<version>2.5</version> </dependencyReducedPomLocation>
<scope>provided</scope> <relocations>
</dependency> <relocation>
<dependency> <pattern>io.papermc.lib</pattern>
<groupId>io.papermc</groupId> <shadedPattern>com.wimbli.WorldBorder.paperlib</shadedPattern> <!-- Replace this -->
<artifactId>paperlib</artifactId> </relocation>
<version>1.0.2</version> </relocations>
<scope>compile</scope> </configuration>
</dependency> <executions>
</dependencies> <execution>
<phase>package</phase>
<build> <goals>
<defaultGoal>clean install</defaultGoal> <goal>shade</goal>
<finalName>${project.artifactId}</finalName> </goals>
<plugins> </execution>
<plugin> </executions>
<groupId>org.apache.maven.plugins</groupId> </plugin>
<artifactId>maven-compiler-plugin</artifactId> </plugins>
<version>3.8.0</version> </build>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.1.1</version>
<configuration>
<dependencyReducedPomLocation>${project.build.directory}/dependency-reduced-pom.xml</dependencyReducedPomLocation>
<relocations>
<relocation>
<pattern>io.papermc.lib</pattern>
<shadedPattern>com.wimbli.WorldBorder.paperlib</shadedPattern> <!-- Replace this -->
</relocation>
</relocations>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project> </project>

View File

@ -9,27 +9,23 @@ import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.event.block.BlockPlaceEvent;
public class BlockPlaceListener implements Listener public class BlockPlaceListener implements Listener {
{ @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onBlockPlace(BlockPlaceEvent event) {
public void onBlockPlace(BlockPlaceEvent event) Location loc = event.getBlockPlaced().getLocation();
{ if (loc == null) return;
Location loc = event.getBlockPlaced().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()))
{
event.setCancelled(true);
}
}
public void unregister() World world = loc.getWorld();
{ if (world == null) return;
HandlerList.unregisterAll(this); BorderData border = Config.Border(world.getName());
} if (border == null) return;
if (!border.insideBorder(loc.getX(), loc.getZ(), Config.ShapeRound())) {
event.setCancelled(true);
}
}
public void unregister() {
HandlerList.unregisterAll(this);
}
} }

View File

@ -1,178 +1,157 @@
package com.wimbli.WorldBorder; package com.wimbli.WorldBorder;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity; import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
import org.bukkit.Location;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
import org.bukkit.World;
import java.util.*;
public class BorderCheckTask implements Runnable public class BorderCheckTask implements Runnable {
{ // track players who are being handled (moved back inside the border) already; needed since Bukkit is sometimes sending teleport events with the old (now incorrect) location still indicated, which can lead to a loop when we then teleport them thinking they're outside the border, triggering event again, etc.
@Override private static Set<String> handlingPlayers = Collections.synchronizedSet(new LinkedHashSet<String>());
public void run()
{
// if knockback is set to 0, simply return
if (Config.KnockBack() == 0.0)
return;
Collection<Player> players = ImmutableList.copyOf(Bukkit.getServer().getOnlinePlayers()); // set targetLoc only if not current player location; set returnLocationOnly to true to have new Location returned if they need to be moved to one, instead of directly handling it
public static Location checkPlayer(Player player, Location targetLoc, boolean returnLocationOnly, boolean notify) {
if (player == null || !player.isOnline()) return null;
for (Player player : players) Location loc = (targetLoc == null) ? player.getLocation().clone() : targetLoc;
{ if (loc == null) return null;
checkPlayer(player, null, false, true);
}
}
// track players who are being handled (moved back inside the border) already; needed since Bukkit is sometimes sending teleport events with the old (now incorrect) location still indicated, which can lead to a loop when we then teleport them thinking they're outside the border, triggering event again, etc. World world = loc.getWorld();
private static Set<String> handlingPlayers = Collections.synchronizedSet(new LinkedHashSet<String>()); if (world == null) return null;
BorderData border = Config.Border(world.getName());
if (border == null) return null;
// set targetLoc only if not current player location; set returnLocationOnly to true to have new Location returned if they need to be moved to one, instead of directly handling it if (border.insideBorder(loc.getX(), loc.getZ(), Config.ShapeRound()))
public static Location checkPlayer(Player player, Location targetLoc, boolean returnLocationOnly, boolean notify) return null;
{
if (player == null || !player.isOnline()) return null;
Location loc = (targetLoc == null) ? player.getLocation().clone() : targetLoc; // if player is in bypass list (from bypass command), allow them beyond border; also ignore players currently being handled already
if (loc == null) return null; if (Config.isPlayerBypassing(player.getUniqueId()) || handlingPlayers.contains(player.getName().toLowerCase()))
return null;
World world = loc.getWorld(); // tag this player as being handled so we can't get stuck in a loop due to Bukkit currently sometimes repeatedly providing incorrect location through teleport event
if (world == null) return null; handlingPlayers.add(player.getName().toLowerCase());
BorderData border = Config.Border(world.getName());
if (border == null) return null;
if (border.insideBorder(loc.getX(), loc.getZ(), Config.ShapeRound())) Location newLoc = newLocation(player, loc, border, notify);
return null; boolean handlingVehicle = false;
// if player is in bypass list (from bypass command), allow them beyond border; also ignore players currently being handled already /*
if (Config.isPlayerBypassing(player.getUniqueId()) || handlingPlayers.contains(player.getName().toLowerCase())) * since we need to forcibly eject players who are inside vehicles, that fires a teleport event (go figure) and
return null; * so would effectively double trigger for us, so we need to handle it here to prevent sending two messages and
* two log entries etc.
* after players are ejected we can wait a few ticks (long enough for their client to receive new entity location)
* and then set them as passenger of the vehicle again
*/
if (player.isInsideVehicle()) {
Entity ride = player.getVehicle();
player.leaveVehicle();
if (ride != null) { // vehicles need to be offset vertically and have velocity stopped
double vertOffset = (ride instanceof LivingEntity) ? 0 : ride.getLocation().getY() - loc.getY();
Location rideLoc = newLoc.clone();
rideLoc.setY(newLoc.getY() + vertOffset);
if (Config.Debug())
Config.logWarn("Player was riding a \"" + ride.toString() + "\".");
// tag this player as being handled so we can't get stuck in a loop due to Bukkit currently sometimes repeatedly providing incorrect location through teleport event ride.setVelocity(new Vector(0, 0, 0));
handlingPlayers.add(player.getName().toLowerCase()); ride.teleport(rideLoc, TeleportCause.PLUGIN);
Location newLoc = newLocation(player, loc, border, notify); if (Config.RemountTicks() > 0) {
boolean handlingVehicle = false; setPassengerDelayed(ride, player, player.getName(), Config.RemountTicks());
handlingVehicle = true;
}
}
}
/* // check if player has something (a pet, maybe?) riding them; only possible through odd plugins.
* since we need to forcibly eject players who are inside vehicles, that fires a teleport event (go figure) and // it can prevent all teleportation of the player completely, so it's very much not good and needs handling
* so would effectively double trigger for us, so we need to handle it here to prevent sending two messages and List<Entity> passengers = player.getPassengers();
* two log entries etc. if (!passengers.isEmpty()) {
* after players are ejected we can wait a few ticks (long enough for their client to receive new entity location) player.eject();
* and then set them as passenger of the vehicle again for (Entity rider : passengers) {
*/ rider.teleport(newLoc, TeleportCause.PLUGIN);
if (player.isInsideVehicle()) if (Config.Debug())
{ Config.logWarn("Player had a passenger riding on them: " + rider.getType());
Entity ride = player.getVehicle(); }
player.leaveVehicle(); player.sendMessage("Your passenger" + ((passengers.size() > 1) ? "s have" : " has") + " been ejected.");
if (ride != null) }
{ // vehicles need to be offset vertically and have velocity stopped
double vertOffset = (ride instanceof LivingEntity) ? 0 : ride.getLocation().getY() - loc.getY();
Location rideLoc = newLoc.clone();
rideLoc.setY(newLoc.getY() + vertOffset);
if (Config.Debug())
Config.logWarn("Player was riding a \"" + ride.toString() + "\".");
ride.setVelocity(new Vector(0, 0, 0)); // give some particle and sound effects where the player was beyond the border, if "whoosh effect" is enabled
ride.teleport(rideLoc, TeleportCause.PLUGIN); Config.showWhooshEffect(loc);
if (Config.RemountTicks() > 0) if (!returnLocationOnly)
{ player.teleport(newLoc, TeleportCause.PLUGIN);
setPassengerDelayed(ride, player, player.getName(), Config.RemountTicks());
handlingVehicle = true;
}
}
}
// check if player has something (a pet, maybe?) riding them; only possible through odd plugins. if (!handlingVehicle)
// it can prevent all teleportation of the player completely, so it's very much not good and needs handling handlingPlayers.remove(player.getName().toLowerCase());
List<Entity> passengers = player.getPassengers();
if (!passengers.isEmpty())
{
player.eject();
for (Entity rider : passengers)
{
rider.teleport(newLoc, TeleportCause.PLUGIN);
if (Config.Debug())
Config.logWarn("Player had a passenger riding on them: " + rider.getType());
}
player.sendMessage("Your passenger" + ((passengers.size() > 1) ? "s have" : " has") + " been ejected.");
}
// give some particle and sound effects where the player was beyond the border, if "whoosh effect" is enabled if (returnLocationOnly)
Config.showWhooshEffect(loc); return newLoc;
if (!returnLocationOnly) return null;
player.teleport(newLoc, TeleportCause.PLUGIN); }
if (!handlingVehicle) public static Location checkPlayer(Player player, Location targetLoc, boolean returnLocationOnly) {
handlingPlayers.remove(player.getName().toLowerCase()); return checkPlayer(player, targetLoc, returnLocationOnly, true);
}
if (returnLocationOnly) private static Location newLocation(Player player, Location loc, BorderData border, boolean notify) {
return newLoc; if (Config.Debug()) {
Config.logWarn((notify ? "Border crossing" : "Check was run") + " in \"" + loc.getWorld().getName() + "\". 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()));
}
return null; Location newLoc = border.correctedPosition(loc, Config.ShapeRound(), player.isFlying());
}
public static Location checkPlayer(Player player, Location targetLoc, boolean returnLocationOnly)
{
return checkPlayer(player, targetLoc, returnLocationOnly, true);
}
private static Location newLocation(Player player, Location loc, BorderData border, boolean notify) // it's remotely possible (such as in the Nether) a suitable location isn't available, in which case...
{ if (newLoc == null) {
if (Config.Debug()) if (Config.Debug())
{ Config.logWarn("Target new location unviable, using spawn or killing player.");
Config.logWarn((notify ? "Border crossing" : "Check was run") + " in \"" + loc.getWorld().getName() + "\". Border " + border.toString()); if (Config.getIfPlayerKill()) {
Config.logWarn("Player position X: " + Config.coord.format(loc.getX()) + " Y: " + Config.coord.format(loc.getY()) + " Z: " + Config.coord.format(loc.getZ())); player.setHealth(0.0D);
} return null;
}
newLoc = player.getWorld().getSpawnLocation();
}
Location newLoc = border.correctedPosition(loc, Config.ShapeRound(), player.isFlying()); if (Config.Debug())
Config.logWarn("New position in world \"" + newLoc.getWorld().getName() + "\" at X: " + Config.coord.format(newLoc.getX()) + " Y: " + Config.coord.format(newLoc.getY()) + " Z: " + Config.coord.format(newLoc.getZ()));
// it's remotely possible (such as in the Nether) a suitable location isn't available, in which case... if (notify)
if (newLoc == null) player.sendMessage(Config.Message());
{
if (Config.Debug())
Config.logWarn("Target new location unviable, using spawn or killing player.");
if (Config.getIfPlayerKill())
{
player.setHealth(0.0D);
return null;
}
newLoc = player.getWorld().getSpawnLocation();
}
if (Config.Debug()) return newLoc;
Config.logWarn("New position in world \"" + newLoc.getWorld().getName() + "\" at X: " + Config.coord.format(newLoc.getX()) + " Y: " + Config.coord.format(newLoc.getY()) + " Z: " + Config.coord.format(newLoc.getZ())); }
if (notify) private static void setPassengerDelayed(final Entity vehicle, final Player player, final String playerName, long delay) {
player.sendMessage(Config.Message()); Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(WorldBorder.plugin, new Runnable() {
@Override
public void run() {
handlingPlayers.remove(playerName.toLowerCase());
if (vehicle == null || player == null)
return;
return newLoc; vehicle.addPassenger(player);
} }
}, delay);
}
private static void setPassengerDelayed(final Entity vehicle, final Player player, final String playerName, long delay) @Override
{ public void run() {
Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(WorldBorder.plugin, new Runnable() // if knockback is set to 0, simply return
{ if (Config.KnockBack() == 0.0)
@Override return;
public void run()
{
handlingPlayers.remove(playerName.toLowerCase());
if (vehicle == null || player == null)
return;
vehicle.addPassenger(player); Collection<Player> players = ImmutableList.copyOf(Bukkit.getServer().getOnlinePlayers());
}
}, delay); for (Player player : players) {
} checkPlayer(player, null, false, true);
}
}
} }

View File

@ -1,493 +1,461 @@
package com.wimbli.WorldBorder; package com.wimbli.WorldBorder;
import java.util.EnumSet;
import org.bukkit.Chunk; import org.bukkit.Chunk;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.World; import org.bukkit.World;
import java.util.EnumSet;
public class BorderData
{
// the main data interacted with
private double x = 0;
private double z = 0;
private int radiusX = 0;
private int radiusZ = 0;
private Boolean shapeRound = null;
private boolean wrapping = false;
// some extra data kept handy for faster border checks
private double maxX;
private double minX;
private double maxZ;
private double minZ;
private double radiusXSquared;
private double radiusZSquared;
private double DefiniteRectangleX;
private double DefiniteRectangleZ;
private double radiusSquaredQuotient;
public BorderData(double x, double z, int radiusX, int radiusZ, Boolean shapeRound, boolean wrap)
{
setData(x, z, radiusX, radiusZ, shapeRound, wrap);
}
public BorderData(double x, double z, int radiusX, int radiusZ)
{
setData(x, z, radiusX, radiusZ, null);
}
public BorderData(double x, double z, int radiusX, int radiusZ, Boolean shapeRound)
{
setData(x, z, radiusX, radiusZ, shapeRound);
}
public BorderData(double x, double z, int radius)
{
setData(x, z, radius, null);
}
public BorderData(double x, double z, int radius, Boolean shapeRound)
{
setData(x, z, radius, shapeRound);
}
public final void setData(double x, double z, int radiusX, int radiusZ, Boolean shapeRound, boolean wrap)
{
this.x = x;
this.z = z;
this.shapeRound = shapeRound;
this.wrapping = wrap;
this.setRadiusX(radiusX);
this.setRadiusZ(radiusZ);
}
public final void setData(double x, double z, int radiusX, int radiusZ, Boolean shapeRound)
{
setData(x, z, radiusX, radiusZ, shapeRound, false);
}
public final void setData(double x, double z, int radius, Boolean shapeRound)
{
setData(x, z, radius, radius, shapeRound, false);
}
public BorderData copy()
{
return new BorderData(x, z, radiusX, radiusZ, shapeRound, wrapping);
}
public double getX()
{
return x;
}
public void setX(double x)
{
this.x = x;
this.maxX = x + radiusX;
this.minX = x - radiusX;
}
public double getZ()
{
return z;
}
public void setZ(double z)
{
this.z = z;
this.maxZ = z + radiusZ;
this.minZ = z - radiusZ;
}
public int getRadiusX()
{
return radiusX;
}
public int getRadiusZ()
{
return radiusZ;
}
public void setRadiusX(int radiusX)
{
this.radiusX = radiusX;
this.maxX = x + radiusX;
this.minX = x - radiusX;
this.radiusXSquared = (double)radiusX * (double)radiusX;
this.radiusSquaredQuotient = this.radiusXSquared / this.radiusZSquared;
this.DefiniteRectangleX = Math.sqrt(.5 * this.radiusXSquared);
}
public void setRadiusZ(int radiusZ)
{
this.radiusZ = radiusZ;
this.maxZ = z + radiusZ;
this.minZ = z - radiusZ;
this.radiusZSquared = (double)radiusZ * (double)radiusZ;
this.radiusSquaredQuotient = this.radiusXSquared / this.radiusZSquared;
this.DefiniteRectangleZ = Math.sqrt(.5 * this.radiusZSquared);
}
// backwards-compatible methods from before elliptical/rectangular shapes were supported public class BorderData {
/** //these material IDs are acceptable for places to teleport player; breathable blocks and water
* @deprecated Replaced by {@link #getRadiusX()} and {@link #getRadiusZ()}; public static final EnumSet<Material> safeOpenBlocks = EnumSet.noneOf(Material.class);
* this method now returns an average of those two values and is thus imprecise //these material IDs are ones we don't want to drop the player onto, like cactus or lava or fire or activated Ender portal
*/ public static final EnumSet<Material> painfulBlocks = EnumSet.noneOf(Material.class);
public int getRadius() private static final int limBot = 0;
{
return (radiusX + radiusZ) / 2; // average radius; not great, but probably best for backwards compatibility static {
} safeOpenBlocks.add(Material.AIR);
public void setRadius(int radius) safeOpenBlocks.add(Material.CAVE_AIR);
{ safeOpenBlocks.add(Material.OAK_SAPLING);
setRadiusX(radius); safeOpenBlocks.add(Material.SPRUCE_SAPLING);
setRadiusZ(radius); safeOpenBlocks.add(Material.BIRCH_SAPLING);
} safeOpenBlocks.add(Material.JUNGLE_SAPLING);
safeOpenBlocks.add(Material.ACACIA_SAPLING);
safeOpenBlocks.add(Material.DARK_OAK_SAPLING);
safeOpenBlocks.add(Material.WATER);
safeOpenBlocks.add(Material.RAIL);
safeOpenBlocks.add(Material.POWERED_RAIL);
safeOpenBlocks.add(Material.DETECTOR_RAIL);
safeOpenBlocks.add(Material.ACTIVATOR_RAIL);
safeOpenBlocks.add(Material.COBWEB);
safeOpenBlocks.add(Material.GRASS);
safeOpenBlocks.add(Material.FERN);
safeOpenBlocks.add(Material.DEAD_BUSH);
safeOpenBlocks.add(Material.DANDELION);
safeOpenBlocks.add(Material.POPPY);
safeOpenBlocks.add(Material.BLUE_ORCHID);
safeOpenBlocks.add(Material.ALLIUM);
safeOpenBlocks.add(Material.AZURE_BLUET);
safeOpenBlocks.add(Material.RED_TULIP);
safeOpenBlocks.add(Material.ORANGE_TULIP);
safeOpenBlocks.add(Material.WHITE_TULIP);
safeOpenBlocks.add(Material.PINK_TULIP);
safeOpenBlocks.add(Material.OXEYE_DAISY);
safeOpenBlocks.add(Material.BROWN_MUSHROOM);
safeOpenBlocks.add(Material.RED_MUSHROOM);
safeOpenBlocks.add(Material.TORCH);
safeOpenBlocks.add(Material.WALL_TORCH);
safeOpenBlocks.add(Material.REDSTONE_WIRE);
safeOpenBlocks.add(Material.WHEAT);
safeOpenBlocks.add(Material.LADDER);
safeOpenBlocks.add(Material.LEVER);
safeOpenBlocks.add(Material.LIGHT_WEIGHTED_PRESSURE_PLATE);
safeOpenBlocks.add(Material.HEAVY_WEIGHTED_PRESSURE_PLATE);
safeOpenBlocks.add(Material.STONE_PRESSURE_PLATE);
safeOpenBlocks.add(Material.OAK_PRESSURE_PLATE);
safeOpenBlocks.add(Material.SPRUCE_PRESSURE_PLATE);
safeOpenBlocks.add(Material.BIRCH_PRESSURE_PLATE);
safeOpenBlocks.add(Material.JUNGLE_PRESSURE_PLATE);
safeOpenBlocks.add(Material.ACACIA_PRESSURE_PLATE);
safeOpenBlocks.add(Material.DARK_OAK_PRESSURE_PLATE);
safeOpenBlocks.add(Material.REDSTONE_TORCH);
safeOpenBlocks.add(Material.REDSTONE_WALL_TORCH);
safeOpenBlocks.add(Material.STONE_BUTTON);
safeOpenBlocks.add(Material.SNOW);
safeOpenBlocks.add(Material.SUGAR_CANE);
safeOpenBlocks.add(Material.REPEATER);
safeOpenBlocks.add(Material.COMPARATOR);
safeOpenBlocks.add(Material.OAK_TRAPDOOR);
safeOpenBlocks.add(Material.SPRUCE_TRAPDOOR);
safeOpenBlocks.add(Material.BIRCH_TRAPDOOR);
safeOpenBlocks.add(Material.JUNGLE_TRAPDOOR);
safeOpenBlocks.add(Material.ACACIA_TRAPDOOR);
safeOpenBlocks.add(Material.DARK_OAK_TRAPDOOR);
safeOpenBlocks.add(Material.MELON_STEM);
safeOpenBlocks.add(Material.ATTACHED_MELON_STEM);
safeOpenBlocks.add(Material.PUMPKIN_STEM);
safeOpenBlocks.add(Material.ATTACHED_PUMPKIN_STEM);
safeOpenBlocks.add(Material.VINE);
safeOpenBlocks.add(Material.NETHER_WART);
safeOpenBlocks.add(Material.TRIPWIRE);
safeOpenBlocks.add(Material.TRIPWIRE_HOOK);
safeOpenBlocks.add(Material.CARROTS);
safeOpenBlocks.add(Material.POTATOES);
safeOpenBlocks.add(Material.OAK_BUTTON);
safeOpenBlocks.add(Material.SPRUCE_BUTTON);
safeOpenBlocks.add(Material.BIRCH_BUTTON);
safeOpenBlocks.add(Material.JUNGLE_BUTTON);
safeOpenBlocks.add(Material.ACACIA_BUTTON);
safeOpenBlocks.add(Material.DARK_OAK_BUTTON);
safeOpenBlocks.add(Material.SUNFLOWER);
safeOpenBlocks.add(Material.LILAC);
safeOpenBlocks.add(Material.ROSE_BUSH);
safeOpenBlocks.add(Material.PEONY);
safeOpenBlocks.add(Material.TALL_GRASS);
safeOpenBlocks.add(Material.LARGE_FERN);
safeOpenBlocks.add(Material.BEETROOTS);
try { // signs in 1.14 can be different wood types
safeOpenBlocks.add(Material.ACACIA_SIGN);
safeOpenBlocks.add(Material.ACACIA_WALL_SIGN);
safeOpenBlocks.add(Material.BIRCH_SIGN);
safeOpenBlocks.add(Material.BIRCH_WALL_SIGN);
safeOpenBlocks.add(Material.DARK_OAK_SIGN);
safeOpenBlocks.add(Material.DARK_OAK_WALL_SIGN);
safeOpenBlocks.add(Material.JUNGLE_SIGN);
safeOpenBlocks.add(Material.JUNGLE_WALL_SIGN);
safeOpenBlocks.add(Material.OAK_SIGN);
safeOpenBlocks.add(Material.OAK_WALL_SIGN);
safeOpenBlocks.add(Material.SPRUCE_SIGN);
safeOpenBlocks.add(Material.SPRUCE_WALL_SIGN);
} catch (NoSuchFieldError ex) {
}
}
static {
painfulBlocks.add(Material.LAVA);
painfulBlocks.add(Material.FIRE);
painfulBlocks.add(Material.CACTUS);
painfulBlocks.add(Material.END_PORTAL);
painfulBlocks.add(Material.MAGMA_BLOCK);
}
// the main data interacted with
private double x = 0;
private double z = 0;
private int radiusX = 0;
private int radiusZ = 0;
private Boolean shapeRound = null;
private boolean wrapping = false;
// some extra data kept handy for faster border checks
private double maxX;
private double minX;
private double maxZ;
private double minZ;
private double radiusXSquared;
private double radiusZSquared;
private double DefiniteRectangleX;
private double DefiniteRectangleZ;
private double radiusSquaredQuotient;
public BorderData(double x, double z, int radiusX, int radiusZ, Boolean shapeRound, boolean wrap) {
setData(x, z, radiusX, radiusZ, shapeRound, wrap);
}
public BorderData(double x, double z, int radiusX, int radiusZ) {
setData(x, z, radiusX, radiusZ, null);
}
public BorderData(double x, double z, int radiusX, int radiusZ, Boolean shapeRound) {
setData(x, z, radiusX, radiusZ, shapeRound);
}
public BorderData(double x, double z, int radius) {
setData(x, z, radius, null);
}
public BorderData(double x, double z, int radius, Boolean shapeRound) {
setData(x, z, radius, shapeRound);
}
public final void setData(double x, double z, int radiusX, int radiusZ, Boolean shapeRound, boolean wrap) {
this.x = x;
this.z = z;
this.shapeRound = shapeRound;
this.wrapping = wrap;
this.setRadiusX(radiusX);
this.setRadiusZ(radiusZ);
}
public final void setData(double x, double z, int radiusX, int radiusZ, Boolean shapeRound) {
setData(x, z, radiusX, radiusZ, shapeRound, false);
}
public final void setData(double x, double z, int radius, Boolean shapeRound) {
setData(x, z, radius, radius, shapeRound, false);
}
public BorderData copy() {
return new BorderData(x, z, radiusX, radiusZ, shapeRound, wrapping);
}
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
this.maxX = x + radiusX;
this.minX = x - radiusX;
}
public double getZ() {
return z;
}
public Boolean getShape() // backwards-compatible methods from before elliptical/rectangular shapes were supported
{
return shapeRound; public void setZ(double z) {
} this.z = z;
public void setShape(Boolean shapeRound) this.maxZ = z + radiusZ;
{ this.minZ = z - radiusZ;
this.shapeRound = shapeRound; }
}
public int getRadiusX() {
return radiusX;
}
public void setRadiusX(int radiusX) {
this.radiusX = radiusX;
this.maxX = x + radiusX;
this.minX = x - radiusX;
this.radiusXSquared = (double) radiusX * (double) radiusX;
this.radiusSquaredQuotient = this.radiusXSquared / this.radiusZSquared;
this.DefiniteRectangleX = Math.sqrt(.5 * this.radiusXSquared);
}
public int getRadiusZ() {
return radiusZ;
}
public void setRadiusZ(int radiusZ) {
this.radiusZ = radiusZ;
this.maxZ = z + radiusZ;
this.minZ = z - radiusZ;
this.radiusZSquared = (double) radiusZ * (double) radiusZ;
this.radiusSquaredQuotient = this.radiusXSquared / this.radiusZSquared;
this.DefiniteRectangleZ = Math.sqrt(.5 * this.radiusZSquared);
}
/**
* @deprecated Replaced by {@link #getRadiusX()} and {@link #getRadiusZ()};
* this method now returns an average of those two values and is thus imprecise
*/
public int getRadius() {
return (radiusX + radiusZ) / 2; // average radius; not great, but probably best for backwards compatibility
}
public void setRadius(int radius) {
setRadiusX(radius);
setRadiusZ(radius);
}
public Boolean getShape() {
return shapeRound;
}
public void setShape(Boolean shapeRound) {
this.shapeRound = shapeRound;
}
public boolean getWrapping() {
return wrapping;
}
public void setWrapping(boolean wrap) {
this.wrapping = wrap;
}
@Override
public String toString() {
return "radius " + ((radiusX == radiusZ) ? radiusX : radiusX + "x" + radiusZ) + " at X: " + Config.coord.format(x) + " Z: " + Config.coord.format(z) + (shapeRound != null ? (" (shape override: " + Config.ShapeName(shapeRound.booleanValue()) + ")") : "") + (wrapping ? (" (wrapping)") : "");
}
// 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 this border has a shape override set, use it
if (shapeRound != null)
round = shapeRound.booleanValue();
// square border
if (!round)
return !(xLoc < minX || xLoc > maxX || zLoc < minZ || zLoc > maxZ);
// round border
else {
// elegant round border checking algorithm is from rBorder by Reil with almost no changes, all credit to him for it
double X = Math.abs(x - xLoc);
double Z = Math.abs(z - zLoc);
if (X < DefiniteRectangleX && Z < DefiniteRectangleZ)
return true; // Definitely inside
else if (X >= radiusX || Z >= radiusZ)
return false; // Definitely outside
else if (X * X + Z * Z * radiusSquaredQuotient < radiusXSquared)
return true; // After further calculation, inside
else
return false; // Apparently outside, then
}
}
public boolean insideBorder(double xLoc, double zLoc) {
return insideBorder(xLoc, zLoc, Config.ShapeRound());
}
public boolean insideBorder(Location loc) {
return insideBorder(loc.getX(), loc.getZ(), Config.ShapeRound());
}
public boolean insideBorder(CoordXZ coord, boolean round) {
return insideBorder(coord.x, coord.z, round);
}
public boolean insideBorder(CoordXZ coord) {
return insideBorder(coord.x, coord.z, Config.ShapeRound());
}
public Location correctedPosition(Location loc, boolean round, boolean flying) {
// if this border has a shape override set, use it
if (shapeRound != null)
round = shapeRound.booleanValue();
double xLoc = loc.getX();
double zLoc = loc.getZ();
double yLoc = loc.getY();
// square border
if (!round) {
if (wrapping) {
if (xLoc <= minX)
xLoc = maxX - Config.KnockBack();
else if (xLoc >= maxX)
xLoc = minX + Config.KnockBack();
if (zLoc <= minZ)
zLoc = maxZ - Config.KnockBack();
else if (zLoc >= maxZ)
zLoc = minZ + Config.KnockBack();
} else {
if (xLoc <= minX)
xLoc = minX + Config.KnockBack();
else if (xLoc >= maxX)
xLoc = maxX - Config.KnockBack();
if (zLoc <= minZ)
zLoc = minZ + Config.KnockBack();
else if (zLoc >= maxZ)
zLoc = maxZ - Config.KnockBack();
}
}
// round border
else {
// algorithm originally from: http://stackoverflow.com/questions/300871/best-way-to-find-a-point-on-a-circle-closest-to-a-given-point
// modified by Lang Lukas to support elliptical border shape
//Transform the ellipse to a circle with radius 1 (we need to transform the point the same way)
double dX = xLoc - x;
double dZ = zLoc - z;
double dU = Math.sqrt(dX * dX + dZ * dZ); //distance of the untransformed point from the center
double dT = Math.sqrt(dX * dX / radiusXSquared + dZ * dZ / radiusZSquared); //distance of the transformed point from the center
double f = (1 / dT - Config.KnockBack() / dU); //"correction" factor for the distances
if (wrapping) {
xLoc = x - dX * f;
zLoc = z - dZ * f;
} else {
xLoc = x + dX * f;
zLoc = z + dZ * f;
}
}
int ixLoc = Location.locToBlock(xLoc);
int izLoc = Location.locToBlock(zLoc);
// Make sure the chunk we're checking in is actually loaded
Chunk tChunk = loc.getWorld().getChunkAt(CoordXZ.blockToChunk(ixLoc), CoordXZ.blockToChunk(izLoc));
if (!tChunk.isLoaded())
tChunk.load();
yLoc = getSafeY(loc.getWorld(), ixLoc, Location.locToBlock(yLoc), izLoc, flying);
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());
}
public Location correctedPosition(Location loc, boolean round) {
return correctedPosition(loc, round, false);
}
public Location correctedPosition(Location loc) {
return correctedPosition(loc, Config.ShapeRound(), false);
}
// 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, boolean flying) {
boolean safe = safeOpenBlocks.contains(world.getBlockAt(X, Y, Z).getType()) // target block open and safe
&& safeOpenBlocks.contains(world.getBlockAt(X, Y + 1, Z).getType()); // above target block open and safe
if (!safe || flying)
return safe;
Material below = world.getBlockAt(X, Y - 1, Z).getType();
return (safe
&& (!safeOpenBlocks.contains(below) || below == Material.WATER) // below target block not open/breathable (so presumably solid), or is water
&& !painfulBlocks.contains(below) // below target block not painful
);
}
// find closest safe Y position from the starting position
private double getSafeY(World world, int X, int Y, int Z, boolean flying) {
// artificial height limit of 127 added for Nether worlds since CraftBukkit still incorrectly returns 255 for their max height, leading to players sent to the "roof" of the Nether
final boolean isNether = world.getEnvironment() == World.Environment.NETHER;
int limTop = isNether ? 125 : world.getMaxHeight() - 2;
final int highestBlockBoundary = Math.min(world.getHighestBlockYAt(X, Z) + 1, limTop);
// if Y is larger than the world can be and user can fly, return Y - Unless we are in the Nether, we might not want players on the roof
if (flying && Y > limTop && !isNether)
return (double) Y;
// make sure Y values are within the boundaries of the world.
if (Y > limTop) {
if (isNether)
Y = limTop; // because of the roof, the nether can not rely on highestBlockBoundary, so limTop has to be used
else {
if (flying)
Y = limTop;
else
Y = highestBlockBoundary; // there will never be a save block to stand on for Y values > highestBlockBoundary
}
}
if (Y < limBot)
Y = limBot;
// for non Nether worlds we don't need to check upwards to the world-limit, it is enough to check up to the highestBlockBoundary, unless player is flying
if (!isNether && !flying)
limTop = highestBlockBoundary;
// Expanding Y search method adapted from Acru's code in the Nether plugin
for (int y1 = Y, y2 = Y; (y1 > limBot) || (y2 < limTop); y1--, y2++) {
// Look below.
if (y1 > limBot) {
if (isSafeSpot(world, X, y1, Z, flying))
return (double) y1;
}
// Look above.
if (y2 < limTop && y2 != y1) {
if (isSafeSpot(world, X, y2, Z, flying))
return (double) y2;
}
}
return -1.0; // no safe Y location?!?!? Must be a rare spot in a Nether world or something
}
public boolean getWrapping() @Override
{ public boolean equals(Object obj) {
return wrapping; if (this == obj)
} return true;
public void setWrapping(boolean wrap) else if (obj == null || obj.getClass() != this.getClass())
{ return false;
this.wrapping = wrap;
}
BorderData test = (BorderData) obj;
return test.x == this.x && test.z == this.z && test.radiusX == this.radiusX && test.radiusZ == this.radiusZ;
}
@Override @Override
public String toString() public int hashCode() {
{ return (((int) (this.x * 10) << 4) + (int) this.z + (this.radiusX << 2) + (this.radiusZ << 3));
return "radius " + ((radiusX == radiusZ) ? radiusX : radiusX + "x" + radiusZ) + " at X: " + Config.coord.format(x) + " Z: " + Config.coord.format(z) + (shapeRound != null ? (" (shape override: " + Config.ShapeName(shapeRound.booleanValue()) + ")") : "") + (wrapping ? (" (wrapping)") : ""); }
}
// 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 this border has a shape override set, use it
if (shapeRound != null)
round = shapeRound.booleanValue();
// square border
if (!round)
return !(xLoc < minX || xLoc > maxX || zLoc < minZ || zLoc > maxZ);
// round border
else
{
// elegant round border checking algorithm is from rBorder by Reil with almost no changes, all credit to him for it
double X = Math.abs(x - xLoc);
double Z = Math.abs(z - zLoc);
if (X < DefiniteRectangleX && Z < DefiniteRectangleZ)
return true; // Definitely inside
else if (X >= radiusX || Z >= radiusZ)
return false; // Definitely outside
else if (X * X + Z * Z * radiusSquaredQuotient < radiusXSquared)
return true; // After further calculation, inside
else
return false; // Apparently outside, then
}
}
public boolean insideBorder(double xLoc, double zLoc)
{
return insideBorder(xLoc, zLoc, Config.ShapeRound());
}
public boolean insideBorder(Location loc)
{
return insideBorder(loc.getX(), loc.getZ(), Config.ShapeRound());
}
public boolean insideBorder(CoordXZ coord, boolean round)
{
return insideBorder(coord.x, coord.z, round);
}
public boolean insideBorder(CoordXZ coord)
{
return insideBorder(coord.x, coord.z, Config.ShapeRound());
}
public Location correctedPosition(Location loc, boolean round, boolean flying)
{
// if this border has a shape override set, use it
if (shapeRound != null)
round = shapeRound.booleanValue();
double xLoc = loc.getX();
double zLoc = loc.getZ();
double yLoc = loc.getY();
// square border
if (!round)
{
if (wrapping)
{
if (xLoc <= minX)
xLoc = maxX - Config.KnockBack();
else if (xLoc >= maxX)
xLoc = minX + Config.KnockBack();
if (zLoc <= minZ)
zLoc = maxZ - Config.KnockBack();
else if (zLoc >= maxZ)
zLoc = minZ + Config.KnockBack();
}
else
{
if (xLoc <= minX)
xLoc = minX + Config.KnockBack();
else if (xLoc >= maxX)
xLoc = maxX - Config.KnockBack();
if (zLoc <= minZ)
zLoc = minZ + Config.KnockBack();
else if (zLoc >= maxZ)
zLoc = maxZ - Config.KnockBack();
}
}
// round border
else
{
// algorithm originally from: http://stackoverflow.com/questions/300871/best-way-to-find-a-point-on-a-circle-closest-to-a-given-point
// modified by Lang Lukas to support elliptical border shape
//Transform the ellipse to a circle with radius 1 (we need to transform the point the same way)
double dX = xLoc - x;
double dZ = zLoc - z;
double dU = Math.sqrt(dX *dX + dZ * dZ); //distance of the untransformed point from the center
double dT = Math.sqrt(dX *dX / radiusXSquared + dZ * dZ / radiusZSquared); //distance of the transformed point from the center
double f = (1 / dT - Config.KnockBack() / dU); //"correction" factor for the distances
if (wrapping)
{
xLoc = x - dX * f;
zLoc = z - dZ * f;
} else {
xLoc = x + dX * f;
zLoc = z + dZ * f;
}
}
int ixLoc = Location.locToBlock(xLoc);
int izLoc = Location.locToBlock(zLoc);
// Make sure the chunk we're checking in is actually loaded
Chunk tChunk = loc.getWorld().getChunkAt(CoordXZ.blockToChunk(ixLoc), CoordXZ.blockToChunk(izLoc));
if (!tChunk.isLoaded())
tChunk.load();
yLoc = getSafeY(loc.getWorld(), ixLoc, Location.locToBlock(yLoc), izLoc, flying);
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());
}
public Location correctedPosition(Location loc, boolean round)
{
return correctedPosition(loc, round, false);
}
public Location correctedPosition(Location loc)
{
return correctedPosition(loc, Config.ShapeRound(), false);
}
//these material IDs are acceptable for places to teleport player; breathable blocks and water
public static final EnumSet<Material> safeOpenBlocks = EnumSet.noneOf(Material.class);
static
{
safeOpenBlocks.add(Material.AIR);
safeOpenBlocks.add(Material.CAVE_AIR);
safeOpenBlocks.add(Material.OAK_SAPLING);
safeOpenBlocks.add(Material.SPRUCE_SAPLING);
safeOpenBlocks.add(Material.BIRCH_SAPLING);
safeOpenBlocks.add(Material.JUNGLE_SAPLING);
safeOpenBlocks.add(Material.ACACIA_SAPLING);
safeOpenBlocks.add(Material.DARK_OAK_SAPLING);
safeOpenBlocks.add(Material.WATER);
safeOpenBlocks.add(Material.RAIL);
safeOpenBlocks.add(Material.POWERED_RAIL);
safeOpenBlocks.add(Material.DETECTOR_RAIL);
safeOpenBlocks.add(Material.ACTIVATOR_RAIL);
safeOpenBlocks.add(Material.COBWEB);
safeOpenBlocks.add(Material.GRASS);
safeOpenBlocks.add(Material.FERN);
safeOpenBlocks.add(Material.DEAD_BUSH);
safeOpenBlocks.add(Material.DANDELION);
safeOpenBlocks.add(Material.POPPY);
safeOpenBlocks.add(Material.BLUE_ORCHID);
safeOpenBlocks.add(Material.ALLIUM);
safeOpenBlocks.add(Material.AZURE_BLUET);
safeOpenBlocks.add(Material.RED_TULIP);
safeOpenBlocks.add(Material.ORANGE_TULIP);
safeOpenBlocks.add(Material.WHITE_TULIP);
safeOpenBlocks.add(Material.PINK_TULIP);
safeOpenBlocks.add(Material.OXEYE_DAISY);
safeOpenBlocks.add(Material.BROWN_MUSHROOM);
safeOpenBlocks.add(Material.RED_MUSHROOM);
safeOpenBlocks.add(Material.TORCH);
safeOpenBlocks.add(Material.WALL_TORCH);
safeOpenBlocks.add(Material.REDSTONE_WIRE);
safeOpenBlocks.add(Material.WHEAT);
safeOpenBlocks.add(Material.LADDER);
safeOpenBlocks.add(Material.LEVER);
safeOpenBlocks.add(Material.LIGHT_WEIGHTED_PRESSURE_PLATE);
safeOpenBlocks.add(Material.HEAVY_WEIGHTED_PRESSURE_PLATE);
safeOpenBlocks.add(Material.STONE_PRESSURE_PLATE);
safeOpenBlocks.add(Material.OAK_PRESSURE_PLATE);
safeOpenBlocks.add(Material.SPRUCE_PRESSURE_PLATE);
safeOpenBlocks.add(Material.BIRCH_PRESSURE_PLATE);
safeOpenBlocks.add(Material.JUNGLE_PRESSURE_PLATE);
safeOpenBlocks.add(Material.ACACIA_PRESSURE_PLATE);
safeOpenBlocks.add(Material.DARK_OAK_PRESSURE_PLATE);
safeOpenBlocks.add(Material.REDSTONE_TORCH);
safeOpenBlocks.add(Material.REDSTONE_WALL_TORCH);
safeOpenBlocks.add(Material.STONE_BUTTON);
safeOpenBlocks.add(Material.SNOW);
safeOpenBlocks.add(Material.SUGAR_CANE);
safeOpenBlocks.add(Material.REPEATER);
safeOpenBlocks.add(Material.COMPARATOR);
safeOpenBlocks.add(Material.OAK_TRAPDOOR);
safeOpenBlocks.add(Material.SPRUCE_TRAPDOOR);
safeOpenBlocks.add(Material.BIRCH_TRAPDOOR);
safeOpenBlocks.add(Material.JUNGLE_TRAPDOOR);
safeOpenBlocks.add(Material.ACACIA_TRAPDOOR);
safeOpenBlocks.add(Material.DARK_OAK_TRAPDOOR);
safeOpenBlocks.add(Material.MELON_STEM);
safeOpenBlocks.add(Material.ATTACHED_MELON_STEM);
safeOpenBlocks.add(Material.PUMPKIN_STEM);
safeOpenBlocks.add(Material.ATTACHED_PUMPKIN_STEM);
safeOpenBlocks.add(Material.VINE);
safeOpenBlocks.add(Material.NETHER_WART);
safeOpenBlocks.add(Material.TRIPWIRE);
safeOpenBlocks.add(Material.TRIPWIRE_HOOK);
safeOpenBlocks.add(Material.CARROTS);
safeOpenBlocks.add(Material.POTATOES);
safeOpenBlocks.add(Material.OAK_BUTTON);
safeOpenBlocks.add(Material.SPRUCE_BUTTON);
safeOpenBlocks.add(Material.BIRCH_BUTTON);
safeOpenBlocks.add(Material.JUNGLE_BUTTON);
safeOpenBlocks.add(Material.ACACIA_BUTTON);
safeOpenBlocks.add(Material.DARK_OAK_BUTTON);
safeOpenBlocks.add(Material.SUNFLOWER);
safeOpenBlocks.add(Material.LILAC);
safeOpenBlocks.add(Material.ROSE_BUSH);
safeOpenBlocks.add(Material.PEONY);
safeOpenBlocks.add(Material.TALL_GRASS);
safeOpenBlocks.add(Material.LARGE_FERN);
safeOpenBlocks.add(Material.BEETROOTS);
try
{ // signs in 1.14 can be different wood types
safeOpenBlocks.add(Material.ACACIA_SIGN);
safeOpenBlocks.add(Material.ACACIA_WALL_SIGN);
safeOpenBlocks.add(Material.BIRCH_SIGN);
safeOpenBlocks.add(Material.BIRCH_WALL_SIGN);
safeOpenBlocks.add(Material.DARK_OAK_SIGN);
safeOpenBlocks.add(Material.DARK_OAK_WALL_SIGN);
safeOpenBlocks.add(Material.JUNGLE_SIGN);
safeOpenBlocks.add(Material.JUNGLE_WALL_SIGN);
safeOpenBlocks.add(Material.OAK_SIGN);
safeOpenBlocks.add(Material.OAK_WALL_SIGN);
safeOpenBlocks.add(Material.SPRUCE_SIGN);
safeOpenBlocks.add(Material.SPRUCE_WALL_SIGN);
}
catch (NoSuchFieldError ex) {}
}
//these material IDs are ones we don't want to drop the player onto, like cactus or lava or fire or activated Ender portal
public static final EnumSet<Material> painfulBlocks = EnumSet.noneOf(Material.class);
static
{
painfulBlocks.add(Material.LAVA);
painfulBlocks.add(Material.FIRE);
painfulBlocks.add(Material.CACTUS);
painfulBlocks.add(Material.END_PORTAL);
painfulBlocks.add(Material.MAGMA_BLOCK);
}
// 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, boolean flying)
{
boolean safe = safeOpenBlocks.contains(world.getBlockAt(X, Y, Z).getType()) // target block open and safe
&& safeOpenBlocks.contains(world.getBlockAt(X, Y + 1, Z).getType()); // above target block open and safe
if (!safe || flying)
return safe;
Material below = world.getBlockAt(X, Y - 1, Z).getType();
return (safe
&& (!safeOpenBlocks.contains(below) || below == Material.WATER) // below target block not open/breathable (so presumably solid), or is water
&& !painfulBlocks.contains(below) // below target block not painful
);
}
private static final int limBot = 0;
// find closest safe Y position from the starting position
private double getSafeY(World world, int X, int Y, int Z, boolean flying)
{
// artificial height limit of 127 added for Nether worlds since CraftBukkit still incorrectly returns 255 for their max height, leading to players sent to the "roof" of the Nether
final boolean isNether = world.getEnvironment() == World.Environment.NETHER;
int limTop = isNether ? 125 : world.getMaxHeight() - 2;
final int highestBlockBoundary = Math.min(world.getHighestBlockYAt(X, Z) + 1, limTop);
// if Y is larger than the world can be and user can fly, return Y - Unless we are in the Nether, we might not want players on the roof
if (flying && Y > limTop && !isNether)
return (double) Y;
// make sure Y values are within the boundaries of the world.
if (Y > limTop)
{
if (isNether)
Y = limTop; // because of the roof, the nether can not rely on highestBlockBoundary, so limTop has to be used
else
{
if (flying)
Y = limTop;
else
Y = highestBlockBoundary; // there will never be a save block to stand on for Y values > highestBlockBoundary
}
}
if (Y < limBot)
Y = limBot;
// for non Nether worlds we don't need to check upwards to the world-limit, it is enough to check up to the highestBlockBoundary, unless player is flying
if (!isNether && !flying)
limTop = highestBlockBoundary;
// Expanding Y search method adapted from Acru's code in the Nether plugin
for(int y1 = Y, y2 = Y; (y1 > limBot) || (y2 < limTop); y1--, y2++){
// Look below.
if(y1 > limBot)
{
if (isSafeSpot(world, X, y1, Z, flying))
return (double)y1;
}
// Look above.
if(y2 < limTop && y2 != y1)
{
if (isSafeSpot(world, X, y2, Z, flying))
return (double)y2;
}
}
return -1.0; // no safe Y location?!?!? Must be a rare spot in a Nether world or something
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
else if (obj == null || obj.getClass() != this.getClass())
return false;
BorderData test = (BorderData)obj;
return test.x == this.x && test.z == this.z && test.radiusX == this.radiusX && test.radiusZ == this.radiusZ;
}
@Override
public int hashCode()
{
return (((int)(this.x * 10) << 4) + (int)this.z + (this.radiusX << 2) + (this.radiusZ << 3));
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -2,58 +2,54 @@ package com.wimbli.WorldBorder;
// simple storage class for chunk x/z values // simple storage class for chunk x/z values
public class CoordXZ public class CoordXZ {
{ public int x, z;
public int x, z;
public CoordXZ(int x, int z)
{
this.x = x;
this.z = z;
}
// transform values between block, chunk, and region public CoordXZ(int x, int z) {
// bit-shifting is used because it's mucho rapido this.x = x;
public static int blockToChunk(int blockVal) this.z = z;
{ // 1 chunk is 16x16 blocks }
return blockVal >> 4; // ">>4" == "/16"
} // transform values between block, chunk, and region
public static int blockToRegion(int blockVal) // bit-shifting is used because it's mucho rapido
{ // 1 region is 512x512 blocks public static int blockToChunk(int blockVal) { // 1 chunk is 16x16 blocks
return blockVal >> 9; // ">>9" == "/512" return blockVal >> 4; // ">>4" == "/16"
} }
public static int chunkToRegion(int chunkVal)
{ // 1 region is 32x32 chunks public static int blockToRegion(int blockVal) { // 1 region is 512x512 blocks
return chunkVal >> 5; // ">>5" == "/32" return blockVal >> 9; // ">>9" == "/512"
} }
public static int chunkToBlock(int chunkVal)
{ public static int chunkToRegion(int chunkVal) { // 1 region is 32x32 chunks
return chunkVal << 4; // "<<4" == "*16" return chunkVal >> 5; // ">>5" == "/32"
} }
public static int regionToBlock(int regionVal)
{ public static int chunkToBlock(int chunkVal) {
return regionVal << 9; // "<<9" == "*512" return chunkVal << 4; // "<<4" == "*16"
} }
public static int regionToChunk(int regionVal)
{ public static int regionToBlock(int regionVal) {
return regionVal << 5; // "<<5" == "*32" return regionVal << 9; // "<<9" == "*512"
} }
public static int regionToChunk(int regionVal) {
return regionVal << 5; // "<<5" == "*32"
}
@Override @Override
public boolean equals(Object obj) public boolean equals(Object obj) {
{ if (this == obj)
if (this == obj) return true;
return true; else if (obj == null || obj.getClass() != this.getClass())
else if (obj == null || obj.getClass() != this.getClass()) return false;
return false;
CoordXZ test = (CoordXZ)obj; CoordXZ test = (CoordXZ) obj;
return test.x == this.x && test.z == this.z; return test.x == this.x && test.z == this.z;
} }
@Override @Override
public int hashCode() public int hashCode() {
{ return (this.x << 9) + this.z;
return (this.x << 9) + this.z; }
}
} }

View File

@ -1,251 +1,217 @@
package com.wimbli.WorldBorder; package com.wimbli.WorldBorder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.plugin.Plugin;
import org.dynmap.DynmapAPI; import org.dynmap.DynmapAPI;
import org.dynmap.markers.AreaMarker; import org.dynmap.markers.AreaMarker;
import org.dynmap.markers.CircleMarker; import org.dynmap.markers.CircleMarker;
import org.dynmap.markers.MarkerAPI; import org.dynmap.markers.MarkerAPI;
import org.dynmap.markers.MarkerSet; import org.dynmap.markers.MarkerSet;
import java.util.HashMap;
public class DynMapFeatures import java.util.List;
{ import java.util.Map;
private static DynmapAPI api; import java.util.Map.Entry;
private static MarkerAPI markApi;
private static MarkerSet markSet;
private static int lineWeight = 3;
private static double lineOpacity = 1.0;
private static int lineColor = 0xFF0000;
// Whether re-rendering functionality is available
public static boolean renderEnabled()
{
return api != null;
}
// Whether circular border markers are available
public static boolean borderEnabled()
{
return markApi != null;
}
public static void setup()
{
Plugin test = Bukkit.getServer().getPluginManager().getPlugin("dynmap");
if (test == null || !test.isEnabled()) return;
api = (DynmapAPI)test;
// make sure DynMap version is new enough to include circular markers
try
{
Class.forName("org.dynmap.markers.CircleMarker");
// for version 0.35 of DynMap, CircleMarkers had just been introduced and were bugged (center position always 0,0)
if (api.getDynmapVersion().startsWith("0.35-"))
throw new ClassNotFoundException();
}
catch (ClassNotFoundException ex)
{
Config.logConfig("DynMap is available, but border display is currently disabled: you need DynMap v0.36 or newer.");
return;
}
catch (NullPointerException ex)
{
Config.logConfig("DynMap is present, but an NPE (type 1) was encountered while trying to integrate. Border display disabled.");
return;
}
try
{
markApi = api.getMarkerAPI();
if (markApi == null) return;
}
catch (NullPointerException ex)
{
Config.logConfig("DynMap is present, but an NPE (type 2) was encountered while trying to integrate. Border display disabled.");
return;
}
// go ahead and show borders for all worlds
showAllBorders();
Config.logConfig("Successfully hooked into DynMap for the ability to display borders.");
}
/* public class DynMapFeatures {
* Re-rendering methods, used for updating trimmed chunks to show them as gone private static DynmapAPI api;
* Sadly, not currently working. Might not even be possible to make it work. private static MarkerAPI markApi;
*/ private static MarkerSet markSet;
private static int lineWeight = 3;
public static void renderRegion(String worldName, CoordXZ coord) private static double lineOpacity = 1.0;
{ private static int lineColor = 0xFF0000;
if (!renderEnabled()) return;
World world = Bukkit.getWorld(worldName);
int y = (world != null) ? world.getMaxHeight() : 255;
int x = CoordXZ.regionToBlock(coord.x);
int z = CoordXZ.regionToBlock(coord.z);
api.triggerRenderOfVolume(worldName, x, 0, z, x+511, y, z+511);
}
public static void renderChunks(String worldName, List<CoordXZ> coords)
{
if (!renderEnabled()) return;
World world = Bukkit.getWorld(worldName);
int y = (world != null) ? world.getMaxHeight() : 255;
for (CoordXZ coord : coords)
{
renderChunk(worldName, coord, y);
}
}
public static void renderChunk(String worldName, CoordXZ coord, int maxY)
{
if (!renderEnabled()) return;
int x = CoordXZ.chunkToBlock(coord.x);
int z = CoordXZ.chunkToBlock(coord.z);
api.triggerRenderOfVolume(worldName, x, 0, z, x+15, maxY, z+15);
}
/*
* Methods for displaying our borders on DynMap's world maps
*/
private static Map<String, CircleMarker> roundBorders = new HashMap<String, CircleMarker>(); private static Map<String, CircleMarker> roundBorders = new HashMap<String, CircleMarker>();
private static Map<String, AreaMarker> squareBorders = new HashMap<String, AreaMarker>(); private static Map<String, AreaMarker> squareBorders = new HashMap<String, AreaMarker>();
public static void showAllBorders() // Whether re-rendering functionality is available
{ public static boolean renderEnabled() {
if (!borderEnabled()) return; return api != null;
}
// in case any borders are already shown
removeAllBorders();
if (!Config.DynmapBorderEnabled()) /*
{ * Re-rendering methods, used for updating trimmed chunks to show them as gone
// don't want to show the marker set in DynMap if our integration is disabled * Sadly, not currently working. Might not even be possible to make it work.
if (markSet != null) */
markSet.deleteMarkerSet();
markSet = null;
return;
}
// make sure the marker set is initialized // Whether circular border markers are available
markSet = markApi.getMarkerSet("worldborder.markerset"); public static boolean borderEnabled() {
if(markSet == null) return markApi != null;
markSet = markApi.createMarkerSet("worldborder.markerset", "WorldBorder", null, false); }
else
markSet.setMarkerSetLabel("WorldBorder");
markSet.setLayerPriority(Config.DynmapPriority());
markSet.setHideByDefault(Config.DynmapHideByDefault());
Map<String, BorderData> borders = Config.getBorders();
for(Entry<String, BorderData> stringBorderDataEntry : borders.entrySet())
{
String worldName = stringBorderDataEntry.getKey();
BorderData border = stringBorderDataEntry.getValue();
showBorder(worldName, border);
}
}
public static void showBorder(String worldName, BorderData border) public static void setup() {
{ Plugin test = Bukkit.getServer().getPluginManager().getPlugin("dynmap");
if (!borderEnabled()) return; if (test == null || !test.isEnabled()) return;
if (!Config.DynmapBorderEnabled()) return; api = (DynmapAPI) test;
if ((border.getShape() == null) ? Config.ShapeRound() : border.getShape()) // make sure DynMap version is new enough to include circular markers
showRoundBorder(worldName, border); try {
else Class.forName("org.dynmap.markers.CircleMarker");
showSquareBorder(worldName, border);
}
private static void showRoundBorder(String worldName, BorderData border) // for version 0.35 of DynMap, CircleMarkers had just been introduced and were bugged (center position always 0,0)
{ if (api.getDynmapVersion().startsWith("0.35-"))
if (squareBorders.containsKey(worldName)) throw new ClassNotFoundException();
removeBorder(worldName); } catch (ClassNotFoundException ex) {
Config.logConfig("DynMap is available, but border display is currently disabled: you need DynMap v0.36 or newer.");
return;
} catch (NullPointerException ex) {
Config.logConfig("DynMap is present, but an NPE (type 1) was encountered while trying to integrate. Border display disabled.");
return;
}
World world = Bukkit.getWorld(worldName); try {
int y = (world != null) ? world.getMaxHeight() : 255; markApi = api.getMarkerAPI();
if (markApi == null) return;
} catch (NullPointerException ex) {
Config.logConfig("DynMap is present, but an NPE (type 2) was encountered while trying to integrate. Border display disabled.");
return;
}
CircleMarker marker = roundBorders.get(worldName); // go ahead and show borders for all worlds
if (marker == null) showAllBorders();
{
marker = markSet.createCircleMarker("worldborder_"+worldName, Config.DynmapMessage(), false, worldName, border.getX(), y, border.getZ(), border.getRadiusX(), border.getRadiusZ(), true);
marker.setLineStyle(lineWeight, lineOpacity, lineColor);
marker.setFillStyle(0.0, 0x000000);
roundBorders.put(worldName, marker);
}
else
{
marker.setCenter(worldName, border.getX(), y, border.getZ());
marker.setRadius(border.getRadiusX(), border.getRadiusZ());
}
}
private static void showSquareBorder(String worldName, BorderData border) Config.logConfig("Successfully hooked into DynMap for the ability to display borders.");
{ }
if (roundBorders.containsKey(worldName))
removeBorder(worldName);
// corners of the square border public static void renderRegion(String worldName, CoordXZ coord) {
double[] xVals = {border.getX() - border.getRadiusX(), border.getX() + border.getRadiusX()}; if (!renderEnabled()) return;
double[] zVals = {border.getZ() - border.getRadiusZ(), border.getZ() + border.getRadiusZ()};
AreaMarker marker = squareBorders.get(worldName); World world = Bukkit.getWorld(worldName);
if (marker == null) int y = (world != null) ? world.getMaxHeight() : 255;
{ int x = CoordXZ.regionToBlock(coord.x);
marker = markSet.createAreaMarker("worldborder_"+worldName, Config.DynmapMessage(), false, worldName, xVals, zVals, true); int z = CoordXZ.regionToBlock(coord.z);
marker.setLineStyle(3, 1.0, 0xFF0000); api.triggerRenderOfVolume(worldName, x, 0, z, x + 511, y, z + 511);
marker.setFillStyle(0.0, 0x000000); }
squareBorders.put(worldName, marker);
}
else
{
marker.setCornerLocations(xVals, zVals);
}
}
public static void removeAllBorders()
{
if (!borderEnabled()) return;
for(CircleMarker marker : roundBorders.values()) /*
{ * Methods for displaying our borders on DynMap's world maps
marker.deleteMarker(); */
}
roundBorders.clear();
for(AreaMarker marker : squareBorders.values()) public static void renderChunks(String worldName, List<CoordXZ> coords) {
{ if (!renderEnabled()) return;
marker.deleteMarker();
}
squareBorders.clear();
}
public static void removeBorder(String worldName) World world = Bukkit.getWorld(worldName);
{ int y = (world != null) ? world.getMaxHeight() : 255;
if (!borderEnabled()) return;
CircleMarker marker = roundBorders.remove(worldName); for (CoordXZ coord : coords) {
if (marker != null) renderChunk(worldName, coord, y);
marker.deleteMarker(); }
}
AreaMarker marker2 = squareBorders.remove(worldName); public static void renderChunk(String worldName, CoordXZ coord, int maxY) {
if (marker2 != null) if (!renderEnabled()) return;
marker2.deleteMarker();
} int x = CoordXZ.chunkToBlock(coord.x);
int z = CoordXZ.chunkToBlock(coord.z);
api.triggerRenderOfVolume(worldName, x, 0, z, x + 15, maxY, z + 15);
}
public static void showAllBorders() {
if (!borderEnabled()) return;
// in case any borders are already shown
removeAllBorders();
if (!Config.DynmapBorderEnabled()) {
// don't want to show the marker set in DynMap if our integration is disabled
if (markSet != null)
markSet.deleteMarkerSet();
markSet = null;
return;
}
// make sure the marker set is initialized
markSet = markApi.getMarkerSet("worldborder.markerset");
if (markSet == null)
markSet = markApi.createMarkerSet("worldborder.markerset", "WorldBorder", null, false);
else
markSet.setMarkerSetLabel("WorldBorder");
markSet.setLayerPriority(Config.DynmapPriority());
markSet.setHideByDefault(Config.DynmapHideByDefault());
Map<String, BorderData> borders = Config.getBorders();
for (Entry<String, BorderData> stringBorderDataEntry : borders.entrySet()) {
String worldName = stringBorderDataEntry.getKey();
BorderData border = stringBorderDataEntry.getValue();
showBorder(worldName, border);
}
}
public static void showBorder(String worldName, BorderData border) {
if (!borderEnabled()) return;
if (!Config.DynmapBorderEnabled()) return;
if ((border.getShape() == null) ? Config.ShapeRound() : border.getShape())
showRoundBorder(worldName, border);
else
showSquareBorder(worldName, border);
}
private static void showRoundBorder(String worldName, BorderData border) {
if (squareBorders.containsKey(worldName))
removeBorder(worldName);
World world = Bukkit.getWorld(worldName);
int y = (world != null) ? world.getMaxHeight() : 255;
CircleMarker marker = roundBorders.get(worldName);
if (marker == null) {
marker = markSet.createCircleMarker("worldborder_" + worldName, Config.DynmapMessage(), false, worldName, border.getX(), y, border.getZ(), border.getRadiusX(), border.getRadiusZ(), true);
marker.setLineStyle(lineWeight, lineOpacity, lineColor);
marker.setFillStyle(0.0, 0x000000);
roundBorders.put(worldName, marker);
} else {
marker.setCenter(worldName, border.getX(), y, border.getZ());
marker.setRadius(border.getRadiusX(), border.getRadiusZ());
}
}
private static void showSquareBorder(String worldName, BorderData border) {
if (roundBorders.containsKey(worldName))
removeBorder(worldName);
// corners of the square border
double[] xVals = {border.getX() - border.getRadiusX(), border.getX() + border.getRadiusX()};
double[] zVals = {border.getZ() - border.getRadiusZ(), border.getZ() + border.getRadiusZ()};
AreaMarker marker = squareBorders.get(worldName);
if (marker == null) {
marker = markSet.createAreaMarker("worldborder_" + worldName, Config.DynmapMessage(), false, worldName, xVals, zVals, true);
marker.setLineStyle(3, 1.0, 0xFF0000);
marker.setFillStyle(0.0, 0x000000);
squareBorders.put(worldName, marker);
} else {
marker.setCornerLocations(xVals, zVals);
}
}
public static void removeAllBorders() {
if (!borderEnabled()) return;
for (CircleMarker marker : roundBorders.values()) {
marker.deleteMarker();
}
roundBorders.clear();
for (AreaMarker marker : squareBorders.values()) {
marker.deleteMarker();
}
squareBorders.clear();
}
public static void removeBorder(String worldName) {
if (!borderEnabled()) return;
CircleMarker marker = roundBorders.remove(worldName);
if (marker != null)
marker.deleteMarker();
AreaMarker marker2 = squareBorders.remove(worldName);
if (marker2 != null)
marker2.deleteMarker();
}
} }

View File

@ -1,42 +1,36 @@
package com.wimbli.WorldBorder.Events; package com.wimbli.WorldBorder.Events;
import org.bukkit.World;
import org.bukkit.event.Event; import org.bukkit.event.Event;
import org.bukkit.event.HandlerList; import org.bukkit.event.HandlerList;
import org.bukkit.World;
/** /**
* Created by timafh on 04.09.2015. * Created by timafh on 04.09.2015.
*/ */
public class WorldBorderFillFinishedEvent extends Event public class WorldBorderFillFinishedEvent extends Event {
{ private static final HandlerList handlers = new HandlerList();
private static final HandlerList handlers = new HandlerList(); private World world;
private World world; private long totalChunks;
private long totalChunks;
public WorldBorderFillFinishedEvent(World world, long totalChunks) public WorldBorderFillFinishedEvent(World world, long totalChunks) {
{ this.world = world;
this.world = world; this.totalChunks = totalChunks;
this.totalChunks = totalChunks; }
}
@Override public static HandlerList getHandlerList() {
public HandlerList getHandlers() return handlers;
{ }
return handlers;
}
public static HandlerList getHandlerList() @Override
{ public HandlerList getHandlers() {
return handlers; return handlers;
} }
public World getWorld() public World getWorld() {
{ return world;
return world; }
}
public long getTotalChunks() public long getTotalChunks() {
{ return totalChunks;
return totalChunks; }
}
} }

View File

@ -1,36 +1,31 @@
package com.wimbli.WorldBorder.Events; package com.wimbli.WorldBorder.Events;
import com.wimbli.WorldBorder.WorldFillTask;
import org.bukkit.event.Event; import org.bukkit.event.Event;
import org.bukkit.event.HandlerList; import org.bukkit.event.HandlerList;
import com.wimbli.WorldBorder.WorldFillTask;
/** /**
* Created by Maximvdw on 12.01.2016. * Created by Maximvdw on 12.01.2016.
*/ */
public class WorldBorderFillStartEvent extends Event public class WorldBorderFillStartEvent extends Event {
{ private static final HandlerList handlers = new HandlerList();
private static final HandlerList handlers = new HandlerList(); private WorldFillTask fillTask;
private WorldFillTask fillTask;
public WorldBorderFillStartEvent(WorldFillTask worldFillTask) public WorldBorderFillStartEvent(WorldFillTask worldFillTask) {
{ this.fillTask = worldFillTask;
this.fillTask = worldFillTask; }
}
@Override public static HandlerList getHandlerList() {
public HandlerList getHandlers() return handlers;
{ }
return handlers;
}
public static HandlerList getHandlerList() @Override
{ public HandlerList getHandlers() {
return handlers; return handlers;
} }
public WorldFillTask getFillTask(){ public WorldFillTask getFillTask() {
return this.fillTask; return this.fillTask;
} }
} }

View File

@ -1,42 +1,36 @@
package com.wimbli.WorldBorder.Events; package com.wimbli.WorldBorder.Events;
import org.bukkit.World;
import org.bukkit.event.Event; import org.bukkit.event.Event;
import org.bukkit.event.HandlerList; import org.bukkit.event.HandlerList;
import org.bukkit.World;
/** /**
* Created by timafh on 04.09.2015. * Created by timafh on 04.09.2015.
*/ */
public class WorldBorderTrimFinishedEvent extends Event public class WorldBorderTrimFinishedEvent extends Event {
{ private static final HandlerList handlers = new HandlerList();
private static final HandlerList handlers = new HandlerList(); private World world;
private World world; private long totalChunks;
private long totalChunks;
public WorldBorderTrimFinishedEvent(World world, long totalChunks) public WorldBorderTrimFinishedEvent(World world, long totalChunks) {
{ this.world = world;
this.world = world; this.totalChunks = totalChunks;
this.totalChunks = totalChunks; }
}
@Override public static HandlerList getHandlerList() {
public HandlerList getHandlers() return handlers;
{ }
return handlers;
}
public static HandlerList getHandlerList() @Override
{ public HandlerList getHandlers() {
return handlers; return handlers;
} }
public World getWorld() public World getWorld() {
{ return world;
return world; }
}
public long getTotalChunks() public long getTotalChunks() {
{ return totalChunks;
return totalChunks; }
}
} }

View File

@ -1,36 +1,31 @@
package com.wimbli.WorldBorder.Events; package com.wimbli.WorldBorder.Events;
import com.wimbli.WorldBorder.WorldTrimTask;
import org.bukkit.event.Event; import org.bukkit.event.Event;
import org.bukkit.event.HandlerList; import org.bukkit.event.HandlerList;
import com.wimbli.WorldBorder.WorldTrimTask;
/** /**
* Created by Maximvdw on 12.01.2016. * Created by Maximvdw on 12.01.2016.
*/ */
public class WorldBorderTrimStartEvent extends Event public class WorldBorderTrimStartEvent extends Event {
{ private static final HandlerList handlers = new HandlerList();
private static final HandlerList handlers = new HandlerList(); private WorldTrimTask trimTask;
private WorldTrimTask trimTask;
public WorldBorderTrimStartEvent(WorldTrimTask trimTask) public WorldBorderTrimStartEvent(WorldTrimTask trimTask) {
{ this.trimTask = trimTask;
this.trimTask = trimTask; }
}
@Override public static HandlerList getHandlerList() {
public HandlerList getHandlers() return handlers;
{ }
return handlers;
}
public static HandlerList getHandlerList() @Override
{ public HandlerList getHandlers() {
return handlers; return handlers;
} }
public WorldTrimTask getTrimTask(){ public WorldTrimTask getTrimTask() {
return this.trimTask; return this.trimTask;
} }
} }

View File

@ -9,27 +9,23 @@ import org.bukkit.event.Listener;
import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.event.entity.CreatureSpawnEvent;
public class MobSpawnListener implements Listener public class MobSpawnListener implements Listener {
{ @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onCreatureSpawn(CreatureSpawnEvent event) {
public void onCreatureSpawn(CreatureSpawnEvent event) Location loc = event.getEntity().getLocation();
{ if (loc == null) return;
Location loc = event.getEntity().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()))
{
event.setCancelled(true);
}
}
public void unregister() World world = loc.getWorld();
{ if (world == null) return;
HandlerList.unregisterAll(this); BorderData border = Config.Border(world.getName());
} if (border == null) return;
if (!border.insideBorder(loc.getX(), loc.getZ(), Config.ShapeRound())) {
event.setCancelled(true);
}
}
public void unregister() {
HandlerList.unregisterAll(this);
}
} }

View File

@ -4,6 +4,9 @@
package com.wimbli.WorldBorder.UUID; package com.wimbli.WorldBorder.UUID;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
@ -16,134 +19,127 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.function.Consumer; import java.util.function.Consumer;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class UUIDFetcher { public class UUIDFetcher {
/**
* Date when name changes were introduced
* @see UUIDFetcher#getUUIDAt(String, long)
*/
public static final long FEBRUARY_2015 = 1422748800000L;
private static Gson gson = new GsonBuilder().registerTypeAdapter(UUID.class, new UUIDTypeAdapter()).create();
private static final String UUID_URL = "https://api.mojang.com/users/profiles/minecraft/%s?at=%d";
private static final String NAME_URL = "https://api.mojang.com/user/profiles/%s/names";
private static Map<String, UUID> uuidCache = new HashMap<String, UUID>(); /**
private static Map<UUID, String> nameCache = new HashMap<UUID, String>(); * Date when name changes were introduced
*
* @see UUIDFetcher#getUUIDAt(String, long)
*/
public static final long FEBRUARY_2015 = 1422748800000L;
private static final String UUID_URL = "https://api.mojang.com/users/profiles/minecraft/%s?at=%d";
private static final String NAME_URL = "https://api.mojang.com/user/profiles/%s/names";
private static Gson gson = new GsonBuilder().registerTypeAdapter(UUID.class, new UUIDTypeAdapter()).create();
private static Map<String, UUID> uuidCache = new HashMap<String, UUID>();
private static Map<UUID, String> nameCache = new HashMap<UUID, String>();
private static ExecutorService pool = Executors.newCachedThreadPool(); private static ExecutorService pool = Executors.newCachedThreadPool();
private String name;
private UUID id;
/**
* Fetches the uuid asynchronously and passes it to the consumer
*
* @param name The name
* @param action Do what you want to do with the uuid her
*/
public static void getUUID(String name, Consumer<UUID> action) {
pool.execute(() -> action.accept(getUUID(name)));
}
/**
* Fetches the uuid synchronously and returns it
*
* @param name The name
* @return The uuid
*/
public static UUID getUUID(String name) {
return getUUIDAt(name, System.currentTimeMillis());
}
/**
* Fetches the uuid synchronously for a specified name and time and passes the result to the consumer
*
* @param name The name
* @param timestamp Time when the player had this name in milliseconds
* @param action Do what you want to do with the uuid her
*/
public static void getUUIDAt(String name, long timestamp, Consumer<UUID> action) {
pool.execute(() -> action.accept(getUUIDAt(name, timestamp)));
}
/**
* Fetches the uuid synchronously for a specified name and time
*
* @param name The name
* @param timestamp Time when the player had this name in milliseconds
* @see UUIDFetcher#FEBRUARY_2015
*/
public static UUID getUUIDAt(String name, long timestamp) {
name = name.toLowerCase();
if (uuidCache.containsKey(name)) {
return uuidCache.get(name);
}
try {
HttpURLConnection connection = (HttpURLConnection) new URL(String.format(UUID_URL, name, timestamp/1000)).openConnection();
connection.setReadTimeout(5000);
UUIDFetcher data = gson.fromJson(new BufferedReader(new InputStreamReader(connection.getInputStream())), UUIDFetcher.class);
uuidCache.put(name, data.id);
nameCache.put(data.id, data.name);
return data.id; private String name;
} catch (Exception e) { private UUID id;
e.printStackTrace();
}
return null;
}
/**
* Fetches the name asynchronously and passes it to the consumer
*
* @param uuid The uuid
* @param action Do what you want to do with the name her
*/
public static void getName(UUID uuid, Consumer<String> action) {
pool.execute(() -> action.accept(getName(uuid)));
}
/** /**
* Fetches the name synchronously and returns it * Fetches the uuid asynchronously and passes it to the consumer
* *
* @param uuid The uuid * @param name The name
* @return The name * @param action Do what you want to do with the uuid her
*/ */
public static String getName(UUID uuid) { public static void getUUID(String name, Consumer<UUID> action) {
if (nameCache.containsKey(uuid)) { pool.execute(() -> action.accept(getUUID(name)));
return nameCache.get(uuid); }
}
try {
HttpURLConnection connection = (HttpURLConnection) new URL(String.format(NAME_URL, UUIDTypeAdapter.fromUUID(uuid))).openConnection();
connection.setReadTimeout(5000);
UUIDFetcher[] nameHistory = gson.fromJson(new BufferedReader(new InputStreamReader(connection.getInputStream())), UUIDFetcher[].class);
UUIDFetcher currentNameData = nameHistory[nameHistory.length - 1];
uuidCache.put(currentNameData.name.toLowerCase(), uuid); /**
nameCache.put(uuid, currentNameData.name); * Fetches the uuid synchronously and returns it
*
* @param name The name
* @return The uuid
*/
public static UUID getUUID(String name) {
return getUUIDAt(name, System.currentTimeMillis());
}
return currentNameData.name; /**
} catch (Exception e) { * Fetches the uuid synchronously for a specified name and time and passes the result to the consumer
e.printStackTrace(); *
} * @param name The name
* @param timestamp Time when the player had this name in milliseconds
* @param action Do what you want to do with the uuid her
*/
public static void getUUIDAt(String name, long timestamp, Consumer<UUID> action) {
pool.execute(() -> action.accept(getUUIDAt(name, timestamp)));
}
return null; /**
} * Fetches the uuid synchronously for a specified name and time
*
* @param name The name
* @param timestamp Time when the player had this name in milliseconds
* @see UUIDFetcher#FEBRUARY_2015
*/
public static UUID getUUIDAt(String name, long timestamp) {
name = name.toLowerCase();
if (uuidCache.containsKey(name)) {
return uuidCache.get(name);
}
try {
HttpURLConnection connection = (HttpURLConnection) new URL(String.format(UUID_URL, name, timestamp / 1000)).openConnection();
connection.setReadTimeout(5000);
UUIDFetcher data = gson.fromJson(new BufferedReader(new InputStreamReader(connection.getInputStream())), UUIDFetcher.class);
public static Map<UUID, String> getNameList(ArrayList<UUID> uuids) { uuidCache.put(name, data.id);
Map<UUID, String> uuidStringMap = new HashMap<>(); nameCache.put(data.id, data.name);
for (UUID uuid: uuids)
{ return data.id;
uuidStringMap.put(uuid, getName(uuid)); } catch (Exception e) {
} e.printStackTrace();
return uuidStringMap; }
}
return null;
}
/**
* Fetches the name asynchronously and passes it to the consumer
*
* @param uuid The uuid
* @param action Do what you want to do with the name her
*/
public static void getName(UUID uuid, Consumer<String> action) {
pool.execute(() -> action.accept(getName(uuid)));
}
/**
* Fetches the name synchronously and returns it
*
* @param uuid The uuid
* @return The name
*/
public static String getName(UUID uuid) {
if (nameCache.containsKey(uuid)) {
return nameCache.get(uuid);
}
try {
HttpURLConnection connection = (HttpURLConnection) new URL(String.format(NAME_URL, UUIDTypeAdapter.fromUUID(uuid))).openConnection();
connection.setReadTimeout(5000);
UUIDFetcher[] nameHistory = gson.fromJson(new BufferedReader(new InputStreamReader(connection.getInputStream())), UUIDFetcher[].class);
UUIDFetcher currentNameData = nameHistory[nameHistory.length - 1];
uuidCache.put(currentNameData.name.toLowerCase(), uuid);
nameCache.put(uuid, currentNameData.name);
return currentNameData.name;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static Map<UUID, String> getNameList(ArrayList<UUID> uuids) {
Map<UUID, String> uuidStringMap = new HashMap<>();
for (UUID uuid : uuids) {
uuidStringMap.put(uuid, getName(uuid));
}
return uuidStringMap;
}
} }

View File

@ -4,29 +4,29 @@
package com.wimbli.WorldBorder.UUID; package com.wimbli.WorldBorder.UUID;
import java.io.IOException;
import java.util.UUID;
import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter; import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.util.UUID;
public class UUIDTypeAdapter extends TypeAdapter<UUID> { public class UUIDTypeAdapter extends TypeAdapter<UUID> {
public void write(JsonWriter out, UUID value) throws IOException { public static String fromUUID(UUID value) {
out.value(fromUUID(value)); return value.toString().replace("-", "");
} }
public UUID read(JsonReader in) throws IOException { public static UUID fromString(String input) {
return fromString(in.nextString()); return UUID.fromString(input.replaceFirst(
} "(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", "$1-$2-$3-$4-$5"));
}
public static String fromUUID(UUID value) { public void write(JsonWriter out, UUID value) throws IOException {
return value.toString().replace("-", ""); out.value(fromUUID(value));
} }
public static UUID fromString(String input) { public UUID read(JsonReader in) throws IOException {
return UUID.fromString(input.replaceFirst( return fromString(in.nextString());
"(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", "$1-$2-$3-$4-$5")); }
}
} }

View File

@ -1,213 +1,184 @@
package com.wimbli.WorldBorder; package com.wimbli.WorldBorder;
import java.util.ArrayList; import com.wimbli.WorldBorder.cmd.*;
import java.util.Arrays; import org.bukkit.command.Command;
import java.util.Iterator; import org.bukkit.command.CommandExecutor;
import java.util.LinkedHashMap; import org.bukkit.command.CommandSender;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.bukkit.command.*;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import com.wimbli.WorldBorder.cmd.*; import java.util.*;
public class WBCommand implements CommandExecutor public class WBCommand implements CommandExecutor {
{ // map of all sub-commands with the command name (string) for quick reference
// map of all sub-commands with the command name (string) for quick reference public Map<String, WBCmd> subCommands = new LinkedHashMap<String, WBCmd>();
public Map<String, WBCmd> subCommands = new LinkedHashMap<String, WBCmd>(); // ref. list of the commands which can have a world name in front of the command itself (ex. /wb _world_ radius 100)
// ref. list of the commands which can have a world name in front of the command itself (ex. /wb _world_ radius 100) private Set<String> subCommandsWithWorldNames = new LinkedHashSet<String>();
private Set<String> subCommandsWithWorldNames = new LinkedHashSet<String>(); private boolean wasWorldQuotation = false;
// constructor
public WBCommand ()
{
addCmd(new CmdHelp()); // 1 example
addCmd(new CmdSet()); // 4 examples for player, 3 for console
addCmd(new CmdSetcorners()); // 1
addCmd(new CmdRadius()); // 1
addCmd(new CmdList()); // 1
//----- 8 per page of examples
addCmd(new CmdShape()); // 2
addCmd(new CmdClear()); // 2
addCmd(new CmdFill()); // 1
addCmd(new CmdTrim()); // 1
addCmd(new CmdBypass()); // 1
addCmd(new CmdBypasslist()); // 1
//-----
addCmd(new CmdKnockback()); // 1
addCmd(new CmdWrap()); // 1
addCmd(new CmdWhoosh()); // 1
addCmd(new CmdGetmsg()); // 1
addCmd(new CmdSetmsg()); // 1
addCmd(new CmdWshape()); // 3
//-----
addCmd(new CmdPreventPlace()); // 1
addCmd(new CmdPreventSpawn()); // 1
addCmd(new CmdDelay()); // 1
addCmd(new CmdDynmap()); // 1
addCmd(new CmdDynmapmsg()); // 1
addCmd(new CmdRemount()); // 1
addCmd(new CmdFillautosave()); // 1
addCmd(new CmdPortal()); // 1
//-----
addCmd(new CmdDenypearl()); // 1
addCmd(new CmdReload()); // 1
addCmd(new CmdDebug()); // 1
// this is the default command, which shows command example pages; should be last just in case
addCmd(new CmdCommands());
}
private void addCmd(WBCmd cmd) // constructor
{ public WBCommand() {
subCommands.put(cmd.name, cmd); addCmd(new CmdHelp()); // 1 example
if (cmd.hasWorldNameInput) addCmd(new CmdSet()); // 4 examples for player, 3 for console
subCommandsWithWorldNames.add(cmd.name); addCmd(new CmdSetcorners()); // 1
} addCmd(new CmdRadius()); // 1
addCmd(new CmdList()); // 1
//----- 8 per page of examples
addCmd(new CmdShape()); // 2
addCmd(new CmdClear()); // 2
addCmd(new CmdFill()); // 1
addCmd(new CmdTrim()); // 1
addCmd(new CmdBypass()); // 1
addCmd(new CmdBypasslist()); // 1
//-----
addCmd(new CmdKnockback()); // 1
addCmd(new CmdWrap()); // 1
addCmd(new CmdWhoosh()); // 1
addCmd(new CmdGetmsg()); // 1
addCmd(new CmdSetmsg()); // 1
addCmd(new CmdWshape()); // 3
//-----
addCmd(new CmdPreventPlace()); // 1
addCmd(new CmdPreventSpawn()); // 1
addCmd(new CmdDelay()); // 1
addCmd(new CmdDynmap()); // 1
addCmd(new CmdDynmapmsg()); // 1
addCmd(new CmdRemount()); // 1
addCmd(new CmdFillautosave()); // 1
addCmd(new CmdPortal()); // 1
//-----
addCmd(new CmdDenypearl()); // 1
addCmd(new CmdReload()); // 1
addCmd(new CmdDebug()); // 1
// this is the default command, which shows command example pages; should be last just in case
addCmd(new CmdCommands());
}
private void addCmd(WBCmd cmd) {
subCommands.put(cmd.name, cmd);
if (cmd.hasWorldNameInput)
subCommandsWithWorldNames.add(cmd.name);
}
@Override @Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] split) public boolean onCommand(CommandSender sender, Command command, String label, String[] split) {
{ Player player = (sender instanceof Player) ? (Player) sender : null;
Player player = (sender instanceof Player) ? (Player)sender : null;
// if world name is passed inside quotation marks, handle that, and get List<String> instead of String[] // if world name is passed inside quotation marks, handle that, and get List<String> instead of String[]
List<String> params = concatenateQuotedWorldName(split); List<String> params = concatenateQuotedWorldName(split);
String worldName = null; String worldName = null;
// is second parameter the command and first parameter a world name? definitely world name if it was in quotation marks // is second parameter the command and first parameter a world name? definitely world name if it was in quotation marks
if (wasWorldQuotation || (params.size() > 1 && !subCommands.containsKey(params.get(0)) && subCommandsWithWorldNames.contains(params.get(1)))) if (wasWorldQuotation || (params.size() > 1 && !subCommands.containsKey(params.get(0)) && subCommandsWithWorldNames.contains(params.get(1))))
worldName = params.get(0); worldName = params.get(0);
// no command specified? show command examples / help // no command specified? show command examples / help
if (params.isEmpty()) if (params.isEmpty())
params.add(0, "commands"); params.add(0, "commands");
// determined the command name // determined the command name
String cmdName = (worldName == null) ? params.get(0).toLowerCase() : params.get(1).toLowerCase(); String cmdName = (worldName == null) ? params.get(0).toLowerCase() : params.get(1).toLowerCase();
// remove command name and (if there) world name from front of param array // remove command name and (if there) world name from front of param array
params.remove(0); params.remove(0);
if (worldName != null) if (worldName != null)
params.remove(0); params.remove(0);
// make sure command is recognized, default to showing command examples / help if not; also check for specified page number // make sure command is recognized, default to showing command examples / help if not; also check for specified page number
if (!subCommands.containsKey(cmdName)) if (!subCommands.containsKey(cmdName)) {
{ int page = (player == null) ? 0 : 1;
int page = (player == null) ? 0 : 1; try {
try page = Integer.parseInt(cmdName);
{ } catch (NumberFormatException ignored) {
page = Integer.parseInt(cmdName); sender.sendMessage(WBCmd.C_ERR + "Command not recognized. Showing command list.");
} }
catch(NumberFormatException ignored) cmdName = "commands";
{ params.add(0, Integer.toString(page));
sender.sendMessage(WBCmd.C_ERR + "Command not recognized. Showing command list."); }
}
cmdName = "commands";
params.add(0, Integer.toString(page));
}
WBCmd subCommand = subCommands.get(cmdName); WBCmd subCommand = subCommands.get(cmdName);
// check permission // check permission
if (!Config.HasPermission(player, subCommand.permission)) if (!Config.HasPermission(player, subCommand.permission))
return true; return true;
// if command requires world name when run by console, make sure that's in place // if command requires world name when run by console, make sure that's in place
if (player == null && subCommand.hasWorldNameInput && subCommand.consoleRequiresWorldName && worldName == null) if (player == null && subCommand.hasWorldNameInput && subCommand.consoleRequiresWorldName && worldName == null) {
{ sender.sendMessage(WBCmd.C_ERR + "This command requires a world to be specified if run by the console.");
sender.sendMessage(WBCmd.C_ERR + "This command requires a world to be specified if run by the console."); subCommand.sendCmdHelp(sender);
subCommand.sendCmdHelp(sender); return true;
return true; }
}
// make sure valid number of parameters has been provided // make sure valid number of parameters has been provided
if (params.size() < subCommand.minParams || params.size() > subCommand.maxParams) if (params.size() < subCommand.minParams || params.size() > subCommand.maxParams) {
{ if (subCommand.maxParams == 0)
if (subCommand.maxParams == 0) sender.sendMessage(WBCmd.C_ERR + "This command does not accept any parameters.");
sender.sendMessage(WBCmd.C_ERR + "This command does not accept any parameters."); else
else sender.sendMessage(WBCmd.C_ERR + "You have not provided a valid number of parameters.");
sender.sendMessage(WBCmd.C_ERR + "You have not provided a valid number of parameters."); subCommand.sendCmdHelp(sender);
subCommand.sendCmdHelp(sender); return true;
return true; }
}
// execute command // execute command
subCommand.execute(sender, player, params, worldName); subCommand.execute(sender, player, params, worldName);
return true; return true;
} }
// if world name is surrounded by quotation marks, combine it down and flag wasWorldQuotation if it's first param.
// also return List<String> instead of input primitive String[]
private List<String> concatenateQuotedWorldName(String[] split) {
wasWorldQuotation = false;
List<String> args = new ArrayList<String>(Arrays.asList(split));
private boolean wasWorldQuotation = false; int startIndex = -1;
for (int i = 0; i < args.size(); i++) {
if (args.get(i).startsWith("\"")) {
startIndex = i;
break;
}
}
if (startIndex == -1)
return args;
// if world name is surrounded by quotation marks, combine it down and flag wasWorldQuotation if it's first param. if (args.get(startIndex).endsWith("\"")) {
// also return List<String> instead of input primitive String[] args.set(startIndex, args.get(startIndex).substring(1, args.get(startIndex).length() - 1));
private List<String> concatenateQuotedWorldName(String[] split) if (startIndex == 0)
{ wasWorldQuotation = true;
wasWorldQuotation = false; } else {
List<String> args = new ArrayList<String>(Arrays.asList(split)); List<String> concat = new ArrayList<String>(args);
Iterator<String> concatI = concat.iterator();
int startIndex = -1; // skip past any parameters in front of the one we're starting on
for (int i = 0; i < args.size(); i++) for (int i = 1; i < startIndex + 1; i++) {
{ concatI.next();
if (args.get(i).startsWith("\"")) }
{
startIndex = i;
break;
}
}
if (startIndex == -1)
return args;
if (args.get(startIndex).endsWith("\"")) StringBuilder quote = new StringBuilder(concatI.next());
{ while (concatI.hasNext()) {
args.set(startIndex, args.get(startIndex).substring(1, args.get(startIndex).length() - 1)); String next = concatI.next();
if (startIndex == 0) concatI.remove();
wasWorldQuotation = true; quote.append(" ");
} quote.append(next);
else if (next.endsWith("\"")) {
{ concat.set(startIndex, quote.substring(1, quote.length() - 1));
List<String> concat = new ArrayList<String>(args); args = concat;
Iterator<String> concatI = concat.iterator(); if (startIndex == 0)
wasWorldQuotation = true;
break;
}
}
}
return args;
}
// skip past any parameters in front of the one we're starting on public Set<String> getCommandNames() {
for (int i = 1; i < startIndex + 1; i++) // using TreeSet to sort alphabetically
{ Set<String> commands = new TreeSet<>(subCommands.keySet());
concatI.next(); // removing default "commands" command as it's not normally shown or run like other commands
} commands.remove("commands");
return commands;
StringBuilder quote = new StringBuilder(concatI.next()); }
while (concatI.hasNext())
{
String next = concatI.next();
concatI.remove();
quote.append(" ");
quote.append(next);
if (next.endsWith("\""))
{
concat.set(startIndex, quote.substring(1, quote.length() - 1));
args = concat;
if (startIndex == 0)
wasWorldQuotation = true;
break;
}
}
}
return args;
}
public Set<String> getCommandNames()
{
// using TreeSet to sort alphabetically
Set<String> commands = new TreeSet<>(subCommands.keySet());
// removing default "commands" command as it's not normally shown or run like other commands
commands.remove("commands");
return commands;
}
} }

View File

@ -1,56 +1,50 @@
package com.wimbli.WorldBorder; package com.wimbli.WorldBorder;
import org.bukkit.Chunk; import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority; import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.event.player.PlayerPortalEvent; import org.bukkit.event.player.PlayerPortalEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.event.world.ChunkLoadEvent; import org.bukkit.event.world.ChunkLoadEvent;
import org.bukkit.event.world.ChunkUnloadEvent; import org.bukkit.event.world.ChunkUnloadEvent;
import org.bukkit.Location;
public class WBListener implements Listener public class WBListener implements Listener {
{ @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onPlayerTeleport(PlayerTeleportEvent event) {
public void onPlayerTeleport(PlayerTeleportEvent event) // if knockback is set to 0, simply return
{ if (Config.KnockBack() == 0.0)
// if knockback is set to 0, simply return return;
if (Config.KnockBack() == 0.0)
return;
if (Config.Debug()) if (Config.Debug())
Config.log("Teleport cause: " + event.getCause().toString()); Config.log("Teleport cause: " + event.getCause().toString());
Location newLoc = BorderCheckTask.checkPlayer(event.getPlayer(), event.getTo(), true, true); Location newLoc = BorderCheckTask.checkPlayer(event.getPlayer(), event.getTo(), true, true);
if (newLoc != null) if (newLoc != null) {
{ if (event.getCause() == PlayerTeleportEvent.TeleportCause.ENDER_PEARL && Config.getDenyEnderpearl()) {
if(event.getCause() == PlayerTeleportEvent.TeleportCause.ENDER_PEARL && Config.getDenyEnderpearl()) event.setCancelled(true);
{ return;
event.setCancelled(true); }
return;
}
event.setTo(newLoc); event.setTo(newLoc);
} }
} }
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void onPlayerPortal(PlayerPortalEvent event) public void onPlayerPortal(PlayerPortalEvent event) {
{ // if knockback is set to 0, or portal redirection is disabled, simply return
// if knockback is set to 0, or portal redirection is disabled, simply return if (Config.KnockBack() == 0.0 || !Config.portalRedirection())
if (Config.KnockBack() == 0.0 || !Config.portalRedirection()) return;
return;
Location newLoc = BorderCheckTask.checkPlayer(event.getPlayer(), event.getTo(), true, false); Location newLoc = BorderCheckTask.checkPlayer(event.getPlayer(), event.getTo(), true, false);
if (newLoc != null) if (newLoc != null)
event.setTo(newLoc); event.setTo(newLoc);
} }
@EventHandler(priority = EventPriority.MONITOR) @EventHandler(priority = EventPriority.MONITOR)
public void onChunkLoad(ChunkLoadEvent event) public void onChunkLoad(ChunkLoadEvent event) {
{
/* // tested, found to spam pretty rapidly as client repeatedly requests the same chunks since they're not being sent /* // tested, found to spam pretty rapidly as client repeatedly requests the same chunks since they're not being sent
// definitely too spammy at only 16 blocks outside border // definitely too spammy at only 16 blocks outside border
// potentially useful at standard 208 block padding as it was triggering only occasionally while trying to get out all along edge of round border, though sometimes up to 3 triggers within a second corresponding to 3 adjacent chunks // potentially useful at standard 208 block padding as it was triggering only occasionally while trying to get out all along edge of round border, though sometimes up to 3 triggers within a second corresponding to 3 adjacent chunks
@ -64,30 +58,29 @@ public class WBListener implements Listener
Config.logWarn("New chunk generation has been prevented at X " + chunk.getX() + ", Z " + chunk.getZ()); Config.logWarn("New chunk generation has been prevented at X " + chunk.getX() + ", Z " + chunk.getZ());
} }
*/ */
// make sure our border monitoring task is still running like it should // make sure our border monitoring task is still running like it should
if (Config.isBorderTimerRunning()) return; if (Config.isBorderTimerRunning()) return;
Config.logWarn("Border-checking task was not running! Something on your server apparently killed it. It will now be restarted."); Config.logWarn("Border-checking task was not running! Something on your server apparently killed it. It will now be restarted.");
Config.StartBorderTimer(); Config.StartBorderTimer();
} }
/* /*
* Check if there is a fill task running, and if yes, if it's for the * Check if there is a fill task running, and if yes, if it's for the
* world that the unload event refers to, set "force loaded" flag off * world that the unload event refers to, set "force loaded" flag off
* and track if chunk was somehow on unload prevention list * and track if chunk was somehow on unload prevention list
*/ */
@EventHandler @EventHandler
public void onChunkUnload(ChunkUnloadEvent e) public void onChunkUnload(ChunkUnloadEvent e) {
{ if (Config.fillTask == null)
if (Config.fillTask == null) return;
return;
Chunk chunk = e.getChunk(); Chunk chunk = e.getChunk();
if (e.getWorld() != Config.fillTask.getWorld()) if (e.getWorld() != Config.fillTask.getWorld())
return; return;
// just to be on the safe side, in case it's still set at this point somehow // just to be on the safe side, in case it's still set at this point somehow
chunk.setForceLoaded(false); chunk.setForceLoaded(false);
} }
} }

View File

@ -4,81 +4,74 @@ import org.bukkit.Location;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
public class WorldBorder extends JavaPlugin public class WorldBorder extends JavaPlugin {
{ public static volatile WorldBorder plugin = null;
public static volatile WorldBorder plugin = null; public static volatile WBCommand wbCommand = null;
public static volatile WBCommand wbCommand = null; private BlockPlaceListener blockPlaceListener = null;
private BlockPlaceListener blockPlaceListener = null; private MobSpawnListener mobSpawnListener = null;
private MobSpawnListener mobSpawnListener = null;
@Override @Override
public void onEnable() public void onEnable() {
{ if (plugin == null)
if (plugin == null) plugin = this;
plugin = this; if (wbCommand == null)
if (wbCommand == null) wbCommand = new WBCommand();
wbCommand = new WBCommand();
// Load (or create new) config file // Load (or create new) config file
Config.load(this, false); Config.load(this, false);
// our one real command, though it does also have aliases "wb" and "worldborder" // our one real command, though it does also have aliases "wb" and "worldborder"
getCommand("wborder").setExecutor(wbCommand); getCommand("wborder").setExecutor(wbCommand);
// keep an eye on teleports, to redirect them to a spot inside the border if necessary // keep an eye on teleports, to redirect them to a spot inside the border if necessary
getServer().getPluginManager().registerEvents(new WBListener(), this); getServer().getPluginManager().registerEvents(new WBListener(), this);
if (Config.preventBlockPlace())
enableBlockPlaceListener(true);
if (Config.preventMobSpawn()) if (Config.preventBlockPlace())
enableMobSpawnListener(true); enableBlockPlaceListener(true);
// integrate with DynMap if it's available if (Config.preventMobSpawn())
DynMapFeatures.setup(); enableMobSpawnListener(true);
// Well I for one find this info useful, so... // integrate with DynMap if it's available
Location spawn = getServer().getWorlds().get(0).getSpawnLocation(); DynMapFeatures.setup();
Config.log("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()));
}
@Override // Well I for one find this info useful, so...
public void onDisable() Location spawn = getServer().getWorlds().get(0).getSpawnLocation();
{ Config.log("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()));
DynMapFeatures.removeAllBorders(); }
Config.StopBorderTimer();
Config.StoreFillTask();
Config.StopFillTask();
}
// for other plugins to hook into @Override
public BorderData getWorldBorder(String worldName) public void onDisable() {
{ DynMapFeatures.removeAllBorders();
return Config.Border(worldName); Config.StopBorderTimer();
} Config.StoreFillTask();
Config.StopFillTask();
}
/** // for other plugins to hook into
* @deprecated Replaced by {@link #getWorldBorder(String worldName)}; public BorderData getWorldBorder(String worldName) {
* this method name starts with an uppercase letter, which it shouldn't return Config.Border(worldName);
*/ }
public BorderData GetWorldBorder(String worldName)
{
return getWorldBorder(worldName);
}
public void enableBlockPlaceListener(boolean enable) /**
{ * @deprecated Replaced by {@link #getWorldBorder(String worldName)};
if (enable) * this method name starts with an uppercase letter, which it shouldn't
getServer().getPluginManager().registerEvents(this.blockPlaceListener = new BlockPlaceListener(), this); */
else if (blockPlaceListener != null) public BorderData GetWorldBorder(String worldName) {
blockPlaceListener.unregister(); return getWorldBorder(worldName);
} }
public void enableMobSpawnListener(boolean enable) public void enableBlockPlaceListener(boolean enable) {
{ if (enable)
if (enable) getServer().getPluginManager().registerEvents(this.blockPlaceListener = new BlockPlaceListener(), this);
getServer().getPluginManager().registerEvents(this.mobSpawnListener = new MobSpawnListener(), this); else if (blockPlaceListener != null)
else if (mobSpawnListener != null) blockPlaceListener.unregister();
mobSpawnListener.unregister(); }
}
public void enableMobSpawnListener(boolean enable) {
if (enable)
getServer().getPluginManager().registerEvents(this.mobSpawnListener = new MobSpawnListener(), this);
else if (mobSpawnListener != null)
mobSpawnListener.unregister();
}
} }

View File

@ -1,311 +1,264 @@
package com.wimbli.WorldBorder; package com.wimbli.WorldBorder;
import org.bukkit.World;
import org.bukkit.entity.Player;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*; import java.io.*;
import java.util.ArrayList;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.HashMap;
import java.nio.IntBuffer; import java.nio.IntBuffer;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.*;
import org.bukkit.entity.Player;
import org.bukkit.World;
// image output stuff, for debugging method at bottom of this file // image output stuff, for debugging method at bottom of this file
import java.awt.*;
import java.awt.image.*;
import javax.imageio.*;
// by the way, this region file handler was created based on the divulged region file format: http://mojang.com/2011/02/16/minecraft-save-file-format-in-beta-1-3/ // by the way, this region file handler was created based on the divulged region file format: http://mojang.com/2011/02/16/minecraft-save-file-format-in-beta-1-3/
public class WorldFileData public class WorldFileData {
{ private transient World world;
private transient World world; private transient File regionFolder = null;
private transient File regionFolder = null; private transient File[] regionFiles = null;
private transient File[] regionFiles = null; private transient Player notifyPlayer = null;
private transient Player notifyPlayer = null; private transient Map<CoordXZ, List<Boolean>> regionChunkExistence = Collections.synchronizedMap(new HashMap<CoordXZ, List<Boolean>>());
private transient Map<CoordXZ, List<Boolean>> regionChunkExistence = Collections.synchronizedMap(new HashMap<CoordXZ, List<Boolean>>());
// Use this static method to create a new instance of this class. If null is returned, there was a problem so any process relying on this should be cancelled. // the constructor is private; use create() method above to create an instance of this class.
public static WorldFileData create(World world, Player notifyPlayer) private WorldFileData(World world, Player notifyPlayer) {
{ this.world = world;
WorldFileData newData = new WorldFileData(world, notifyPlayer); this.notifyPlayer = notifyPlayer;
}
newData.regionFolder = new File(newData.world.getWorldFolder(), "region"); // Use this static method to create a new instance of this class. If null is returned, there was a problem so any process relying on this should be cancelled.
if (!newData.regionFolder.exists() || !newData.regionFolder.isDirectory()) public static WorldFileData create(World world, Player notifyPlayer) {
{ WorldFileData newData = new WorldFileData(world, notifyPlayer);
// check for region folder inside a DIM* folder (DIM-1 for nether, DIM1 for end, DIMwhatever for custom world types)
File[] possibleDimFolders = newData.world.getWorldFolder().listFiles(new DimFolderFileFilter());
for (File possibleDimFolder : possibleDimFolders)
{
File possible = new File(newData.world.getWorldFolder(), possibleDimFolder.getName() + File.separator + "region");
if (possible.exists() && possible.isDirectory())
{
newData.regionFolder = possible;
break;
}
}
if (!newData.regionFolder.exists() || !newData.regionFolder.isDirectory())
{
newData.sendMessage("Could not validate folder for world's region files. Looked in "+newData.world.getWorldFolder().getPath()+" for valid DIM* folder with a region folder in it.");
return null;
}
}
// Accepted region file formats: MCR is from late beta versions through 1.1, MCA is from 1.2+ newData.regionFolder = new File(newData.world.getWorldFolder(), "region");
newData.regionFiles = newData.regionFolder.listFiles(new ExtFileFilter(".MCA")); if (!newData.regionFolder.exists() || !newData.regionFolder.isDirectory()) {
if (newData.regionFiles == null || newData.regionFiles.length == 0) // check for region folder inside a DIM* folder (DIM-1 for nether, DIM1 for end, DIMwhatever for custom world types)
{ File[] possibleDimFolders = newData.world.getWorldFolder().listFiles(new DimFolderFileFilter());
newData.regionFiles = newData.regionFolder.listFiles(new ExtFileFilter(".MCR")); for (File possibleDimFolder : possibleDimFolders) {
if (newData.regionFiles == null || newData.regionFiles.length == 0) File possible = new File(newData.world.getWorldFolder(), possibleDimFolder.getName() + File.separator + "region");
{ if (possible.exists() && possible.isDirectory()) {
newData.sendMessage("Could not find any region files. Looked in: "+newData.regionFolder.getPath()); newData.regionFolder = possible;
return null; break;
} }
} }
if (!newData.regionFolder.exists() || !newData.regionFolder.isDirectory()) {
newData.sendMessage("Could not validate folder for world's region files. Looked in " + newData.world.getWorldFolder().getPath() + " for valid DIM* folder with a region folder in it.");
return null;
}
}
return newData; // Accepted region file formats: MCR is from late beta versions through 1.1, MCA is from 1.2+
} newData.regionFiles = newData.regionFolder.listFiles(new ExtFileFilter(".MCA"));
if (newData.regionFiles == null || newData.regionFiles.length == 0) {
newData.regionFiles = newData.regionFolder.listFiles(new ExtFileFilter(".MCR"));
if (newData.regionFiles == null || newData.regionFiles.length == 0) {
newData.sendMessage("Could not find any region files. Looked in: " + newData.regionFolder.getPath());
return null;
}
}
// the constructor is private; use create() method above to create an instance of this class. return newData;
private WorldFileData(World world, Player notifyPlayer) }
{
this.world = world; // number of region files this world has
this.notifyPlayer = notifyPlayer; public int regionFileCount() {
} return regionFiles.length;
}
// folder where world's region files are located
public File regionFolder() {
return regionFolder;
}
// return entire list of region files
public File[] regionFiles() {
return regionFiles.clone();
}
// return a region file by index
public File regionFile(int index) {
if (regionFiles.length < index)
return null;
return regionFiles[index];
}
// get the X and Z world coordinates of the region from the filename
public CoordXZ regionFileCoordinates(int index) {
File regionFile = this.regionFile(index);
String[] coords = regionFile.getName().split("\\.");
int x, z;
try {
x = Integer.parseInt(coords[1]);
z = Integer.parseInt(coords[2]);
return new CoordXZ(x, z);
} catch (Exception ex) {
sendMessage("Error! Region file found with abnormal name: " + regionFile.getName());
return null;
}
}
// number of region files this world has // Find out if the chunk at the given coordinates exists.
public int regionFileCount() public boolean doesChunkExist(int x, int z) {
{ CoordXZ region = new CoordXZ(CoordXZ.chunkToRegion(x), CoordXZ.chunkToRegion(z));
return regionFiles.length; List<Boolean> regionChunks = this.getRegionData(region);
}
// folder where world's region files are located
public File regionFolder()
{
return regionFolder;
}
// return entire list of region files
public File[] regionFiles()
{
return regionFiles.clone();
}
// return a region file by index
public File regionFile(int index)
{
if (regionFiles.length < index)
return null;
return regionFiles[index];
}
// get the X and Z world coordinates of the region from the filename
public CoordXZ regionFileCoordinates(int index)
{
File regionFile = this.regionFile(index);
String[] coords = regionFile.getName().split("\\.");
int x, z;
try
{
x = Integer.parseInt(coords[1]);
z = Integer.parseInt(coords[2]);
return new CoordXZ (x, z);
}
catch(Exception ex)
{
sendMessage("Error! Region file found with abnormal name: "+regionFile.getName());
return null;
}
}
// Find out if the chunk at the given coordinates exists.
public boolean doesChunkExist(int x, int z)
{
CoordXZ region = new CoordXZ(CoordXZ.chunkToRegion(x), CoordXZ.chunkToRegion(z));
List<Boolean> regionChunks = this.getRegionData(region);
// Bukkit.getLogger().info("x: "+x+" z: "+z+" offset: "+coordToRegionOffset(x, z)); // Bukkit.getLogger().info("x: "+x+" z: "+z+" offset: "+coordToRegionOffset(x, z));
return regionChunks.get(coordToRegionOffset(x, z)); return regionChunks.get(coordToRegionOffset(x, z));
} }
// Find out if the chunk at the given coordinates has been fully generated. // Find out if the chunk at the given coordinates has been fully generated.
// Minecraft only fully generates a chunk when adjacent chunks are also loaded. // Minecraft only fully generates a chunk when adjacent chunks are also loaded.
public boolean isChunkFullyGenerated(int x, int z) public boolean isChunkFullyGenerated(int x, int z) { // if all adjacent chunks exist, it should be a safe enough bet that this one is fully generated
{ // if all adjacent chunks exist, it should be a safe enough bet that this one is fully generated // For 1.13+, due to world gen changes, this is now effectively a 3 chunk radius requirement vs a 1 chunk radius
// For 1.13+, due to world gen changes, this is now effectively a 3 chunk radius requirement vs a 1 chunk radius for (int xx = x - 3; xx <= x + 3; xx++) {
for (int xx = x-3; xx <= x+3; xx++) for (int zz = z - 3; zz <= z + 3; zz++) {
{ if (!doesChunkExist(xx, zz))
for (int zz = z-3; zz <= z+3; zz++) return false;
{ }
if (!doesChunkExist(xx, zz)) }
return false; return true;
} }
}
return true;
}
// Method to let us know a chunk has been generated, to update our region map. // Method to let us know a chunk has been generated, to update our region map.
public void chunkExistsNow(int x, int z) public void chunkExistsNow(int x, int z) {
{ CoordXZ region = new CoordXZ(CoordXZ.chunkToRegion(x), CoordXZ.chunkToRegion(z));
CoordXZ region = new CoordXZ(CoordXZ.chunkToRegion(x), CoordXZ.chunkToRegion(z)); List<Boolean> regionChunks = this.getRegionData(region);
List<Boolean> regionChunks = this.getRegionData(region); regionChunks.set(coordToRegionOffset(x, z), true);
regionChunks.set(coordToRegionOffset(x, z), true); }
}
// region is 32 * 32 chunks; chunk pointers are stored in region file at position: x + z*32 (32 * 32 chunks = 1024)
// input x and z values can be world-based chunk coordinates or local-to-region chunk coordinates either one
private int coordToRegionOffset(int x, int z) {
// "%" modulus is used to convert potential world coordinates to definitely be local region coordinates
x = x % 32;
z = z % 32;
// similarly, for local coordinates, we need to wrap negative values around
if (x < 0) x += 32;
if (z < 0) z += 32;
// return offset position for the now definitely local x and z values
return (x + (z * 32));
}
// region is 32 * 32 chunks; chunk pointers are stored in region file at position: x + z*32 (32 * 32 chunks = 1024) private List<Boolean> getRegionData(CoordXZ region) {
// input x and z values can be world-based chunk coordinates or local-to-region chunk coordinates either one List<Boolean> data = regionChunkExistence.get(region);
private int coordToRegionOffset(int x, int z) if (data != null)
{ return data;
// "%" modulus is used to convert potential world coordinates to definitely be local region coordinates
x = x % 32;
z = z % 32;
// similarly, for local coordinates, we need to wrap negative values around
if (x < 0) x += 32;
if (z < 0) z += 32;
// return offset position for the now definitely local x and z values
return (x + (z * 32));
}
private List<Boolean> getRegionData(CoordXZ region) // data for the specified region isn't loaded yet, so init it as empty and try to find the file and load the data
{ data = new ArrayList<Boolean>(1024);
List<Boolean> data = regionChunkExistence.get(region); for (int i = 0; i < 1024; i++) {
if (data != null) data.add(Boolean.FALSE);
return data; }
// data for the specified region isn't loaded yet, so init it as empty and try to find the file and load the data for (int i = 0; i < regionFiles.length; i++) {
data = new ArrayList<Boolean>(1024); CoordXZ coord = regionFileCoordinates(i);
for (int i = 0; i < 1024; i++) // is this region file the one we're looking for?
{ if (!coord.equals(region))
data.add(Boolean.FALSE); continue;
}
for (int i = 0; i < regionFiles.length; i++) try {
{ RandomAccessFile regionData = new RandomAccessFile(this.regionFile(i), "r");
CoordXZ coord = regionFileCoordinates(i);
// is this region file the one we're looking for?
if ( ! coord.equals(region))
continue;
try // Use of ByteBuffer+IntBuffer for reading file headers to improve performance, as suggested by aikar, reference:
{ // https://github.com/PaperMC/Paper/blob/b62dfa0bf95ac27ba0fbb3fae18c064e4bb61d50/Spigot-Server-Patches/0086-Reduce-IO-ops-opening-a-new-region-file.patch
RandomAccessFile regionData = new RandomAccessFile(this.regionFile(i), "r"); ByteBuffer header = ByteBuffer.allocate(8192);
while (header.hasRemaining()) {
if (regionData.getChannel().read(header) == -1)
throw new EOFException();
}
header.clear();
IntBuffer headerAsInts = header.asIntBuffer();
// Use of ByteBuffer+IntBuffer for reading file headers to improve performance, as suggested by aikar, reference: // first 4096 bytes of region file consists of 4-byte int pointers to chunk data in the file (32*32 chunks = 1024; 1024 chunks * 4 bytes each = 4096)
// https://github.com/PaperMC/Paper/blob/b62dfa0bf95ac27ba0fbb3fae18c064e4bb61d50/Spigot-Server-Patches/0086-Reduce-IO-ops-opening-a-new-region-file.patch for (int j = 0; j < 1024; j++) {
ByteBuffer header = ByteBuffer.allocate(8192); // if chunk pointer data is 0, chunk doesn't exist yet; otherwise, it does
while (header.hasRemaining()) if (headerAsInts.get() != 0)
{ data.set(j, true);
if (regionData.getChannel().read(header) == -1) }
throw new EOFException(); // Read timestamps
} for (int j = 0; j < 1024; j++) {
header.clear(); // if timestamp is zero, it is protochunk (ignore it)
IntBuffer headerAsInts = header.asIntBuffer(); if ((headerAsInts.get() == 0) && data.get(j))
data.set(j, false);
// first 4096 bytes of region file consists of 4-byte int pointers to chunk data in the file (32*32 chunks = 1024; 1024 chunks * 4 bytes each = 4096) }
for (int j = 0; j < 1024; j++) regionData.close();
{ } catch (FileNotFoundException ex) {
// if chunk pointer data is 0, chunk doesn't exist yet; otherwise, it does sendMessage("Error! Could not open region file to find generated chunks: " + this.regionFile(i).getName());
if (headerAsInts.get() != 0) } catch (IOException ex) {
data.set(j, true); sendMessage("Error! Could not read region file to find generated chunks: " + this.regionFile(i).getName());
} }
// Read timestamps }
for (int j = 0; j < 1024; j++) regionChunkExistence.put(region, data);
{
// if timestamp is zero, it is protochunk (ignore it)
if ((headerAsInts.get() == 0) && data.get(j))
data.set(j, false);
}
regionData.close();
}
catch (FileNotFoundException ex)
{
sendMessage("Error! Could not open region file to find generated chunks: "+this.regionFile(i).getName());
}
catch (IOException ex)
{
sendMessage("Error! Could not read region file to find generated chunks: "+this.regionFile(i).getName());
}
}
regionChunkExistence.put(region, data);
// testImage(region, data); // testImage(region, data);
return data; return data;
} }
// send a message to the server console/log and possibly to an in-game player // send a message to the server console/log and possibly to an in-game player
private void sendMessage(String text) private void sendMessage(String text) {
{ Config.log("[WorldData] " + text);
Config.log("[WorldData] " + text); if (notifyPlayer != null && notifyPlayer.isOnline())
if (notifyPlayer != null && notifyPlayer.isOnline()) notifyPlayer.sendMessage("[WorldData] " + text);
notifyPlayer.sendMessage("[WorldData] " + text); }
}
// file filter used for region files // crude chunk map PNG image output, for debugging
private static class ExtFileFilter implements FileFilter private void testImage(CoordXZ region, List<Boolean> data) {
{ int width = 32;
String ext; int height = 32;
public ExtFileFilter(String extension) BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
{ Graphics2D g2 = bi.createGraphics();
this.ext = extension.toLowerCase(); int current = 0;
} g2.setColor(Color.BLACK);
@Override for (int x = 0; x < 32; x++) {
public boolean accept(File file) for (int z = 0; z < 32; z++) {
{ if (data.get(current).booleanValue())
return ( g2.fillRect(x, z, x + 1, z + 1);
file.exists() current++;
&& file.isFile() }
&& file.getName().toLowerCase().endsWith(ext) }
);
}
}
// file filter used for DIM* folders (for nether, End, and custom world types) File f = new File("region_" + region.x + "_" + region.z + "_.png");
private static class DimFolderFileFilter implements FileFilter Config.log(f.getAbsolutePath());
{ try {
@Override // png is an image format (like gif or jpg)
public boolean accept(File file) ImageIO.write(bi, "png", f);
{ } catch (IOException ex) {
return ( Config.log("[SEVERE]" + ex.getLocalizedMessage());
file.exists() }
&& file.isDirectory() }
&& file.getName().toLowerCase().startsWith("dim")
);
}
}
// file filter used for region files
private static class ExtFileFilter implements FileFilter {
String ext;
// crude chunk map PNG image output, for debugging public ExtFileFilter(String extension) {
private void testImage(CoordXZ region, List<Boolean> data) { this.ext = extension.toLowerCase();
int width = 32; }
int height = 32;
BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = bi.createGraphics();
int current = 0;
g2.setColor(Color.BLACK);
for (int x = 0; x < 32; x++) @Override
{ public boolean accept(File file) {
for (int z = 0; z < 32; z++) return (
{ file.exists()
if (data.get(current).booleanValue()) && file.isFile()
g2.fillRect(x,z, x+1, z+1); && file.getName().toLowerCase().endsWith(ext)
current++; );
} }
} }
File f = new File("region_"+region.x+"_"+region.z+"_.png"); // file filter used for DIM* folders (for nether, End, and custom world types)
Config.log(f.getAbsolutePath()); private static class DimFolderFileFilter implements FileFilter {
try { @Override
// png is an image format (like gif or jpg) public boolean accept(File file) {
ImageIO.write(bi, "png", f); return (
} catch (IOException ex) { file.exists()
Config.log("[SEVERE]" + ex.getLocalizedMessage()); && file.isDirectory()
} && file.getName().toLowerCase().startsWith("dim")
} );
}
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,12 @@
package com.wimbli.WorldBorder; package com.wimbli.WorldBorder;
import com.wimbli.WorldBorder.Events.WorldBorderTrimFinishedEvent;
import com.wimbli.WorldBorder.Events.WorldBorderTrimStartEvent;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.entity.Player;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
@ -7,430 +14,374 @@ import java.io.RandomAccessFile;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.Server;
import org.bukkit.World;
import com.wimbli.WorldBorder.Events.WorldBorderTrimFinishedEvent; public class WorldTrimTask implements Runnable {
import com.wimbli.WorldBorder.Events.WorldBorderTrimStartEvent; // general task-related reference data
private transient Server server = null;
private transient World world = null;
private transient WorldFileData worldData = null;
private transient BorderData border = null;
private transient boolean readyToGo = false;
private transient boolean paused = false;
private transient int taskID = -1;
private transient Player notifyPlayer = null;
private transient int chunksPerRun = 1;
// values for what chunk in the current region we're at
private transient int currentRegion = -1; // region(file) we're at in regionFiles
private transient int regionX = 0; // X location value of the current region
private transient int regionZ = 0; // X location value of the current region
private transient int currentChunk = 0; // chunk we've reached in the current region (regionChunks)
private transient List<CoordXZ> regionChunks = new ArrayList<CoordXZ>(1024);
private transient List<CoordXZ> trimChunks = new ArrayList<CoordXZ>(1024);
private transient int counter = 0;
// for reporting progress back to user occasionally
private transient long lastReport = Config.Now();
private transient int reportTarget = 0;
private transient int reportTotal = 0;
private transient int reportTrimmedRegions = 0;
private transient int reportTrimmedChunks = 0;
public class WorldTrimTask implements Runnable public WorldTrimTask(Server theServer, Player player, String worldName, int trimDistance, int chunksPerRun) {
{ this.server = theServer;
// general task-related reference data this.notifyPlayer = player;
private transient Server server = null; this.chunksPerRun = chunksPerRun;
private transient World world = null;
private transient WorldFileData worldData = null;
private transient BorderData border = null;
private transient boolean readyToGo = false;
private transient boolean paused = false;
private transient int taskID = -1;
private transient Player notifyPlayer = null;
private transient int chunksPerRun = 1;
// values for what chunk in the current region we're at
private transient int currentRegion = -1; // region(file) we're at in regionFiles
private transient int regionX = 0; // X location value of the current region
private transient int regionZ = 0; // X location value of the current region
private transient int currentChunk = 0; // chunk we've reached in the current region (regionChunks)
private transient List<CoordXZ> regionChunks = new ArrayList<CoordXZ>(1024);
private transient List<CoordXZ> trimChunks = new ArrayList<CoordXZ>(1024);
private transient int counter = 0;
// for reporting progress back to user occasionally this.world = server.getWorld(worldName);
private transient long lastReport = Config.Now(); if (this.world == null) {
private transient int reportTarget = 0; if (worldName.isEmpty())
private transient int reportTotal = 0; sendMessage("You must specify a world!");
private transient int reportTrimmedRegions = 0; else
private transient int reportTrimmedChunks = 0; sendMessage("World \"" + worldName + "\" not found!");
this.stop();
return;
}
this.border = (Config.Border(worldName) == null) ? null : Config.Border(worldName).copy();
if (this.border == null) {
sendMessage("No border found for world \"" + worldName + "\"!");
this.stop();
return;
}
this.border.setRadiusX(border.getRadiusX() + trimDistance);
this.border.setRadiusZ(border.getRadiusZ() + trimDistance);
worldData = WorldFileData.create(world, notifyPlayer);
if (worldData == null) {
this.stop();
return;
}
// each region file covers up to 1024 chunks; with all operations we might need to do, let's figure 3X that
this.reportTarget = worldData.regionFileCount() * 3072;
// queue up the first file
if (!nextFile())
return;
this.readyToGo = true;
Bukkit.getServer().getPluginManager().callEvent(new WorldBorderTrimStartEvent(this));
}
public void setTaskID(int ID) {
this.taskID = ID;
}
public WorldTrimTask(Server theServer, Player player, String worldName, int trimDistance, int chunksPerRun) public void run() {
{ if (server == null || !readyToGo || paused)
this.server = theServer; return;
this.notifyPlayer = player;
this.chunksPerRun = chunksPerRun;
this.world = server.getWorld(worldName); // this is set so it only does one iteration at a time, no matter how frequently the timer fires
if (this.world == null) readyToGo = false;
{ // and this is tracked to keep one iteration from dragging on too long and possibly choking the system if the user specified a really high frequency
if (worldName.isEmpty()) long loopStartTime = Config.Now();
sendMessage("You must specify a world!");
else
sendMessage("World \"" + worldName + "\" not found!");
this.stop();
return;
}
this.border = (Config.Border(worldName) == null) ? null : Config.Border(worldName).copy(); counter = 0;
if (this.border == null) while (counter <= chunksPerRun) {
{ // in case the task has been paused while we're repeating...
sendMessage("No border found for world \"" + worldName + "\"!"); if (paused)
this.stop(); return;
return;
}
this.border.setRadiusX(border.getRadiusX() + trimDistance); long now = Config.Now();
this.border.setRadiusZ(border.getRadiusZ() + trimDistance);
worldData = WorldFileData.create(world, notifyPlayer); // every 5 seconds or so, give basic progress report to let user know how it's going
if (worldData == null) if (now > lastReport + 5000)
{ reportProgress();
this.stop();
return;
}
// each region file covers up to 1024 chunks; with all operations we might need to do, let's figure 3X that // if this iteration has been running for 45ms (almost 1 tick) or more, stop to take a breather; shouldn't normally be possible with Trim, but just in case
this.reportTarget = worldData.regionFileCount() * 3072; if (now > loopStartTime + 45) {
readyToGo = true;
return;
}
// queue up the first file if (regionChunks.isEmpty())
if (!nextFile()) addCornerChunks();
return; else if (currentChunk == 4) { // determine if region is completely _inside_ border based on corner chunks
if (trimChunks.isEmpty()) { // it is, so skip it and move on to next file
this.readyToGo = true; counter += 4;
Bukkit.getServer().getPluginManager().callEvent(new WorldBorderTrimStartEvent(this)); nextFile();
} continue;
}
public void setTaskID(int ID) addEdgeChunks();
{ addInnerChunks();
this.taskID = ID; } else if (currentChunk == 124 && trimChunks.size() == 124) { // region is completely _outside_ border based on edge chunks, so delete file and move on to next
} counter += 16;
trimChunks = regionChunks;
unloadChunks();
public void run() reportTrimmedRegions++;
{ File regionFile = worldData.regionFile(currentRegion);
if (server == null || !readyToGo || paused) if (!regionFile.delete()) {
return; sendMessage("Error! Region file which is outside the border could not be deleted: " + regionFile.getName());
wipeChunks();
// this is set so it only does one iteration at a time, no matter how frequently the timer fires } else {
readyToGo = false; // if DynMap is installed, re-render the trimmed region ... disabled since it's not currently working, oh well
// and this is tracked to keep one iteration from dragging on too long and possibly choking the system if the user specified a really high frequency
long loopStartTime = Config.Now();
counter = 0;
while (counter <= chunksPerRun)
{
// in case the task has been paused while we're repeating...
if (paused)
return;
long now = Config.Now();
// every 5 seconds or so, give basic progress report to let user know how it's going
if (now > lastReport + 5000)
reportProgress();
// if this iteration has been running for 45ms (almost 1 tick) or more, stop to take a breather; shouldn't normally be possible with Trim, but just in case
if (now > loopStartTime + 45)
{
readyToGo = true;
return;
}
if (regionChunks.isEmpty())
addCornerChunks();
else if (currentChunk == 4)
{ // determine if region is completely _inside_ border based on corner chunks
if (trimChunks.isEmpty())
{ // it is, so skip it and move on to next file
counter += 4;
nextFile();
continue;
}
addEdgeChunks();
addInnerChunks();
}
else if (currentChunk == 124 && trimChunks.size() == 124)
{ // region is completely _outside_ border based on edge chunks, so delete file and move on to next
counter += 16;
trimChunks = regionChunks;
unloadChunks();
reportTrimmedRegions++;
File regionFile = worldData.regionFile(currentRegion);
if (!regionFile.delete())
{
sendMessage("Error! Region file which is outside the border could not be deleted: "+regionFile.getName());
wipeChunks();
}
else
{
// if DynMap is installed, re-render the trimmed region ... disabled since it's not currently working, oh well
// DynMapFeatures.renderRegion(world.getName(), new CoordXZ(regionX, regionZ)); // DynMapFeatures.renderRegion(world.getName(), new CoordXZ(regionX, regionZ));
} }
nextFile(); nextFile();
continue; continue;
} } else if (currentChunk == 1024) { // last chunk of the region has been checked, time to wipe out whichever chunks are outside the border
else if (currentChunk == 1024) counter += 32;
{ // last chunk of the region has been checked, time to wipe out whichever chunks are outside the border unloadChunks();
counter += 32; wipeChunks();
unloadChunks(); nextFile();
wipeChunks(); continue;
nextFile(); }
continue;
}
// check whether chunk is inside the border or not, add it to the "trim" list if not // check whether chunk is inside the border or not, add it to the "trim" list if not
CoordXZ chunk = regionChunks.get(currentChunk); CoordXZ chunk = regionChunks.get(currentChunk);
if (!isChunkInsideBorder(chunk)) if (!isChunkInsideBorder(chunk))
trimChunks.add(chunk); trimChunks.add(chunk);
currentChunk++; currentChunk++;
counter++; counter++;
} }
reportTotal += counter; reportTotal += counter;
// ready for the next iteration to run // ready for the next iteration to run
readyToGo = true; readyToGo = true;
} }
// Advance to the next region file. Returns true if successful, false if the next file isn't accessible for any reason // Advance to the next region file. Returns true if successful, false if the next file isn't accessible for any reason
private boolean nextFile() private boolean nextFile() {
{ reportTotal = currentRegion * 3072;
reportTotal = currentRegion * 3072; currentRegion++;
currentRegion++; regionX = regionZ = currentChunk = 0;
regionX = regionZ = currentChunk = 0; regionChunks = new ArrayList<CoordXZ>(1024);
regionChunks = new ArrayList<CoordXZ>(1024); trimChunks = new ArrayList<CoordXZ>(1024);
trimChunks = new ArrayList<CoordXZ>(1024);
// have we already handled all region files? // have we already handled all region files?
if (currentRegion >= worldData.regionFileCount()) if (currentRegion >= worldData.regionFileCount()) { // hey, we're done
{ // hey, we're done paused = true;
paused = true; readyToGo = false;
readyToGo = false; finish();
finish(); return false;
return false; }
}
counter += 16; counter += 16;
// get the X and Z coordinates of the current region // get the X and Z coordinates of the current region
CoordXZ coord = worldData.regionFileCoordinates(currentRegion); CoordXZ coord = worldData.regionFileCoordinates(currentRegion);
if (coord == null) if (coord == null)
return false; return false;
regionX = coord.x; regionX = coord.x;
regionZ = coord.z; regionZ = coord.z;
return true; return true;
} }
// add just the 4 corner chunks of the region; can determine if entire region is _inside_ the border // add just the 4 corner chunks of the region; can determine if entire region is _inside_ the border
private void addCornerChunks() private void addCornerChunks() {
{ regionChunks.add(new CoordXZ(CoordXZ.regionToChunk(regionX), CoordXZ.regionToChunk(regionZ)));
regionChunks.add(new CoordXZ(CoordXZ.regionToChunk(regionX), CoordXZ.regionToChunk(regionZ))); regionChunks.add(new CoordXZ(CoordXZ.regionToChunk(regionX) + 31, CoordXZ.regionToChunk(regionZ)));
regionChunks.add(new CoordXZ(CoordXZ.regionToChunk(regionX) + 31, CoordXZ.regionToChunk(regionZ))); regionChunks.add(new CoordXZ(CoordXZ.regionToChunk(regionX), CoordXZ.regionToChunk(regionZ) + 31));
regionChunks.add(new CoordXZ(CoordXZ.regionToChunk(regionX), CoordXZ.regionToChunk(regionZ) + 31)); regionChunks.add(new CoordXZ(CoordXZ.regionToChunk(regionX) + 31, CoordXZ.regionToChunk(regionZ) + 31));
regionChunks.add(new CoordXZ(CoordXZ.regionToChunk(regionX) + 31, CoordXZ.regionToChunk(regionZ) + 31)); }
}
// add all chunks along the 4 edges of the region (minus the corners); can determine if entire region is _outside_ the border // add all chunks along the 4 edges of the region (minus the corners); can determine if entire region is _outside_ the border
private void addEdgeChunks() private void addEdgeChunks() {
{ int chunkX = 0, chunkZ;
int chunkX = 0, chunkZ;
for (chunkZ = 1; chunkZ < 31; chunkZ++) for (chunkZ = 1; chunkZ < 31; chunkZ++) {
{ regionChunks.add(new CoordXZ(CoordXZ.regionToChunk(regionX) + chunkX, CoordXZ.regionToChunk(regionZ) + chunkZ));
regionChunks.add(new CoordXZ(CoordXZ.regionToChunk(regionX)+chunkX, CoordXZ.regionToChunk(regionZ)+chunkZ)); }
} chunkX = 31;
chunkX = 31; for (chunkZ = 1; chunkZ < 31; chunkZ++) {
for (chunkZ = 1; chunkZ < 31; chunkZ++) regionChunks.add(new CoordXZ(CoordXZ.regionToChunk(regionX) + chunkX, CoordXZ.regionToChunk(regionZ) + chunkZ));
{ }
regionChunks.add(new CoordXZ(CoordXZ.regionToChunk(regionX)+chunkX, CoordXZ.regionToChunk(regionZ)+chunkZ)); chunkZ = 0;
} for (chunkX = 1; chunkX < 31; chunkX++) {
chunkZ = 0; regionChunks.add(new CoordXZ(CoordXZ.regionToChunk(regionX) + chunkX, CoordXZ.regionToChunk(regionZ) + chunkZ));
for (chunkX = 1; chunkX < 31; chunkX++) }
{ chunkZ = 31;
regionChunks.add(new CoordXZ(CoordXZ.regionToChunk(regionX)+chunkX, CoordXZ.regionToChunk(regionZ)+chunkZ)); for (chunkX = 1; chunkX < 31; chunkX++) {
} regionChunks.add(new CoordXZ(CoordXZ.regionToChunk(regionX) + chunkX, CoordXZ.regionToChunk(regionZ) + chunkZ));
chunkZ = 31; }
for (chunkX = 1; chunkX < 31; chunkX++) counter += 4;
{ }
regionChunks.add(new CoordXZ(CoordXZ.regionToChunk(regionX)+chunkX, CoordXZ.regionToChunk(regionZ)+chunkZ));
}
counter += 4;
}
// add the remaining interior chunks (after corners and edges) // add the remaining interior chunks (after corners and edges)
private void addInnerChunks() private void addInnerChunks() {
{ for (int chunkX = 1; chunkX < 31; chunkX++) {
for (int chunkX = 1; chunkX < 31; chunkX++) for (int chunkZ = 1; chunkZ < 31; chunkZ++) {
{ regionChunks.add(new CoordXZ(CoordXZ.regionToChunk(regionX) + chunkX, CoordXZ.regionToChunk(regionZ) + chunkZ));
for (int chunkZ = 1; chunkZ < 31; chunkZ++) }
{ }
regionChunks.add(new CoordXZ(CoordXZ.regionToChunk(regionX)+chunkX, CoordXZ.regionToChunk(regionZ)+chunkZ)); counter += 32;
} }
}
counter += 32;
}
// make sure chunks set to be trimmed are not currently loaded by the server // make sure chunks set to be trimmed are not currently loaded by the server
private void unloadChunks() private void unloadChunks() {
{ for (CoordXZ unload : trimChunks) {
for (CoordXZ unload : trimChunks) if (world.isChunkLoaded(unload.x, unload.z))
{ world.unloadChunk(unload.x, unload.z, false);
if (world.isChunkLoaded(unload.x, unload.z)) }
world.unloadChunk(unload.x, unload.z, false); counter += trimChunks.size();
} }
counter += trimChunks.size();
}
// edit region file to wipe all chunk pointers for chunks outside the border // edit region file to wipe all chunk pointers for chunks outside the border
private void wipeChunks() private void wipeChunks() {
{ File regionFile = worldData.regionFile(currentRegion);
File regionFile = worldData.regionFile(currentRegion); if (!regionFile.canWrite()) {
if (!regionFile.canWrite()) if (!regionFile.setWritable(true))
{ throw new RuntimeException();
if (!regionFile.setWritable(true))
throw new RuntimeException();
if (!regionFile.canWrite()) if (!regionFile.canWrite()) {
{ sendMessage("Error! region file is locked and can't be trimmed: " + regionFile.getName());
sendMessage("Error! region file is locked and can't be trimmed: "+regionFile.getName()); return;
return; }
} }
}
// since our stored chunk positions are based on world, we need to offset those to positions in the region file // since our stored chunk positions are based on world, we need to offset those to positions in the region file
int offsetX = CoordXZ.regionToChunk(regionX); int offsetX = CoordXZ.regionToChunk(regionX);
int offsetZ = CoordXZ.regionToChunk(regionZ); int offsetZ = CoordXZ.regionToChunk(regionZ);
long wipePos = 0; long wipePos = 0;
int chunkCount = 0; int chunkCount = 0;
try try {
{ RandomAccessFile unChunk = new RandomAccessFile(regionFile, "rwd");
RandomAccessFile unChunk = new RandomAccessFile(regionFile, "rwd"); for (CoordXZ wipe : trimChunks) {
for (CoordXZ wipe : trimChunks) // if the chunk pointer is empty (chunk doesn't technically exist), no need to wipe the already empty pointer
{ if (!worldData.doesChunkExist(wipe.x, wipe.z))
// if the chunk pointer is empty (chunk doesn't technically exist), no need to wipe the already empty pointer continue;
if (!worldData.doesChunkExist(wipe.x, wipe.z))
continue;
// wipe this extraneous chunk's pointer... note that this method isn't perfect since the actual chunk data is left orphaned, // wipe this extraneous chunk's pointer... note that this method isn't perfect since the actual chunk data is left orphaned,
// but Minecraft will overwrite the orphaned data sector if/when another chunk is created in the region, so it's not so bad // but Minecraft will overwrite the orphaned data sector if/when another chunk is created in the region, so it's not so bad
wipePos = 4 * ((wipe.x - offsetX) + ((wipe.z - offsetZ) * 32)); wipePos = 4 * ((wipe.x - offsetX) + ((wipe.z - offsetZ) * 32));
unChunk.seek(wipePos); unChunk.seek(wipePos);
unChunk.writeInt(0); unChunk.writeInt(0);
chunkCount++; chunkCount++;
} }
unChunk.close(); unChunk.close();
// if DynMap is installed, re-render the trimmed chunks ... disabled since it's not currently working, oh well // if DynMap is installed, re-render the trimmed chunks ... disabled since it's not currently working, oh well
// DynMapFeatures.renderChunks(world.getName(), trimChunks); // DynMapFeatures.renderChunks(world.getName(), trimChunks);
reportTrimmedChunks += chunkCount; reportTrimmedChunks += chunkCount;
} } catch (FileNotFoundException ex) {
catch (FileNotFoundException ex) sendMessage("Error! Could not open region file to wipe individual chunks: " + regionFile.getName());
{ } catch (IOException ex) {
sendMessage("Error! Could not open region file to wipe individual chunks: "+regionFile.getName()); sendMessage("Error! Could not modify region file to wipe individual chunks: " + regionFile.getName());
} }
catch (IOException ex) counter += trimChunks.size();
{ }
sendMessage("Error! Could not modify region file to wipe individual chunks: "+regionFile.getName());
}
counter += trimChunks.size();
}
private boolean isChunkInsideBorder(CoordXZ chunk) private boolean isChunkInsideBorder(CoordXZ chunk) {
{ return border.insideBorder(CoordXZ.chunkToBlock(chunk.x) + 8, CoordXZ.chunkToBlock(chunk.z) + 8);
return border.insideBorder(CoordXZ.chunkToBlock(chunk.x) + 8, CoordXZ.chunkToBlock(chunk.z) + 8); }
}
// for successful completion // for successful completion
public void finish() public void finish() {
{ reportTotal = reportTarget;
reportTotal = reportTarget; reportProgress();
reportProgress(); Bukkit.getServer().getPluginManager().callEvent(new WorldBorderTrimFinishedEvent(world, reportTotal));
Bukkit.getServer().getPluginManager().callEvent(new WorldBorderTrimFinishedEvent(world, reportTotal)); sendMessage("task successfully completed!");
sendMessage("task successfully completed!"); this.stop();
this.stop(); }
}
// for cancelling prematurely // for cancelling prematurely
public void cancel() public void cancel() {
{ this.stop();
this.stop(); }
}
// we're done, whether finished or cancelled // we're done, whether finished or cancelled
private void stop() private void stop() {
{ if (server == null)
if (server == null) return;
return;
readyToGo = false; readyToGo = false;
if (taskID != -1) if (taskID != -1)
server.getScheduler().cancelTask(taskID); server.getScheduler().cancelTask(taskID);
server = null; server = null;
sendMessage("NOTICE: it is recommended that you restart your server after a Trim, to be on the safe side."); sendMessage("NOTICE: it is recommended that you restart your server after a Trim, to be on the safe side.");
if (DynMapFeatures.renderEnabled()) if (DynMapFeatures.renderEnabled())
sendMessage("This especially true with DynMap. You should also run a fullrender in DynMap for the trimmed world after restarting, so trimmed chunks are updated on the map."); sendMessage("This especially true with DynMap. You should also run a fullrender in DynMap for the trimmed world after restarting, so trimmed chunks are updated on the map.");
} }
// is this task still valid/workable? // is this task still valid/workable?
public boolean valid() public boolean valid() {
{ return this.server != null;
return this.server != null; }
}
// handle pausing/unpausing the task // handle pausing/unpausing the task
public void pause() public void pause() {
{ pause(!this.paused);
pause(!this.paused); }
}
public void pause(boolean pause)
{
this.paused = pause;
if (pause)
reportProgress();
}
public boolean isPaused()
{
return this.paused;
}
// let the user know how things are coming along public void pause(boolean pause) {
private void reportProgress() this.paused = pause;
{ if (pause)
lastReport = Config.Now(); reportProgress();
double perc = getPercentageCompleted(); }
sendMessage(reportTrimmedRegions + " entire region(s) and " + reportTrimmedChunks + " individual chunk(s) trimmed so far (" + Config.coord.format(perc) + "% done" + ")");
}
// send a message to the server console/log and possibly to an in-game player public boolean isPaused() {
private void sendMessage(String text) return this.paused;
{ }
Config.log("[Trim] " + text);
if (notifyPlayer != null)
notifyPlayer.sendMessage("[Trim] " + text);
}
/**
* Get the percentage completed for the trim task.
*
* @return Percentage
*/
public double getPercentageCompleted() {
return ((double) (reportTotal) / (double) reportTarget) * 100;
}
/** // let the user know how things are coming along
* Amount of chunks completed for the trim task. private void reportProgress() {
* lastReport = Config.Now();
* @return Number of chunks processed. double perc = getPercentageCompleted();
*/ sendMessage(reportTrimmedRegions + " entire region(s) and " + reportTrimmedChunks + " individual chunk(s) trimmed so far (" + Config.coord.format(perc) + "% done" + ")");
public int getChunksCompleted() { }
return reportTotal;
}
/** // send a message to the server console/log and possibly to an in-game player
* Total amount of chunks that need to be trimmed for the trim task. private void sendMessage(String text) {
* Config.log("[Trim] " + text);
* @return Number of chunks that need to be processed. if (notifyPlayer != null)
*/ notifyPlayer.sendMessage("[Trim] " + text);
public int getChunksTotal() { }
return reportTarget;
} /**
* Get the percentage completed for the trim task.
*
* @return Percentage
*/
public double getPercentageCompleted() {
return ((double) (reportTotal) / (double) reportTarget) * 100;
}
/**
* Amount of chunks completed for the trim task.
*
* @return Number of chunks processed.
*/
public int getChunksCompleted() {
return reportTotal;
}
/**
* Total amount of chunks that need to be trimmed for the trim task.
*
* @return Number of chunks that need to be processed.
*/
public int getChunksTotal() {
return reportTarget;
}
} }

View File

@ -1,100 +1,85 @@
package com.wimbli.WorldBorder.cmd; 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.List;
import java.util.UUID; import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import com.wimbli.WorldBorder.*; public class CmdBypass extends WBCmd {
import com.wimbli.WorldBorder.UUID.UUIDFetcher; 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.";
}
public class CmdBypass extends WBCmd @Override
{ public void cmdStatus(CommandSender sender) {
public CmdBypass() if (!(sender instanceof Player))
{ return;
name = permission = "bypass";
minParams = 0;
maxParams = 2;
addCmdExample(nameEmphasized() + "{player} [on|off] - let player go beyond border."); boolean bypass = Config.isPlayerBypassing(((Player) sender).getUniqueId());
helpText = "If [player] isn't specified, command sender is used. If [on|off] isn't specified, the value will " + sender.sendMessage(C_HEAD + "Border bypass is currently " + enabledColored(bypass) + C_HEAD + " for you.");
"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 @Override
public void cmdStatus(CommandSender sender) public void execute(final CommandSender sender, final Player player, final List<String> params, String worldName) {
{ if (player == null && params.isEmpty()) {
if (!(sender instanceof Player)) sendErrorAndHelp(sender, "When running this command from console, you must specify a player.");
return; return;
}
boolean bypass = Config.isPlayerBypassing(((Player)sender).getUniqueId()); Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(WorldBorder.plugin, new Runnable() {
sender.sendMessage(C_HEAD + "Border bypass is currently " + enabledColored(bypass) + C_HEAD + " for you."); @Override
} public void run() {
final String sPlayer = (params.isEmpty()) ? player.getName() : params.get(0);
UUID uPlayer = (params.isEmpty()) ? player.getUniqueId() : null;
@Override if (uPlayer == null) {
public void execute(final CommandSender sender, final Player player, final List<String> params, String worldName) Player p = Bukkit.getPlayer(sPlayer);
{ if (p != null) {
if (player == null && params.isEmpty()) uPlayer = p.getUniqueId();
{ } else {
sendErrorAndHelp(sender, "When running this command from console, you must specify a player."); // only do UUID lookup using Mojang server if specified player isn't online
return; 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;
}
Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(WorldBorder.plugin, new Runnable() boolean bypassing = !Config.isPlayerBypassing(uPlayer);
{ if (params.size() > 1)
@Override bypassing = strAsBool(params.get(1));
public void run()
{
final String sPlayer = (params.isEmpty()) ? player.getName() : params.get(0);
UUID uPlayer = (params.isEmpty()) ? player.getUniqueId() : null;
if (uPlayer == null) Config.setPlayerBypass(uPlayer, bypassing);
{
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); Player target = Bukkit.getPlayer(sPlayer);
if (params.size() > 1) if (target != null && target.isOnline())
bypassing = strAsBool(params.get(1)); target.sendMessage("Border bypass is now " + enabledColored(bypassing) + ".");
Config.setPlayerBypass(uPlayer, bypassing); Config.log("Border bypass for player \"" + sPlayer + "\" is " + (bypassing ? "enabled" : "disabled") +
(player != null ? " at the command of player \"" + player.getName() + "\"" : "") + ".");
Player target = Bukkit.getPlayer(sPlayer); if (player != null && player != target)
if (target != null && target.isOnline()) sender.sendMessage("Border bypass for player \"" + sPlayer + "\" is " + enabledColored(bypassing) + ".");
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) + ".");
}
});
}
} }

View File

@ -1,58 +1,49 @@
package com.wimbli.WorldBorder.cmd; 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.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import com.wimbli.WorldBorder.*; public class CmdBypasslist extends WBCmd {
import com.wimbli.WorldBorder.UUID.UUIDFetcher; 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.";
}
public class CmdBypasslist extends WBCmd @Override
{ public void execute(final CommandSender sender, Player player, List<String> params, String worldName) {
public CmdBypasslist() final ArrayList<UUID> uuids = Config.getPlayerBypassList();
{ if (uuids == null || uuids.isEmpty()) {
name = permission = "bypasslist"; sender.sendMessage("Players with border bypass enabled: <none>");
minParams = maxParams = 0; return;
}
addCmdExample(nameEmphasized() + "- list players with border bypass enabled."); Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(WorldBorder.plugin, new Runnable() {
helpText = "The bypass list will persist between server restarts, and applies to all worlds. Use the " + @Override
commandEmphasized("bypass") + C_DESC + "command to add or remove players."; public void run() {
} try {
Map<UUID, String> names = UUIDFetcher.getNameList(uuids);
String nameString = names.values().toString();
@Override sender.sendMessage("Players with border bypass enabled: " + nameString.substring(1, nameString.length() - 1));
public void execute(final CommandSender sender, Player player, List<String> params, String worldName) } catch (Exception ex) {
{ sendErrorAndHelp(sender, "Failed to look up names for the UUIDs in the border bypass list. " + ex.getLocalizedMessage());
final ArrayList<UUID> uuids = Config.getPlayerBypassList(); return;
if (uuids == null || uuids.isEmpty()) }
{ }
sender.sendMessage("Players with border bypass enabled: <none>"); });
return; }
}
Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(WorldBorder.plugin, new Runnable()
{
@Override
public void run()
{
try
{
Map<UUID, String> 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;
}
}
});
}
} }

View File

@ -1,68 +1,60 @@
package com.wimbli.WorldBorder.cmd; 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; import java.util.List;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import com.wimbli.WorldBorder.*; 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.";
}
public class CmdClear extends WBCmd @Override
{ public void execute(CommandSender sender, Player player, List<String> params, String worldName) {
public CmdClear() // handle "clear all" command separately
{ if (params.size() == 1 && params.get(0).equalsIgnoreCase("all")) {
name = permission = "clear"; if (worldName != null) {
hasWorldNameInput = true; sendErrorAndHelp(sender, "You should not specify a world with \"clear all\".");
consoleRequiresWorldName = false; return;
minParams = 0; }
maxParams = 1;
addCmdExample(nameEmphasizedW() + "- remove border for this world."); Config.removeAllBorders();
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 if (player != null)
public void execute(CommandSender sender, Player player, List<String> params, String worldName) sender.sendMessage("All borders have been cleared for all worlds.");
{ return;
// 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 (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();
}
if (player != null) BorderData border = Config.Border(worldName);
sender.sendMessage("All borders have been cleared for all worlds."); if (border == null) {
return; sendErrorAndHelp(sender, "This world (\"" + worldName + "\") does not have a border set.");
} return;
}
if (worldName == null) Config.removeBorder(worldName);
{
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 (player != null)
if (border == null) sender.sendMessage("Border cleared for world \"" + worldName + "\".");
{ }
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 + "\".");
}
} }

View File

@ -1,74 +1,63 @@
package com.wimbli.WorldBorder.cmd; 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.List;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import com.wimbli.WorldBorder.*; public class CmdCommands extends WBCmd {
private static 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;
}
public class CmdCommands extends WBCmd @Override
{ public void execute(CommandSender sender, Player player, List<String> params, String worldName) {
private static int pageSize = 8; // examples to list per page; 10 lines available, 1 for header, 1 for footer // 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) {
}
}
public CmdCommands() // see whether we're showing examples to player or to console, and determine number of pages available
{ List<String> examples = (player == null) ? cmdExamplesConsole : cmdExamplesPlayer;
name = "commands"; int pageCount = (int) Math.ceil(examples.size() / (double) pageSize);
permission = "help";
hasWorldNameInput = false;
}
@Override // if specified page number is negative or higher than we have available, default back to first page
public void execute(CommandSender sender, Player player, List<String> params, String worldName) if (page < 0 || page > pageCount)
{ page = (player == null) ? 0 : 1;
// 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 // send command example header
List<String> examples = (player == null) ? cmdExamplesConsole : cmdExamplesPlayer; sender.sendMessage(C_HEAD + WorldBorder.plugin.getDescription().getFullName() + " - key: " +
int pageCount = (int) Math.ceil(examples.size() / (double) pageSize); commandEmphasized("command") + C_REQ + "<required> " + C_OPT + "[optional]");
// if specified page number is negative or higher than we have available, default back to first page if (page > 0) {
if (page < 0 || page > pageCount) // send examples for this page
page = (player == null) ? 0 : 1; 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 command example header // send page footer, if relevant; manual spacing to get right side lined up near edge is crude, but sufficient
sender.sendMessage( C_HEAD + WorldBorder.plugin.getDescription().getFullName() + " - key: " + String footer = C_HEAD + " (Page " + page + "/" + pageCount + ") " + cmd(sender);
commandEmphasized("command") + C_REQ + "<required> " + C_OPT + "[optional]" ); if (page < pageCount)
sender.sendMessage(footer + Integer.toString(page + 1) + C_DESC + " - view next page of commands.");
if (page > 0) else if (page > 1)
{ sender.sendMessage(footer + C_DESC + "- view first page of commands.");
// send examples for this page } else {
int first = ((page - 1) * pageSize); // if page "0" is specified, send all examples; done by default for console but can be specified by player
int count = Math.min(pageSize, examples.size() - first); for (String example : examples) {
for(int i = first; i < first + count; i++) sender.sendMessage(example);
{ }
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 + Integer.toString(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);
}
}
}
} }

View File

@ -1,40 +1,34 @@
package com.wimbli.WorldBorder.cmd; 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.List;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import com.wimbli.WorldBorder.*; public class CmdDebug extends WBCmd {
public CmdDebug() {
name = permission = "debug";
minParams = maxParams = 1;
addCmdExample(nameEmphasized() + "<on|off> - 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.";
}
public class CmdDebug extends WBCmd @Override
{ public void cmdStatus(CommandSender sender) {
public CmdDebug() sender.sendMessage(C_HEAD + "Debug mode is " + enabledColored(Config.Debug()) + C_HEAD + ".");
{ }
name = permission = "debug";
minParams = maxParams = 1;
addCmdExample(nameEmphasized() + "<on|off> - turn console debug output on or off."); @Override
helpText = "Default value: off. Debug mode will show some extra debugging data in the server console/log when " + public void execute(CommandSender sender, Player player, List<String> params, String worldName) {
"players are knocked back from the border or are teleported."; Config.setDebug(strAsBool(params.get(0)));
}
@Override if (player != null) {
public void cmdStatus(CommandSender sender) Config.log((Config.Debug() ? "Enabled" : "Disabled") + " debug output at the command of player \"" + player.getName() + "\".");
{ cmdStatus(sender);
sender.sendMessage(C_HEAD + "Debug mode is " + enabledColored(Config.Debug()) + C_HEAD + "."); }
} }
@Override
public void execute(CommandSender sender, Player player, List<String> 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);
}
}
} }

View File

@ -1,51 +1,43 @@
package com.wimbli.WorldBorder.cmd; 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.List;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import com.wimbli.WorldBorder.*; public class CmdDelay extends WBCmd {
public CmdDelay() {
name = permission = "delay";
minParams = maxParams = 1;
addCmdExample(nameEmphasized() + "<amount> - time between border checks.");
helpText = "Default value: 5. The <amount> 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.";
}
public class CmdDelay extends WBCmd @Override
{ public void cmdStatus(CommandSender sender) {
public CmdDelay() int delay = Config.TimerTicks();
{ sender.sendMessage(C_HEAD + "Timer delay is set to " + delay + " tick(s). That is roughly " + (delay * 50) + "ms.");
name = permission = "delay"; }
minParams = maxParams = 1;
addCmdExample(nameEmphasized() + "<amount> - time between border checks."); @Override
helpText = "Default value: 5. The <amount> is in server ticks, of which there are roughly 20 every second, each " + public void execute(CommandSender sender, Player player, List<String> params, String worldName) {
"tick taking ~50ms. The default value therefore has border checks run about 4 times per second."; 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;
}
@Override Config.setTimerTicks(delay);
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 if (player != null)
public void execute(CommandSender sender, Player player, List<String> params, String worldName) cmdStatus(sender);
{ }
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);
}
} }

View File

@ -1,42 +1,36 @@
package com.wimbli.WorldBorder.cmd; 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.List;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import com.wimbli.WorldBorder.*; public class CmdDenypearl extends WBCmd {
public CmdDenypearl() {
name = permission = "denypearl";
minParams = maxParams = 1;
addCmdExample(nameEmphasized() + "<on|off> - 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.";
}
public class CmdDenypearl extends WBCmd @Override
{ public void cmdStatus(CommandSender sender) {
public CmdDenypearl() sender.sendMessage(C_HEAD + "Direct cancellation of ender pearls thrown past the border is " +
{ enabledColored(Config.getDenyEnderpearl()) + C_HEAD + ".");
name = permission = "denypearl"; }
minParams = maxParams = 1;
addCmdExample(nameEmphasized() + "<on|off> - stop ender pearls past the border."); @Override
helpText = "Default value: on. When enabled, this setting will directly cancel attempts to use an ender pearl to " + public void execute(CommandSender sender, Player player, List<String> params, String worldName) {
"get past the border rather than just knocking the player back. This should prevent usage of ender " + Config.setDenyEnderpearl(strAsBool(params.get(0)));
"pearls to glitch into areas otherwise inaccessible at the border edge.";
}
@Override if (player != null) {
public void cmdStatus(CommandSender sender) Config.log((Config.getDenyEnderpearl() ? "Enabled" : "Disabled") + " direct cancellation of ender pearls thrown past the border at the command of player \"" + player.getName() + "\".");
{ cmdStatus(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<String> 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);
}
}
} }

View File

@ -1,40 +1,34 @@
package com.wimbli.WorldBorder.cmd; 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.List;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import com.wimbli.WorldBorder.*; public class CmdDynmap extends WBCmd {
public CmdDynmap() {
name = permission = "dynmap";
minParams = maxParams = 1;
addCmdExample(nameEmphasized() + "<on|off> - 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.";
}
public class CmdDynmap extends WBCmd @Override
{ public void cmdStatus(CommandSender sender) {
public CmdDynmap() sender.sendMessage(C_HEAD + "DynMap border display is " + enabledColored(Config.DynmapBorderEnabled()) + C_HEAD + ".");
{ }
name = permission = "dynmap";
minParams = maxParams = 1;
addCmdExample(nameEmphasized() + "<on|off> - turn DynMap border display on or off."); @Override
helpText = "Default value: on. If you are running the DynMap plugin and this setting is enabled, all borders will " + public void execute(CommandSender sender, Player player, List<String> params, String worldName) {
"be visually shown in DynMap."; Config.setDynmapBorderEnabled(strAsBool(params.get(0)));
}
@Override if (player != null) {
public void cmdStatus(CommandSender sender) cmdStatus(sender);
{ Config.log((Config.DynmapBorderEnabled() ? "Enabled" : "Disabled") + " DynMap border display at the command of player \"" + player.getName() + "\".");
sender.sendMessage(C_HEAD + "DynMap border display is " + enabledColored(Config.DynmapBorderEnabled()) + C_HEAD + "."); }
} }
@Override
public void execute(CommandSender sender, Player player, List<String> 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() + "\".");
}
}
} }

View File

@ -1,48 +1,42 @@
package com.wimbli.WorldBorder.cmd; 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.List;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import com.wimbli.WorldBorder.*; public class CmdDynmapmsg extends WBCmd {
public CmdDynmapmsg() {
name = permission = "dynmapmsg";
minParams = 1;
addCmdExample(nameEmphasized() + "<text> - 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.";
}
public class CmdDynmapmsg extends WBCmd @Override
{ public void cmdStatus(CommandSender sender) {
public CmdDynmapmsg() sender.sendMessage(C_HEAD + "DynMap border label is set to: " + C_ERR + Config.DynmapMessage());
{ }
name = permission = "dynmapmsg";
minParams = 1;
addCmdExample(nameEmphasized() + "<text> - DynMap border labels will show this."); @Override
helpText = "Default value: \"The border of the world.\". If you are running the DynMap plugin and the " + public void execute(CommandSender sender, Player player, List<String> params, String worldName) {
commandEmphasized("dynmap") + C_DESC + "command setting is enabled, the borders shown in DynMap will " + StringBuilder message = new StringBuilder();
"be labelled with this text."; boolean first = true;
} for (String param : params) {
if (!first)
message.append(" ");
message.append(param);
first = false;
}
@Override Config.setDynmapMessage(message.toString());
public void cmdStatus(CommandSender sender)
{
sender.sendMessage(C_HEAD + "DynMap border label is set to: " + C_ERR + Config.DynmapMessage());
}
@Override if (player != null)
public void execute(CommandSender sender, Player player, List<String> params, String worldName) cmdStatus(sender);
{ }
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);
}
} }

View File

@ -1,183 +1,161 @@
package com.wimbli.WorldBorder.cmd; 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; import java.util.List;
import org.bukkit.Bukkit;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import com.wimbli.WorldBorder.*; 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;
public class CmdFill extends WBCmd addCmdExample(nameEmphasizedW() + "[freq] [pad] [force] - fill world to border.");
{ helpText = "This command will generate missing world chunks inside your border. [freq] is the frequency " +
public CmdFill() "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 " +
name = permission = "fill"; "to force all chunks to be loaded even if they seem to be fully generated (default false).";
hasWorldNameInput = true; }
consoleRequiresWorldName = false;
minParams = 0;
maxParams = 3;
addCmdExample(nameEmphasizedW() + "[freq] [pad] [force] - fill world to border."); @Override
helpText = "This command will generate missing world chunks inside your border. [freq] is the frequency " + public void execute(CommandSender sender, Player player, List<String> params, String worldName) {
"of chunks per second that will be checked (default 20). [pad] is the number of blocks padding added " + boolean confirm = false;
"beyond the border itself (default 208, to cover player visual range). [force] can be specified as true " + // check for "cancel", "pause", or "confirm"
"to force all chunks to be loaded even if they seem to be fully generated (default false)."; if (params.size() >= 1) {
} String check = params.get(0).toLowerCase();
@Override if (check.equals("cancel") || check.equals("stop")) {
public void execute(CommandSender sender, Player player, List<String> params, String worldName) if (!makeSureFillIsRunning(sender))
{ return;
boolean confirm = false; sender.sendMessage(C_HEAD + "Cancelling the world map generation task.");
// check for "cancel", "pause", or "confirm" fillDefaults();
if (params.size() >= 1) Config.StopFillTask();
{ return;
String check = params.get(0).toLowerCase(); } 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;
}
if (check.equals("cancel") || check.equals("stop")) confirm = check.equals("confirm");
{ }
if (!makeSureFillIsRunning(sender))
return;
sender.sendMessage(C_HEAD + "Cancelling the world map generation task.");
fillDefaults();
Config.StopFillTask();
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;
}
}
// if not just confirming, make sure a world name is available // colorized "/wb fill "
if (worldName == null && !confirm) String cmd = cmd(sender) + nameEmphasized() + C_CMD;
{
if (player != null)
worldName = player.getWorld().getName();
else
{
sendErrorAndHelp(sender, "You must specify a world!");
return;
}
}
// colorized "/wb fill " // make sure Fill isn't already running
String cmd = cmd(sender) + nameEmphasized() + C_CMD; 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;
}
// make sure Fill isn't already running // set frequency and/or padding if those were specified
if (Config.fillTask != null && Config.fillTask.valid()) try {
{ if (params.size() >= 1 && !confirm)
sender.sendMessage(C_ERR + "The world map generation task is already running."); fillFrequency = Math.abs(Integer.parseInt(params.get(0)));
sender.sendMessage(C_DESC + "You can cancel at any time with " + cmd + "cancel" + C_DESC + ", or pause/unpause with " + cmd + "pause" + C_DESC + "."); if (params.size() >= 2 && !confirm)
return; 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;
}
// set frequency and/or padding if those were specified // see if the command specifies to load even chunks which should already be fully generated
try if (params.size() == 3)
{ fillForceLoad = strAsBool(params.get(2));
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 // set world if it was specified
if (params.size() == 3) if (worldName != null)
fillForceLoad = strAsBool(params.get(2)); fillWorld = worldName;
// set world if it was specified if (confirm) { // command confirmed, go ahead with it
if (worldName != null) if (fillWorld.isEmpty()) {
fillWorld = worldName; sendErrorAndHelp(sender, "You must first use this command successfully without confirming.");
return;
}
if (confirm) if (player != null)
{ // command confirmed, go ahead with it Config.log("Filling out world to border at the command of player \"" + player.getName() + "\".");
if (fillWorld.isEmpty())
{
sendErrorAndHelp(sender, "You must first use this command successfully without confirming.");
return;
}
if (player != null) int ticks = 1, repeats = 1;
Config.log("Filling out world to border at the command of player \"" + player.getName() + "\"."); if (fillFrequency > 20)
repeats = fillFrequency / 20;
else
ticks = 20 / fillFrequency;
int ticks = 1, repeats = 1; /* */
if (fillFrequency > 20) Config.log("world: " + fillWorld + " padding: " + fillPadding + " repeats: " + repeats + " ticks: " + ticks);
repeats = fillFrequency / 20; Config.fillTask = new WorldFillTask(Bukkit.getServer(), player, fillWorld, fillPadding, repeats, ticks, fillForceLoad);
else if (Config.fillTask.valid()) {
ticks = 20 / fillFrequency; 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.");
/* */ Config.log("world: " + fillWorld + " padding: " + fillPadding + " repeats: " + repeats + " ticks: " + ticks); fillDefaults();
Config.fillTask = new WorldFillTask(Bukkit.getServer(), player, fillWorld, fillPadding, repeats, ticks, fillForceLoad); } else {
if (Config.fillTask.valid()) if (fillWorld.isEmpty()) {
{ sendErrorAndHelp(sender, "You must first specify a valid world.");
int task = Bukkit.getServer().getScheduler().scheduleSyncRepeatingTask(WorldBorder.plugin, Config.fillTask, ticks, ticks); return;
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(); 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.");
else 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 + ".");
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.")); private void fillDefaults() {
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."); fillWorld = "";
sender.sendMessage(C_DESC + "You should now use " + cmd + "confirm" + C_DESC + " to start the process."); fillFrequency = 20;
sender.sendMessage(C_DESC + "You can cancel at any time with " + cmd + "cancel" + C_DESC + ", or pause/unpause with " + cmd + "pause" + C_DESC + "."); fillPadding = defaultPadding;
} fillForceLoad = false;
} }
/* with "view-distance=10" in server.properties on a fast VM test server and "Render Distance: Far" in client, private boolean makeSureFillIsRunning(CommandSender sender) {
* hitting border during testing was loading 11+ chunks beyond the border in a couple of directions (10 chunks in if (Config.fillTask != null && Config.fillTask.valid())
* the other two directions). This could be worse on a more loaded or worse server, so: return true;
*/ sendErrorAndHelp(sender, "The world map generation task is not currently running.");
private final int defaultPadding = CoordXZ.chunkToBlock(13); return false;
}
private String fillWorld = "";
private int fillFrequency = 20;
private int fillPadding = defaultPadding;
private boolean fillForceLoad = false;
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;
}
} }

View File

@ -1,61 +1,50 @@
package com.wimbli.WorldBorder.cmd; 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.List;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import com.wimbli.WorldBorder.*; public class CmdFillautosave extends WBCmd {
public CmdFillautosave() {
name = permission = "fillautosave";
minParams = maxParams = 1;
addCmdExample(nameEmphasized() + "<seconds> - world save interval for Fill.");
helpText = "Default value: 30 seconds.";
}
public class CmdFillautosave extends WBCmd @Override
{ public void cmdStatus(CommandSender sender) {
public CmdFillautosave() int seconds = Config.FillAutosaveFrequency();
{ if (seconds == 0) {
name = permission = "fillautosave"; sender.sendMessage(C_HEAD + "World autosave frequency during Fill process is set to 0, disabling it.");
minParams = maxParams = 1; 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.");
}
}
addCmdExample(nameEmphasized() + "<seconds> - world save interval for Fill."); @Override
helpText = "Default value: 30 seconds."; public void execute(CommandSender sender, Player player, List<String> 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;
}
@Override Config.setFillAutosaveFrequency(seconds);
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 if (player != null)
public void execute(CommandSender sender, Player player, List<String> params, String worldName) cmdStatus(sender);
{ }
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);
}
} }

View File

@ -1,30 +1,26 @@
package com.wimbli.WorldBorder.cmd; 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.List;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import com.wimbli.WorldBorder.*; 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.";
}
public class CmdGetmsg extends WBCmd @Override
{ public void execute(CommandSender sender, Player player, List<String> params, String worldName) {
public CmdGetmsg() sender.sendMessage("Border message is currently set to:");
{ sender.sendMessage(Config.MessageRaw());
name = permission = "getmsg"; sender.sendMessage("Formatted border message:");
minParams = maxParams = 0; sender.sendMessage(Config.Message());
}
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<String> params, String worldName)
{
sender.sendMessage("Border message is currently set to:");
sender.sendMessage(Config.MessageRaw());
sender.sendMessage("Formatted border message:");
sender.sendMessage(Config.Message());
}
} }

View File

@ -1,53 +1,45 @@
package com.wimbli.WorldBorder.cmd; 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.List;
import java.util.Set; import java.util.Set;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import com.wimbli.WorldBorder.*; public class CmdHelp extends WBCmd {
public CmdHelp() {
name = permission = "help";
minParams = 0;
maxParams = 10;
addCmdExample(nameEmphasized() + "[command] - get help on command usage.");
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."; // helpText = "If [command] is specified, info for that particular command will be provided.";
} }
@Override @Override
public void cmdStatus(CommandSender sender) public void cmdStatus(CommandSender sender) {
{ String commands = WorldBorder.wbCommand.getCommandNames().toString().replace(", ", C_DESC + ", " + C_CMD);
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(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("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.");
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 @Override
public void execute(CommandSender sender, Player player, List<String> params, String worldName) public void execute(CommandSender sender, Player player, List<String> params, String worldName) {
{ if (params.isEmpty()) {
if (params.isEmpty()) sendCmdHelp(sender);
{ return;
sendCmdHelp(sender); }
return;
}
Set<String> commands = WorldBorder.wbCommand.getCommandNames(); Set<String> commands = WorldBorder.wbCommand.getCommandNames();
for (String param : params) for (String param : params) {
{ if (commands.contains(param.toLowerCase())) {
if (commands.contains(param.toLowerCase())) WorldBorder.wbCommand.subCommands.get(param.toLowerCase()).sendCmdHelp(sender);
{ return;
WorldBorder.wbCommand.subCommands.get(param.toLowerCase()).sendCmdHelp(sender); }
return; }
} sendErrorAndHelp(sender, "No command recognized.");
} }
sendErrorAndHelp(sender, "No command recognized.");
}
} }

View File

@ -1,53 +1,45 @@
package com.wimbli.WorldBorder.cmd; 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.List;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import com.wimbli.WorldBorder.*; public class CmdKnockback extends WBCmd {
public CmdKnockback() {
name = permission = "knockback";
minParams = maxParams = 1;
addCmdExample(nameEmphasized() + "<distance> - 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.";
}
public class CmdKnockback extends WBCmd @Override
{ public void cmdStatus(CommandSender sender) {
public CmdKnockback() double kb = Config.KnockBack();
{ if (kb < 1)
name = permission = "knockback"; sender.sendMessage(C_HEAD + "Knockback is set to 0, disabling border enforcement.");
minParams = maxParams = 1; else
sender.sendMessage(C_HEAD + "Knockback is set to " + kb + " blocks inside the border.");
}
addCmdExample(nameEmphasized() + "<distance> - how far to move the player back."); @Override
helpText = "Default value: 3.0 (blocks). Players who cross the border will be knocked back to this distance inside."; public void execute(CommandSender sender, Player player, List<String> 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;
}
@Override Config.setKnockBack(numBlocks);
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 if (player != null)
public void execute(CommandSender sender, Player player, List<String> params, String worldName) cmdStatus(sender);
{ }
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);
}
} }

View File

@ -1,42 +1,36 @@
package com.wimbli.WorldBorder.cmd; 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.List;
import java.util.Set; import java.util.Set;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import com.wimbli.WorldBorder.*; 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.";
}
public class CmdList extends WBCmd @Override
{ public void execute(CommandSender sender, Player player, List<String> params, String worldName) {
public CmdList() sender.sendMessage("Default border shape for all worlds is \"" + Config.ShapeName() + "\".");
{
name = permission = "list";
minParams = maxParams = 0;
addCmdExample(nameEmphasized() + "- show border information for all worlds."); Set<String> list = Config.BorderDescriptions();
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 if (list.isEmpty()) {
public void execute(CommandSender sender, Player player, List<String> params, String worldName) sender.sendMessage("There are no borders currently set.");
{ return;
sender.sendMessage("Default border shape for all worlds is \"" + Config.ShapeName() + "\"."); }
Set<String> list = Config.BorderDescriptions(); for (String borderDesc : list) {
sender.sendMessage(borderDesc);
if (list.isEmpty()) }
{ }
sender.sendMessage("There are no borders currently set.");
return;
}
for(String borderDesc : list)
{
sender.sendMessage(borderDesc);
}
}
} }

View File

@ -1,41 +1,35 @@
package com.wimbli.WorldBorder.cmd; 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.List;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import com.wimbli.WorldBorder.*; public class CmdPortal extends WBCmd {
public CmdPortal() {
name = permission = "portal";
minParams = maxParams = 1;
addCmdExample(nameEmphasized() + "<on|off> - 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.";
}
public class CmdPortal extends WBCmd @Override
{ public void cmdStatus(CommandSender sender) {
public CmdPortal() sender.sendMessage(C_HEAD + "Portal redirection is " + enabledColored(Config.portalRedirection()) + C_HEAD + ".");
{ }
name = permission = "portal";
minParams = maxParams = 1;
addCmdExample(nameEmphasized() + "<on|off> - turn portal redirection on or off."); @Override
helpText = "Default value: on. This feature monitors new portal creation and changes the target new portal " + public void execute(CommandSender sender, Player player, List<String> params, String worldName) {
"location if it is outside of the border. Try disabling this if you have problems with other plugins " + Config.setPortalRedirection(strAsBool(params.get(0)));
"related to portals.";
}
@Override if (player != null) {
public void cmdStatus(CommandSender sender) Config.log((Config.portalRedirection() ? "Enabled" : "Disabled") + " portal redirection at the command of player \"" + player.getName() + "\".");
{ cmdStatus(sender);
sender.sendMessage(C_HEAD + "Portal redirection is " + enabledColored(Config.portalRedirection()) + C_HEAD + "."); }
} }
@Override
public void execute(CommandSender sender, Player player, List<String> 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);
}
}
} }

View File

@ -1,37 +1,33 @@
package com.wimbli.WorldBorder.cmd; package com.wimbli.WorldBorder.cmd;
import java.util.List; import com.wimbli.WorldBorder.Config;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import com.wimbli.WorldBorder.Config; import java.util.List;
public class CmdPreventPlace extends WBCmd { public class CmdPreventPlace extends WBCmd {
public CmdPreventPlace() { public CmdPreventPlace() {
name = permission = "preventblockplace"; name = permission = "preventblockplace";
minParams = maxParams = 1; minParams = maxParams = 1;
addCmdExample(nameEmphasized() + "<on|off> - stop block placement past border."); addCmdExample(nameEmphasized() + "<on|off> - stop block placement past border.");
helpText = "Default value: off. When enabled, this setting will prevent players from placing blocks outside the world's 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 @Override
public void execute(CommandSender sender, Player player, List<String> params, String worldName) public void cmdStatus(CommandSender sender) {
{ sender.sendMessage(C_HEAD + "Prevention of block placement outside the border is " + enabledColored(Config.preventBlockPlace()) + C_HEAD + ".");
Config.setPreventBlockPlace(strAsBool(params.get(0))); }
if (player != null) @Override
{ public void execute(CommandSender sender, Player player, List<String> params, String worldName) {
Config.log((Config.preventBlockPlace() ? "Enabled" : "Disabled") + " preventblockplace at the command of player \"" + player.getName() + "\"."); Config.setPreventBlockPlace(strAsBool(params.get(0)));
cmdStatus(sender);
} if (player != null) {
} Config.log((Config.preventBlockPlace() ? "Enabled" : "Disabled") + " preventblockplace at the command of player \"" + player.getName() + "\".");
cmdStatus(sender);
}
}
} }

View File

@ -8,29 +8,26 @@ import java.util.List;
public class CmdPreventSpawn extends WBCmd { public class CmdPreventSpawn extends WBCmd {
public CmdPreventSpawn() { public CmdPreventSpawn() {
name = permission = "preventmobspawn"; name = permission = "preventmobspawn";
minParams = maxParams = 1; minParams = maxParams = 1;
addCmdExample(nameEmphasized() + "<on|off> - stop mob spawning past border."); addCmdExample(nameEmphasized() + "<on|off> - stop mob spawning past border.");
helpText = "Default value: off. When enabled, this setting will prevent mobs from naturally spawning outside the world's 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 @Override
public void execute(CommandSender sender, Player player, List<String> params, String worldName) public void cmdStatus(CommandSender sender) {
{ sender.sendMessage(C_HEAD + "Prevention of mob spawning outside the border is " + enabledColored(Config.preventMobSpawn()) + C_HEAD + ".");
Config.setPreventMobSpawn(strAsBool(params.get(0))); }
if (player != null) @Override
{ public void execute(CommandSender sender, Player player, List<String> params, String worldName) {
Config.log((Config.preventMobSpawn() ? "Enabled" : "Disabled") + " preventmobspawn at the command of player \"" + player.getName() + "\"."); Config.setPreventMobSpawn(strAsBool(params.get(0)));
cmdStatus(sender);
} if (player != null) {
} Config.log((Config.preventMobSpawn() ? "Enabled" : "Disabled") + " preventmobspawn at the command of player \"" + player.getName() + "\".");
cmdStatus(sender);
}
}
} }

View File

@ -1,91 +1,74 @@
package com.wimbli.WorldBorder.cmd; 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; import java.util.List;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import com.wimbli.WorldBorder.*; public class CmdRadius extends WBCmd {
public CmdRadius() {
name = permission = "radius";
hasWorldNameInput = true;
minParams = 1;
maxParams = 2;
addCmdExample(nameEmphasizedW() + "<radiusX> [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 <radiusX> and [radiusZ] to increase or decrease the existing radius rather than setting a new value.";
}
public class CmdRadius extends WBCmd @Override
{ public void execute(CommandSender sender, Player player, List<String> params, String worldName) {
public CmdRadius() if (worldName == null)
{ worldName = player.getWorld().getName();
name = permission = "radius";
hasWorldNameInput = true;
minParams = 1;
maxParams = 2;
addCmdExample(nameEmphasizedW() + "<radiusX> [radiusZ] - change radius."); BorderData border = Config.Border(worldName);
helpText = "Using this command you can adjust the radius of an existing border. If [radiusZ] is not " + if (border == null) {
"specified, the radiusX value will be used for both. You can also optionally specify + or - at the start " + sendErrorAndHelp(sender, "This world (\"" + worldName + "\") must first have a border set normally.");
"of <radiusX> and [radiusZ] to increase or decrease the existing radius rather than setting a new value."; return;
} }
@Override double x = border.getX();
public void execute(CommandSender sender, Player player, List<String> params, String worldName) double z = border.getZ();
{ int radiusX;
if (worldName == null) int radiusZ;
worldName = player.getWorld().getName(); 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));
BorderData border = Config.Border(worldName); if (params.size() == 2) {
if (border == null) if (params.get(1).startsWith("+")) {
{ // Add to the current radius
sendErrorAndHelp(sender, "This world (\"" + worldName + "\") must first have a border set normally."); radiusZ = border.getRadiusZ();
return; 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;
}
double x = border.getX(); Config.setBorder(worldName, radiusX, radiusZ, x, z);
double z = border.getZ();
int radiusX;
int 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 (player != null)
{ sender.sendMessage("Radius has been set. " + Config.BorderDescription(worldName));
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));
}
} }

View File

@ -1,34 +1,31 @@
package com.wimbli.WorldBorder.cmd; 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; import java.util.List;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import com.wimbli.WorldBorder.*; 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.";
}
public class CmdReload extends WBCmd @Override
{ public void execute(CommandSender sender, Player player, List<String> params, String worldName) {
public CmdReload() if (player != null)
{ Config.log("Reloading config file at the command of player \"" + player.getName() + "\".");
name = permission = "reload";
minParams = maxParams = 0;
addCmdExample(nameEmphasized() + "- re-load data from config.yml."); Config.load(WorldBorder.plugin, true);
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 if (player != null)
public void execute(CommandSender sender, Player player, List<String> params, String worldName) sender.sendMessage("WorldBorder configuration reloaded.");
{ }
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.");
}
} }

View File

@ -1,59 +1,50 @@
package com.wimbli.WorldBorder.cmd; 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.List;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import com.wimbli.WorldBorder.*; public class CmdRemount extends WBCmd {
public CmdRemount() {
name = permission = "remount";
minParams = maxParams = 1;
addCmdExample(nameEmphasized() + "<amount> - 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.";
}
public class CmdRemount extends WBCmd @Override
{ public void cmdStatus(CommandSender sender) {
public CmdRemount() int delay = Config.RemountTicks();
{ if (delay == 0)
name = permission = "remount"; sender.sendMessage(C_HEAD + "Remount delay set to 0. Players will be left dismounted when knocked back from the border while on a vehicle.");
minParams = maxParams = 1; 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.");
}
}
addCmdExample(nameEmphasized() + "<amount> - player remount delay after knockback."); @Override
helpText = "Default value: 0 (disabled). If set higher than 0, WorldBorder will attempt to re-mount players who " + public void execute(CommandSender sender, Player player, List<String> params, String worldName) {
"are knocked back from the border while riding something after this many server ticks. This setting can " + int delay = 0;
"cause really nasty glitches if enabled and set too low due to CraftBukkit teleportation problems."; 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;
}
@Override Config.setRemountTicks(delay);
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 if (player != null)
public void execute(CommandSender sender, Player player, List<String> params, String worldName) cmdStatus(sender);
{ }
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);
}
} }

View File

@ -1,145 +1,119 @@
package com.wimbli.WorldBorder.cmd; 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; import java.util.List;
import org.bukkit.Bukkit;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import org.bukkit.Location;
import org.bukkit.World;
import com.wimbli.WorldBorder.*; public class CmdSet extends WBCmd {
public CmdSet() {
name = permission = "set";
hasWorldNameInput = true;
consoleRequiresWorldName = false;
minParams = 1;
maxParams = 4;
addCmdExample(nameEmphasizedW() + "<radiusX> [radiusZ] <x> <z> - use x/z coords.");
addCmdExample(nameEmphasizedW() + "<radiusX> [radiusZ] ^spawn - use spawn point.");
addCmdExample(nameEmphasized() + "<radiusX> [radiusZ] - set border, centered on you.", true, false, true);
addCmdExample(nameEmphasized() + "<radiusX> [radiusZ] ^player <name> - 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 <x> and <z> coordinates can be decimal values (ex. 1.234).";
}
public class CmdSet extends WBCmd @Override
{ public void execute(CommandSender sender, Player player, List<String> params, String worldName) {
public CmdSet() // passsing a single parameter (radiusX) is only acceptable from player
{ if ((params.size() == 1) && player == null) {
name = permission = "set"; sendErrorAndHelp(sender, "You have not provided a sufficient number of parameters.");
hasWorldNameInput = true; return;
consoleRequiresWorldName = false; }
minParams = 1;
maxParams = 4;
addCmdExample(nameEmphasizedW() + "<radiusX> [radiusZ] <x> <z> - use x/z coords."); // "set" command from player or console, world specified
addCmdExample(nameEmphasizedW() + "<radiusX> [radiusZ] ^spawn - use spawn point."); if (worldName != null) {
addCmdExample(nameEmphasized() + "<radiusX> [radiusZ] - set border, centered on you.", true, false, true); 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
addCmdExample(nameEmphasized() + "<radiusX> [radiusZ] ^player <name> - center on player."); sendErrorAndHelp(sender, "You have not provided a sufficient number of arguments.");
helpText = "Set a border for a world, with several options for defining the center location. [world] is " + return;
"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 <x> and <z> coordinates can be decimal values (ex. 1.234).";
}
@Override World world = sender.getServer().getWorld(worldName);
public void execute(CommandSender sender, Player player, List<String> params, String worldName) if (world == null) {
{ if (params.get(params.size() - 1).equalsIgnoreCase("spawn")) {
// passsing a single parameter (radiusX) is only acceptable from player sendErrorAndHelp(sender, "The world you specified (\"" + worldName + "\") could not be found on the server, so the spawn point cannot be determined.");
if ((params.size() == 1) && player == null) return;
{ }
sendErrorAndHelp(sender, "You have not provided a sufficient number of parameters."); sender.sendMessage("The world you specified (\"" + worldName + "\") could not be found on the server, but data for it will be stored anyway.");
return; }
} }
// "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();
}
// "set" command from player or console, world specified int radiusX, radiusZ;
if (worldName != null) double x, z;
{ int radiusCount = params.size();
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); try {
if (world == null) if (params.get(params.size() - 1).equalsIgnoreCase("spawn")) { // "spawn" specified for x/z coordinates
{ Location loc = sender.getServer().getWorld(worldName).getSpawnLocation();
if (params.get(params.size() - 1).equalsIgnoreCase("spawn")) x = loc.getX();
{ z = loc.getZ();
sendErrorAndHelp(sender, "The world you specified (\"" + worldName + "\") could not be found on the server, so the spawn point cannot be determined."); radiusCount -= 1;
return; } 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));
sender.sendMessage("The world you specified (\"" + worldName + "\") could not be found on the server, but data for it will be stored anyway."); if (playerT == null || !playerT.isOnline()) {
} sendErrorAndHelp(sender, "The player you specified (\"" + params.get(params.size() - 1) + "\") does not appear to be online.");
} return;
// "set" command from player using current world since it isn't specified, or allowed from console only if player name is specified }
else worldName = playerT.getWorld().getName();
{ x = playerT.getLocation().getX();
if (player == null) z = playerT.getLocation().getZ();
{ radiusCount -= 2;
if (! params.get(params.size() - 2).equalsIgnoreCase("player")) } else {
{ // command can only be called by console without world specified if player is specified instead if (player == null || radiusCount > 2) { // x and z specified
sendErrorAndHelp(sender, "You must specify a world name from console if not specifying a player name."); x = Double.parseDouble(params.get(params.size() - 2));
return; z = Double.parseDouble(params.get(params.size() - 1));
} radiusCount -= 2;
player = Bukkit.getPlayer(params.get(params.size() - 1)); } else { // using coordinates of command sender (player)
if (player == null || ! player.isOnline()) x = player.getLocation().getX();
{ z = player.getLocation().getZ();
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; radiusX = Integer.parseInt(params.get(0));
double x, z; if (radiusCount < 2)
int radiusCount = params.size(); radiusZ = radiusX;
else
radiusZ = Integer.parseInt(params.get(1));
try if (radiusX < Config.KnockBack() || radiusZ < Config.KnockBack()) {
{ sendErrorAndHelp(sender, "Radius value(s) must be more than the knockback distance.");
if (params.get(params.size() - 1).equalsIgnoreCase("spawn")) return;
{ // "spawn" specified for x/z coordinates }
Location loc = sender.getServer().getWorld(worldName).getSpawnLocation(); } catch (NumberFormatException ex) {
x = loc.getX(); sendErrorAndHelp(sender, "Radius value(s) must be integers and x and z values must be numerical.");
z = loc.getZ(); return;
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)); Config.setBorder(worldName, radiusX, radiusZ, x, z);
if (radiusCount < 2) sender.sendMessage("Border has been set. " + Config.BorderDescription(worldName));
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));
}
} }

View File

@ -1,58 +1,48 @@
package com.wimbli.WorldBorder.cmd; 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; import java.util.List;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import org.bukkit.World;
import com.wimbli.WorldBorder.*; public class CmdSetcorners extends WBCmd {
public CmdSetcorners() {
name = "setcorners";
permission = "set";
hasWorldNameInput = true;
minParams = maxParams = 4;
addCmdExample(nameEmphasizedW() + "<x1> <z1> <x2> <z2> - 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.";
}
public class CmdSetcorners extends WBCmd @Override
{ public void execute(CommandSender sender, Player player, List<String> params, String worldName) {
public CmdSetcorners() if (worldName == null) {
{ worldName = player.getWorld().getName();
name = "setcorners"; } else {
permission = "set"; World worldTest = sender.getServer().getWorld(worldName);
hasWorldNameInput = true; if (worldTest == null)
minParams = maxParams = 4; sender.sendMessage("The world you specified (\"" + worldName + "\") could not be found on the server, but data for it will be stored anyway.");
}
addCmdExample(nameEmphasizedW() + "<x1> <z1> <x2> <z2> - corner coords."); try {
helpText = "This is an alternate way to set a border, by specifying the X and Z coordinates of two opposite " + double x1 = Double.parseDouble(params.get(0));
"corners of the border area ((x1, z1) to (x2, z2)). [world] is optional for players and defaults to the " + double z1 = Double.parseDouble(params.get(1));
"world the player is in."; 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;
}
@Override if (player != null)
public void execute(CommandSender sender, Player player, List<String> params, String worldName) sender.sendMessage("Border has been set. " + Config.BorderDescription(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));
}
} }

View File

@ -1,48 +1,42 @@
package com.wimbli.WorldBorder.cmd; 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.List;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import com.wimbli.WorldBorder.*; public class CmdSetmsg extends WBCmd {
public CmdSetmsg() {
name = permission = "setmsg";
minParams = 1;
addCmdExample(nameEmphasized() + "<text> - 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.";
}
public class CmdSetmsg extends WBCmd @Override
{ public void cmdStatus(CommandSender sender) {
public CmdSetmsg() sender.sendMessage(C_HEAD + "Border message is set to:");
{ sender.sendMessage(Config.MessageRaw());
name = permission = "setmsg"; sender.sendMessage(C_HEAD + "Formatted border message:");
minParams = 1; sender.sendMessage(Config.Message());
}
addCmdExample(nameEmphasized() + "<text> - set border message."); @Override
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."; public void execute(CommandSender sender, Player player, List<String> params, String worldName) {
} StringBuilder message = new StringBuilder();
boolean first = true;
for (String param : params) {
if (!first)
message.append(" ");
message.append(param);
first = false;
}
@Override Config.setMessage(message.toString());
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 cmdStatus(sender);
public void execute(CommandSender sender, Player player, List<String> 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);
}
} }

View File

@ -1,49 +1,43 @@
package com.wimbli.WorldBorder.cmd; 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.List;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import com.wimbli.WorldBorder.*; public class CmdShape extends WBCmd {
public CmdShape() {
name = permission = "shape";
minParams = maxParams = 1;
addCmdExample(nameEmphasized() + "<round|square> - set the default border shape.");
addCmdExample(nameEmphasized() + "<elliptic|rectangular> - same as above.");
helpText = "Default value: round/elliptic. 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.";
}
public class CmdShape extends WBCmd @Override
{ public void cmdStatus(CommandSender sender) {
public CmdShape() sender.sendMessage(C_HEAD + "The default border shape for all worlds is currently set to \"" + Config.ShapeName() + "\".");
{ }
name = permission = "shape";
minParams = maxParams = 1;
addCmdExample(nameEmphasized() + "<round|square> - set the default border shape."); @Override
addCmdExample(nameEmphasized() + "<elliptic|rectangular> - same as above."); public void execute(CommandSender sender, Player player, List<String> params, String worldName) {
helpText = "Default value: round/elliptic. The default border shape will be used on all worlds which don't " + String shape = params.get(0).toLowerCase();
"have an individual shape set using the " + commandEmphasized("wshape") + C_DESC + "command. Elliptic " + if (shape.equals("rectangular") || shape.equals("square"))
"and round work the same, as rectangular and square do. The difference is down to whether the X and Z " + Config.setShape(false);
"radius are the same."; 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;
}
@Override if (player != null)
public void cmdStatus(CommandSender sender) cmdStatus(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<String> 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);
}
} }

View File

@ -1,175 +1,152 @@
package com.wimbli.WorldBorder.cmd; 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; import java.util.List;
import org.bukkit.Bukkit;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import com.wimbli.WorldBorder.*; 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;
public class CmdTrim extends WBCmd addCmdExample(nameEmphasizedW() + "[freq] [pad] - trim world outside of border.");
{ helpText = "This command will remove chunks which are outside the world's border. [freq] is the frequency " +
public CmdTrim() "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).";
name = permission = "trim"; }
hasWorldNameInput = true;
consoleRequiresWorldName = false;
minParams = 0;
maxParams = 2;
addCmdExample(nameEmphasizedW() + "[freq] [pad] - trim world outside of border."); @Override
helpText = "This command will remove chunks which are outside the world's border. [freq] is the frequency " + public void execute(CommandSender sender, Player player, List<String> params, String worldName) {
"of chunks per second that will be checked (default 5000). [pad] is the number of blocks padding kept " + boolean confirm = false;
"beyond the border itself (default 208, to cover player visual range)."; // check for "cancel", "pause", or "confirm"
} if (params.size() >= 1) {
String check = params.get(0).toLowerCase();
@Override if (check.equals("cancel") || check.equals("stop")) {
public void execute(CommandSender sender, Player player, List<String> params, String worldName) if (!makeSureTrimIsRunning(sender))
{ return;
boolean confirm = false; sender.sendMessage(C_HEAD + "Cancelling the world map trimming task.");
// check for "cancel", "pause", or "confirm" trimDefaults();
if (params.size() >= 1) Config.StopTrimTask();
{ return;
String check = params.get(0).toLowerCase(); } 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;
}
if (check.equals("cancel") || check.equals("stop")) confirm = check.equals("confirm");
{ }
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;
}
}
// if not just confirming, make sure a world name is available // colorized "/wb trim "
if (worldName == null && !confirm) String cmd = cmd(sender) + nameEmphasized() + C_CMD;
{
if (player != null)
worldName = player.getWorld().getName();
else
{
sendErrorAndHelp(sender, "You must specify a world!");
return;
}
}
// colorized "/wb trim " // make sure Trim isn't already running
String cmd = cmd(sender) + nameEmphasized() + C_CMD; 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;
}
// make sure Trim isn't already running // set frequency and/or padding if those were specified
if (Config.trimTask != null && Config.trimTask.valid()) try {
{ if (params.size() >= 1 && !confirm)
sender.sendMessage(C_ERR + "The world map trimming task is already running."); trimFrequency = Math.abs(Integer.parseInt(params.get(0)));
sender.sendMessage(C_DESC + "You can cancel at any time with " + cmd + "cancel" + C_DESC + ", or pause/unpause with " + cmd + "pause" + C_DESC + "."); if (params.size() >= 2 && !confirm)
return; 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 frequency and/or padding if those were specified // set world if it was specified
try if (worldName != null)
{ trimWorld = worldName;
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 (confirm) { // command confirmed, go ahead with it
if (worldName != null) if (trimWorld.isEmpty()) {
trimWorld = worldName; sendErrorAndHelp(sender, "You must first use this command successfully without confirming.");
return;
}
if (confirm) if (player != null)
{ // command confirmed, go ahead with it Config.log("Trimming world beyond border at the command of player \"" + player.getName() + "\".");
if (trimWorld.isEmpty())
{
sendErrorAndHelp(sender, "You must first use this command successfully without confirming.");
return;
}
if (player != null) int ticks = 1, repeats = 1;
Config.log("Trimming world beyond border at the command of player \"" + player.getName() + "\"."); if (trimFrequency > 20)
repeats = trimFrequency / 20;
else
ticks = 20 / trimFrequency;
int ticks = 1, repeats = 1; Config.trimTask = new WorldTrimTask(Bukkit.getServer(), player, trimWorld, trimPadding, repeats);
if (trimFrequency > 20) if (Config.trimTask.valid()) {
repeats = trimFrequency / 20; int task = Bukkit.getServer().getScheduler().scheduleSyncRepeatingTask(WorldBorder.plugin, Config.trimTask, ticks, ticks);
else Config.trimTask.setTaskID(task);
ticks = 20 / trimFrequency; sender.sendMessage("WorldBorder map trimming task for world \"" + trimWorld + "\" started.");
} else
sender.sendMessage(C_ERR + "The world map trimming task failed to start.");
Config.trimTask = new WorldTrimTask(Bukkit.getServer(), player, trimWorld, trimPadding, repeats); trimDefaults();
if (Config.trimTask.valid()) } else {
{ if (trimWorld.isEmpty()) {
int task = Bukkit.getServer().getScheduler().scheduleSyncRepeatingTask(WorldBorder.plugin, Config.trimTask, ticks, ticks); sendErrorAndHelp(sender, "You must first specify a valid world.");
Config.trimTask.setTaskID(task); return;
sender.sendMessage("WorldBorder map trimming task for world \"" + trimWorld + "\" started."); }
}
else
sender.sendMessage(C_ERR + "The world map trimming task failed to start.");
trimDefaults(); 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.");
else 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 + ".");
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 + ")."); private void trimDefaults() {
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."); trimWorld = "";
sender.sendMessage(C_DESC + "You should now use " + cmd + "confirm" + C_DESC + " to start the process."); trimFrequency = 5000;
sender.sendMessage(C_DESC + "You can cancel at any time with " + cmd + "cancel" + C_DESC + ", or pause/unpause with " + cmd + "pause" + C_DESC + "."); trimPadding = defaultPadding;
} }
}
/* with "view-distance=10" in server.properties on a fast VM test server and "Render Distance: Far" in client, private boolean makeSureTrimIsRunning(CommandSender sender) {
* hitting border during testing was loading 11+ chunks beyond the border in a couple of directions (10 chunks in if (Config.trimTask != null && Config.trimTask.valid())
* the other two directions). This could be worse on a more loaded or worse server, so: return true;
*/ sendErrorAndHelp(sender, "The world map trimming task is not currently running.");
private final int defaultPadding = CoordXZ.chunkToBlock(13); return false;
}
private String trimWorld = "";
private int trimFrequency = 5000;
private int trimPadding = defaultPadding;
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;
}
} }

View File

@ -1,40 +1,34 @@
package com.wimbli.WorldBorder.cmd; 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.List;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import com.wimbli.WorldBorder.*; public class CmdWhoosh extends WBCmd {
public CmdWhoosh() {
name = permission = "whoosh";
minParams = maxParams = 1;
addCmdExample(nameEmphasized() + "<on|off> - 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.";
}
public class CmdWhoosh extends WBCmd @Override
{ public void cmdStatus(CommandSender sender) {
public CmdWhoosh() sender.sendMessage(C_HEAD + "\"Whoosh\" knockback effect is " + enabledColored(Config.whooshEffect()) + C_HEAD + ".");
{ }
name = permission = "whoosh";
minParams = maxParams = 1;
addCmdExample(nameEmphasized() + "<on|off> - turn knockback effect on or off."); @Override
helpText = "Default value: on. This will show a particle effect and play a sound where a player is knocked " + public void execute(CommandSender sender, Player player, List<String> params, String worldName) {
"back from the border."; Config.setWhooshEffect(strAsBool(params.get(0)));
}
@Override if (player != null) {
public void cmdStatus(CommandSender sender) Config.log((Config.whooshEffect() ? "Enabled" : "Disabled") + " \"whoosh\" knockback effect at the command of player \"" + player.getName() + "\".");
{ cmdStatus(sender);
sender.sendMessage(C_HEAD + "\"Whoosh\" knockback effect is " + enabledColored(Config.whooshEffect()) + C_HEAD + "."); }
} }
@Override
public void execute(CommandSender sender, Player player, List<String> 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);
}
}
} }

View File

@ -1,61 +1,54 @@
package com.wimbli.WorldBorder.cmd; 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; import java.util.List;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import com.wimbli.WorldBorder.*; public class CmdWrap extends WBCmd {
public CmdWrap() {
name = permission = "wrap";
minParams = 1;
maxParams = 2;
addCmdExample(nameEmphasized() + "{world} <on|off> - 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.";
}
public class CmdWrap extends WBCmd @Override
{ public void execute(CommandSender sender, Player player, List<String> params, String worldName) {
public CmdWrap() if (player == null && params.size() == 1) {
{ sendErrorAndHelp(sender, "When running this command from console, you must specify a world.");
name = permission = "wrap"; return;
minParams = 1; }
maxParams = 2;
addCmdExample(nameEmphasized() + "{world} <on|off> - can make border crossings wrap."); boolean wrap = false;
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 // world and wrap on/off specified
public void execute(CommandSender sender, Player player, List<String> params, String worldName) if (params.size() == 2) {
{ worldName = params.get(0);
if (player == null && params.size() == 1) wrap = strAsBool(params.get(1));
{ }
sendErrorAndHelp(sender, "When running this command from console, you must specify a world."); // no world specified, just wrap on/off
return; else {
} worldName = player.getWorld().getName();
wrap = strAsBool(params.get(0));
}
boolean wrap = false; BorderData border = Config.Border(worldName);
if (border == null) {
sendErrorAndHelp(sender, "This world (\"" + worldName + "\") does not have a border set.");
return;
}
// world and wrap on/off specified border.setWrapping(wrap);
if (params.size() == 2) Config.setBorder(worldName, border, false);
{
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); sender.sendMessage("Border for world \"" + worldName + "\" is now set to " + (wrap ? "" : "not ") + "wrap around.");
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.");
}
} }

View File

@ -1,69 +1,62 @@
package com.wimbli.WorldBorder.cmd; 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; import java.util.List;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import com.wimbli.WorldBorder.*; public class CmdWshape extends WBCmd {
public CmdWshape() {
name = permission = "wshape";
minParams = 1;
maxParams = 2;
addCmdExample(nameEmphasized() + "{world} <elliptic|rectangular|default> - shape");
addCmdExample(C_DESC + " override for a single world.", true, true, false);
addCmdExample(nameEmphasized() + "{world} <round|square|default> - 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.";
}
public class CmdWshape extends WBCmd @Override
{ public void execute(CommandSender sender, Player player, List<String> params, String worldName) {
public CmdWshape() if (player == null && params.size() == 1) {
{ sendErrorAndHelp(sender, "When running this command from console, you must specify a world.");
name = permission = "wshape"; return;
minParams = 1; }
maxParams = 2;
addCmdExample(nameEmphasized() + "{world} <elliptic|rectangular|default> - shape"); String shapeName = "";
addCmdExample(C_DESC + " override for a single world.", true, true, false);
addCmdExample(nameEmphasized() + "{world} <round|square|default> - 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 // world and shape specified
public void execute(CommandSender sender, Player player, List<String> params, String worldName) if (params.size() == 2) {
{ worldName = params.get(0);
if (player == null && params.size() == 1) shapeName = params.get(1).toLowerCase();
{ }
sendErrorAndHelp(sender, "When running this command from console, you must specify a world."); // no world specified, just shape
return; else {
} worldName = player.getWorld().getName();
shapeName = params.get(0).toLowerCase();
}
String shapeName = ""; BorderData border = Config.Border(worldName);
if (border == null) {
sendErrorAndHelp(sender, "This world (\"" + worldName + "\") does not have a border set.");
return;
}
// world and shape specified Boolean shape = null;
if (params.size() == 2) if (shapeName.equals("rectangular") || shapeName.equals("square"))
{ shape = false;
worldName = params.get(0); else if (shapeName.equals("elliptic") || shapeName.equals("round"))
shapeName = params.get(1).toLowerCase(); shape = true;
}
// no world specified, just shape
else
{
worldName = player.getWorld().getName();
shapeName = params.get(0).toLowerCase();
}
BorderData border = Config.Border(worldName); border.setShape(shape);
if (border == null) Config.setBorder(worldName, border, false);
{
sendErrorAndHelp(sender, "This world (\"" + worldName + "\") does not have a border set.");
return;
}
Boolean shape = null; sender.sendMessage("Border shape for world \"" + worldName + "\" is now set to \"" + Config.ShapeName(shape) + "\".");
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) + "\".");
}
} }

View File

@ -1,147 +1,128 @@
package com.wimbli.WorldBorder.cmd; 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.ArrayList;
import java.util.List; import java.util.List;
import org.bukkit.ChatColor;
import org.bukkit.command.*; public abstract class WBCmd {
import org.bukkit.entity.Player; /*
* 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<String> cmdExamplesConsole = new ArrayList<String>(48); // 48 command capacity, 6 full pages
public abstract class WBCmd /*
{ * Helper variables and methods
/* */
* Primary variables, should be set as needed in constructors for the subclassed commands public final static List<String> cmdExamplesPlayer = new ArrayList<String>(48); // still, could need to increase later
*/ // 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;
// 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 List<String> cmdExamplePlayer = new ArrayList<String>();
public List<String> cmdExampleConsole = new ArrayList<String>();
// command name, command permission; normally the same thing /*
public String name = ""; * The guts of the command run in here; needs to be overriden in the subclassed commands
public String permission = null; */
public abstract void execute(CommandSender sender, Player player, List<String> params, String worldName);
// whether command can accept a world name before itself /*
public boolean hasWorldNameInput = false; * This is an optional override, used to provide some extra command status info, like the currently set value
public boolean consoleRequiresWorldName = true; */
public void cmdStatus(CommandSender sender) {
}
// minimum and maximum number of accepted parameters // add command examples for use the default "/wb" command list and for internal usage reference, formatted and colorized
public int minParams = 0; public void addCmdExample(String example) {
public int maxParams = 9999; addCmdExample(example, true, true, true);
}
// help/explanation text to be shown after command example(s) for this command public void addCmdExample(String example, boolean forPlayer, boolean forConsole, boolean prefix) {
public String helpText = null; // 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
* The guts of the command run in here; needs to be overriden in the subclassed commands if (forPlayer) {
*/ String exampleP = (prefix ? CMD_P : "") + example.replace("{", C_OPT + "[").replace("}", "]");
public abstract void execute(CommandSender sender, Player player, List<String> params, String worldName); 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
* This is an optional override, used to provide some extra command status info, like the currently set value public String cmd(CommandSender sender) {
*/ return (sender instanceof Player) ? CMD_P : CMD_C;
public void cmdStatus(CommandSender sender) {} }
// 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
* Helper variables and methods public String enabledColored(boolean enabled) {
*/ return enabled ? C_REQ + "enabled" : C_ERR + "disabled";
}
// color values for strings // formatted and colorized command name, optionally prefixed with "[world]" (for player) / "<world>" (for console)
public final static String C_CMD = ChatColor.AQUA.toString(); // main commands public String nameEmphasized() {
public final static String C_DESC = ChatColor.WHITE.toString(); // command descriptions return commandEmphasized(name);
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 String nameEmphasizedW() {
public final static String CMD_C = C_CMD + "wb "; return "{world} " + nameEmphasized();
public final static String CMD_P = C_CMD + "/wb "; }
// list of command examples for this command to be displayed as usage reference, separate between players and console // send command example message(s) and other helpful info
// ... these generally should be set indirectly using addCmdExample() within the constructor for each command class public void sendCmdHelp(CommandSender sender) {
public List<String> cmdExamplePlayer = new ArrayList<String>(); for (String example : ((sender instanceof Player) ? cmdExamplePlayer : cmdExampleConsole)) {
public List<String> cmdExampleConsole = new ArrayList<String>(); sender.sendMessage(example);
}
cmdStatus(sender);
if (helpText != null && !helpText.isEmpty())
sender.sendMessage(C_DESC + helpText);
}
// much like the above, but used for displaying command list from root /wb command, listing all commands // send error message followed by command example message(s)
public final static List<String> cmdExamplesConsole = new ArrayList<String>(48); // 48 command capacity, 6 full pages public void sendErrorAndHelp(CommandSender sender, String error) {
public final static List<String> cmdExamplesPlayer = new ArrayList<String>(48); // still, could need to increase later sender.sendMessage(C_ERR + error);
sendCmdHelp(sender);
}
// interpret string as boolean value (yes/no, true/false, on/off, +/-, 1/0)
// add command examples for use the default "/wb" command list and for internal usage reference, formatted and colorized public boolean strAsBool(String str) {
public void addCmdExample(String example) str = str.toLowerCase();
{ return str.startsWith("y") || str.startsWith("t") || str.startsWith("on") || str.startsWith("+") || str.startsWith("1");
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) / "<world>" (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");
}
} }

View File

@ -1,7 +1,7 @@
name: WorldBorder name: WorldBorder
author: Brettflan authors: [Brettflan, PryPurity]
description: Efficient, feature-rich plugin for limiting the size of your worlds. description: Efficient, feature-rich plugin for limiting the size of your worlds.
version: 1.9.10 (beta) version: 2.0.0 (beta)
api-version: 1.13 api-version: 1.13
main: com.wimbli.WorldBorder.WorldBorder main: com.wimbli.WorldBorder.WorldBorder
softdepend: softdepend:
@ -11,41 +11,41 @@ commands:
description: Primary command for WorldBorder. description: Primary command for WorldBorder.
aliases: [wb] aliases: [wb]
usage: | usage: |
/<command> - list available commands (show help). /<command> - list available commands (show help).
/<command> help [command] - get help on command usage. /<command> help [command] - get help on command usage.
/<command> [world] set <radiusX> [radiusZ] <x> <z> - set world border. /<command> [world] set <radiusX> [radiusZ] <x> <z> - set world border.
/<command> [world] set <radiusX> [radiusZ] spawn - use spawn point. /<command> [world] set <radiusX> [radiusZ] spawn - use spawn point.
/<command> set <radiusX> [radiusZ] - set world border, centered on you. /<command> set <radiusX> [radiusZ] - set world border, centered on you.
/<command> set <radiusX> [radiusZ] player <name> - center on player. /<command> set <radiusX> [radiusZ] player <name> - center on player.
/<command> [world] setcorners <x1> <z1> <x2> <z2> - set border from corners. /<command> [world] setcorners <x1> <z1> <x2> <z2> - set border from corners.
/<command> [world] radius <radiusX> [radiusZ] - change border's radius. /<command> [world] radius <radiusX> [radiusZ] - change border's radius.
/<command> list - show border information for all worlds. /<command> list - show border information for all worlds.
/<command> shape <elliptic|rectangular> - set the default border shape. /<command> shape <elliptic|rectangular> - set the default border shape.
/<command> shape <round|square> - same as above, backwards compatible. /<command> shape <round|square> - same as above, backwards compatible.
/<command> [world] clear - remove border for this world. /<command> [world] clear - remove border for this world.
/<command> clear all - remove border for all worlds. /<command> clear all - remove border for all worlds.
/<command> [world] fill [freq] [pad] [force] - generate world to border. /<command> [world] fill [freq] [pad] [force] - generate world to border.
/<command> [world] trim [freq] [pad] - trim world outside of border. /<command> [world] trim [freq] [pad] - trim world outside of border.
/<command> bypass [player] [on/off] - let player go beyond border. /<command> bypass [player] [on/off] - let player go beyond border.
/<command> bypasslist - list players with border bypass enabled. /<command> bypasslist - list players with border bypass enabled.
/<command> knockback <distance> - how far to move the player back. /<command> knockback <distance> - how far to move the player back.
/<command> wrap [world] <on/off> - can make border crossings wrap around. /<command> wrap [world] <on/off> - can make border crossings wrap around.
/<command> whoosh <on/off> - turn knockback effect on or off. /<command> whoosh <on/off> - turn knockback effect on or off.
/<command> getmsg - display border message. /<command> getmsg - display border message.
/<command> setmsg <text> - set border message. /<command> setmsg <text> - set border message.
/<command> wshape [world] <elliptic|rectangular|default> - override shape. /<command> wshape [world] <elliptic|rectangular|default> - override shape.
/<command> wshape [world] <round|square|default> - same as above values. /<command> wshape [world] <round|square|default> - same as above values.
/<command> delay <amount> - time between border checks. /<command> delay <amount> - time between border checks.
/<command> dynmap <on/off> - turn DynMap border display on or off. /<command> dynmap <on/off> - turn DynMap border display on or off.
/<command> dynmapmsg <text> - DynMap border labels will show this. /<command> dynmapmsg <text> - DynMap border labels will show this.
/<command> remount <amount> - delay before remounting after knockback. /<command> remount <amount> - delay before remounting after knockback.
/<command> fillautosave <seconds> - world save interval for Fill process. /<command> fillautosave <seconds> - world save interval for Fill process.
/<command> portal <on/off> - turn portal redirection on or off. /<command> portal <on/off> - turn portal redirection on or off.
/<command> denypearl <on/off> - stop ender pearls thrown past the border. /<command> denypearl <on/off> - stop ender pearls thrown past the border.
/<command> preventblockplace <on|off> - stop block placement past border. /<command> preventblockplace <on|off> - stop block placement past border.
/<command> preventmobspawn <on|off> - stop mob spawning past border. /<command> preventmobspawn <on|off> - stop mob spawning past border.
/<command> reload - re-load data from config.yml. /<command> reload - re-load data from config.yml.
/<command> debug <on/off> - turn debug mode on or off. /<command> debug <on/off> - turn debug mode on or off.
permissions: permissions:
worldborder.*: worldborder.*:
description: Grants all WorldBorder permissions description: Grants all WorldBorder permissions