Island delete enhancements

Island deletion is done a few chunks at a time per tick. Current speed
is 5 chunks per tick per world (e.g., 15 chunks per tick if nether and
end islands are used).

Chunks are deleted based on the all-time maximum protection range of the
island. This is because the protection range can grow bigger or smaller
over the island's lifetime. To ensure all possible chunks are erased,
the largest every protection range is remembered and used.

Very large protection ranges will take a long time to fully delete.

Info on islands that are being deleted is stored in the database. If the
server shuts down mid-deletion, deletion will restart when the server
restarts.

While an island is being deleted, new islands cannot occupy that spot
and the spot cannot be reserved by the admin.

In addition, async approaches to island saving and player saving were
removed. Async will be implemented another way.

Now, instead of saving the full island or player database, individual
database entries are saved instead to be more efficient.
This commit is contained in:
tastybento 2019-01-12 17:12:30 -08:00
parent 7a4ca8b48d
commit 501c3257ed
27 changed files with 562 additions and 409 deletions

View File

@ -25,6 +25,7 @@ import world.bentobox.bentobox.managers.AddonsManager;
import world.bentobox.bentobox.managers.CommandsManager;
import world.bentobox.bentobox.managers.FlagsManager;
import world.bentobox.bentobox.managers.HooksManager;
import world.bentobox.bentobox.managers.IslandDeleteManager;
import world.bentobox.bentobox.managers.IslandWorldManager;
import world.bentobox.bentobox.managers.IslandsManager;
import world.bentobox.bentobox.managers.LocalesManager;
@ -67,6 +68,8 @@ public class BentoBox extends JavaPlugin {
private boolean isLoaded;
private IslandDeleteManager islandDeletionManager;
@Override
public void onEnable(){
if (!ServerCompatibility.getInstance().checkCompatibility(this).isCanLaunch()) {
@ -142,10 +145,10 @@ public class BentoBox extends JavaPlugin {
// Load islands from database - need to wait until all the worlds are loaded
islandsManager.load();
// Save islands & players data asynchronously every X minutes
// Save islands & players data every X minutes
instance.getServer().getScheduler().runTaskTimer(instance, () -> {
playersManager.save(true);
islandsManager.save(true);
playersManager.saveAll();
islandsManager.saveAll();
}, getSettings().getDatabaseBackupPeriod() * 20 * 60L, getSettings().getDatabaseBackupPeriod() * 20 * 60L);
// Make sure all flag listeners are registered.
@ -169,13 +172,15 @@ public class BentoBox extends JavaPlugin {
// Setup the Placeholders manager
placeholdersManager = new PlaceholdersManager(this);
// Fire plugin ready event
isLoaded = true;
Bukkit.getServer().getPluginManager().callEvent(new BentoBoxReadyEvent());
// Show banner
User.getInstance(Bukkit.getConsoleSender()).sendMessage("successfully-loaded",
TextVariables.VERSION, instance.getDescription().getVersion(),
"[time]", String.valueOf(System.currentTimeMillis() - startMillis));
// Fire plugin ready event - this should go last after everything else
isLoaded = true;
Bukkit.getServer().getPluginManager().callEvent(new BentoBoxReadyEvent());
});
}
@ -196,6 +201,9 @@ public class BentoBox extends JavaPlugin {
manager.registerEvents(new BannedVisitorCommands(this), this);
// Death counter
manager.registerEvents(new DeathListener(this), this);
// Island Delete Manager
islandDeletionManager = new IslandDeleteManager(this);
manager.registerEvents(islandDeletionManager, this);
}
@Override
@ -356,4 +364,11 @@ public class BentoBox extends JavaPlugin {
public PlaceholdersManager getPlaceholdersManager() {
return placeholdersManager;
}
/**
* @return the islandDeletionManager
*/
public IslandDeleteManager getIslandDeletionManager() {
return islandDeletionManager;
}
}

View File

@ -51,6 +51,12 @@ public class AdminRegisterCommand extends ConfirmableCommand {
return false;
}
// Check if this spot is still being deleted
Location closestIsland = getClosestIsland(user.getLocation());
if (getPlugin().getIslandDeletionManager().inDeletion(closestIsland)) {
user.sendMessage("commands.admin.register.in-deletion");
return false;
}
// Check if island is owned
Optional<Island> island = getIslands().getIslandAt(user.getLocation());
if (island.map(i -> i.getOwner() != null).orElse(false)) {
@ -69,7 +75,7 @@ public class AdminRegisterCommand extends ConfirmableCommand {
user.sendMessage("commands.admin.register.no-island-here");
this.askConfirmation(user, () -> {
// Make island here
Island i = getIslands().createIsland(getClosestIsland(user.getLocation()), targetUUID);
Island i = getIslands().createIsland(closestIsland, targetUUID);
getIslands().setOwner(user, targetUUID, i);
getWorld().getBlockAt(i.getCenter()).setType(Material.BEDROCK);
user.sendMessage("commands.admin.register.registered-island", "[xyz]", Util.xyz(i.getCenter().toVector()));

View File

@ -1,12 +1,15 @@
package world.bentobox.bentobox.api.commands.admin.range;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.util.Util;
public class AdminRangeResetCommand extends CompositeCommand {
@ -50,4 +53,15 @@ public class AdminRangeResetCommand extends CompositeCommand {
return true;
}
@Override
public Optional<List<String>> tabComplete(User user, String alias, List<String> args) {
String lastArg = !args.isEmpty() ? args.get(args.size()-1) : "";
if (args.isEmpty()) {
// Don't show every player on the server. Require at least the first letter
return Optional.empty();
}
List<String> options = new ArrayList<>(Util.getOnlinePlayerList(user));
return Optional.of(Util.tabLimit(options, lastArg));
}
}

View File

@ -1,6 +1,8 @@
package world.bentobox.bentobox.api.commands.admin.range;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.apache.commons.lang.StringUtils;
@ -9,6 +11,7 @@ import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.util.Util;
public class AdminRangeSetCommand extends CompositeCommand {
@ -72,4 +75,15 @@ public class AdminRangeSetCommand extends CompositeCommand {
return true;
}
@Override
public Optional<List<String>> tabComplete(User user, String alias, List<String> args) {
String lastArg = !args.isEmpty() ? args.get(args.size()-1) : "";
if (args.isEmpty()) {
// Don't show every player on the server. Require at least the first letter
return Optional.empty();
}
List<String> options = new ArrayList<>(Util.getOnlinePlayerList(user));
return Optional.of(Util.tabLimit(options, lastArg));
}
}

View File

@ -106,7 +106,7 @@ public class IslandTeamInviteAcceptCommand extends CompositeCommand {
if (inviter != null) {
inviter.sendMessage("commands.island.team.invite.accept.name-joined-your-island", TextVariables.NAME, user.getName());
}
getIslands().save(false);
getIslands().save(island);
return true;
}

View File

@ -10,6 +10,7 @@ import world.bentobox.bentobox.api.events.IslandBaseEvent;
import world.bentobox.bentobox.api.events.team.TeamEvent;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.util.Util;
public class IslandTeamSetownerCommand extends CompositeCommand {
@ -59,9 +60,9 @@ public class IslandTeamSetownerCommand extends CompositeCommand {
return false;
}
// Fire event so add-ons can run commands, etc.
Island island = getIslands().getIsland(getWorld(), playerUUID);
IslandBaseEvent event = TeamEvent.builder()
.island(getIslands()
.getIsland(getWorld(), playerUUID))
.island(island)
.reason(TeamEvent.Reason.SETOWNER)
.involvedPlayer(targetUUID)
.build();
@ -70,7 +71,7 @@ public class IslandTeamSetownerCommand extends CompositeCommand {
return false;
}
getIslands().setOwner(getWorld(), user, targetUUID);
getIslands().save(true);
getIslands().save(island);
return true;
}

View File

@ -43,7 +43,7 @@ public class IslandBaseEvent extends PremadeEvent implements Cancellable {
}
/**
* @return the island involved in this event
* @return the island involved in this event. This may be null in the case of deleted islands, so use location instead
*/
public Island getIsland(){
return island;

View File

@ -6,6 +6,7 @@ import org.bukkit.Bukkit;
import org.bukkit.Location;
import world.bentobox.bentobox.api.events.IslandBaseEvent;
import world.bentobox.bentobox.database.objects.DeletedIslandDO;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.lists.Flags;
@ -58,9 +59,14 @@ public class IslandEvent extends IslandBaseEvent {
*/
CREATED,
/**
* Fired just before any island chunks are to be deleted.
* Fired when an island is to be deleted. Note an island can be deleted without having
* chunks removed.
*/
DELETE,
/**
* Fired when island chunks are going to be deleted
*/
DELETE_CHUNKS,
/**
* Fired after all island chunks have been deleted or set for regeneration by the server
*/
@ -168,14 +174,39 @@ public class IslandEvent extends IslandBaseEvent {
super(island, player, admin, location);
}
}
/**
* Fired when an island chunks are going to be deleted.
* May be cancelled.
*
*/
public static class IslandDeleteChunksEvent extends IslandBaseEvent {
private final DeletedIslandDO deletedIslandInfo;
private IslandDeleteChunksEvent(Island island, UUID player, boolean admin, Location location, DeletedIslandDO deletedIsland) {
// Final variables have to be declared in the constructor
super(island, player, admin, location);
this.deletedIslandInfo = deletedIsland;
}
public DeletedIslandDO getDeletedIslandInfo() {
return deletedIslandInfo;
}
}
/**
* Fired when an island is deleted.
*
*/
public static class IslandDeletedEvent extends IslandBaseEvent {
private IslandDeletedEvent(Island island, UUID player, boolean admin, Location location) {
private final DeletedIslandDO deletedIslandInfo;
private IslandDeletedEvent(Island island, UUID player, boolean admin, Location location, DeletedIslandDO deletedIsland) {
// Final variables have to be declared in the constructor
super(island, player, admin, location);
this.deletedIslandInfo = deletedIsland;
}
public DeletedIslandDO getDeletedIslandInfo() {
return deletedIslandInfo;
}
}
/**
@ -256,6 +287,7 @@ public class IslandEvent extends IslandBaseEvent {
private Reason reason = Reason.UNKNOWN;
private boolean admin;
private Location location;
private DeletedIslandDO deletedIslandInfo;
public IslandEventBuilder island(Island island) {
this.island = island;
@ -295,6 +327,11 @@ public class IslandEvent extends IslandBaseEvent {
return this;
}
public IslandEventBuilder deletedIslandInfo(DeletedIslandDO deletedIslandInfo) {
this.deletedIslandInfo = deletedIslandInfo;
return this;
}
public IslandBaseEvent build() {
// Call the generic event for developers who just want one event and use the Reason enum
Bukkit.getServer().getPluginManager().callEvent(new IslandEvent(island, player, admin, location, reason));
@ -316,8 +353,12 @@ public class IslandEvent extends IslandBaseEvent {
IslandDeleteEvent delete = new IslandDeleteEvent(island, player, admin, location);
Bukkit.getServer().getPluginManager().callEvent(delete);
return delete;
case DELETE_CHUNKS:
IslandDeleteChunksEvent deleteChunks = new IslandDeleteChunksEvent(island, player, admin, location, deletedIslandInfo);
Bukkit.getServer().getPluginManager().callEvent(deleteChunks);
return deleteChunks;
case DELETED:
IslandDeletedEvent deleted = new IslandDeletedEvent(island, player, admin, location);
IslandDeletedEvent deleted = new IslandDeletedEvent(island, player, admin, location, deletedIslandInfo);
Bukkit.getServer().getPluginManager().callEvent(deleted);
return deleted;
case ENTER:

View File

@ -110,4 +110,11 @@ public abstract class AbstractDatabaseHandler<T> {
* Closes the database
*/
public abstract void close();
/**
* Attempts to delete the object with the uniqueId
* @param uniqueId - uniqueId of object
* @return true if successful, false if there is no such uniqueId
*/
public abstract boolean deleteID(String uniqueId);
}

View File

@ -100,6 +100,15 @@ public class Database<T> {
return handler.objectExists(name);
}
/**
* Attempts to delete the object with the uniqueId
* @param uniqueId - uniqueId of object
* @return true if successful, false if there is no such uniqueId
*/
public boolean deleteID(String uniqueId) {
return handler.deleteID(uniqueId);
}
/**
* Delete object from database
* @param object - object to delete
@ -120,4 +129,6 @@ public class Database<T> {
handler.close();
}
}

View File

@ -122,6 +122,28 @@ public class JSONDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
}
}
@Override
public boolean deleteID(String uniqueId) {
// The filename of the JSON file is the value of uniqueId field plus .json. Sometimes the .json is already appended.
if (!uniqueId.endsWith(JSON)) {
uniqueId = uniqueId + JSON;
}
// Get the database and table folders
File dataFolder = new File(plugin.getDataFolder(), DATABASE_FOLDER_NAME);
File tableFolder = new File(dataFolder, dataObject.getSimpleName());
if (tableFolder.exists()) {
// Obtain the file and delete it
File file = new File(tableFolder, uniqueId);
try {
Files.delete(file.toPath());
return true;
} catch (IOException e) {
plugin.logError("Could not delete json database object! " + file.getName() + " - " + e.getMessage());
}
}
return false;
}
@Override
public void deleteObject(T instance) throws IllegalAccessException, InvocationTargetException, IntrospectionException {
// Null check
@ -137,25 +159,7 @@ public class JSONDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
// Obtain the value of uniqueId within the instance (which must be a DataObject)
PropertyDescriptor propertyDescriptor = new PropertyDescriptor("uniqueId", dataObject);
Method method = propertyDescriptor.getReadMethod();
String fileName = (String) method.invoke(instance);
// The filename of the JSON file is the value of uniqueId field plus .json. Sometimes the .json is already appended.
if (!fileName.endsWith(JSON)) {
fileName = fileName + JSON;
}
// Get the database and table folders
File dataFolder = new File(plugin.getDataFolder(), DATABASE_FOLDER_NAME);
File tableFolder = new File(dataFolder, dataObject.getSimpleName());
if (tableFolder.exists()) {
// Obtain the file and delete it
File file = new File(tableFolder, fileName);
try {
Files.delete(file.toPath());
} catch (IOException e) {
plugin.logError("Could not delete json database object! " + file.getName() + " - " + e.getMessage());
}
}
deleteID((String) method.invoke(instance));
}
@Override

View File

@ -106,6 +106,16 @@ public class MongoDBDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
}
}
@Override
public boolean deleteID(String uniqueId) {
try {
return collection.findOneAndDelete(new Document(MONGO_ID, uniqueId)) == null ? false : true;
} catch (Exception e) {
plugin.logError("Could not delete object " + dataObject.getCanonicalName() + " " + uniqueId + " " + e.getMessage());
return false;
}
}
@Override
public void deleteObject(T instance) {
// Null check
@ -117,11 +127,7 @@ public class MongoDBDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
plugin.logError("This class is not a DataObject: " + instance.getClass().getName());
return;
}
try {
collection.findOneAndDelete(new Document(MONGO_ID, ((DataObject)instance).getUniqueId()));
} catch (Exception e) {
plugin.logError("Could not delete object " + instance.getClass().getName() + " " + e.getMessage());
}
deleteID(((DataObject)instance).getUniqueId());
}
@Override

View File

@ -141,6 +141,22 @@ public class MySQLDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
}
}
@Override
public boolean deleteID(String uniqueId) {
String sb = "DELETE FROM `" +
dataObject.getCanonicalName() +
"` WHERE uniqueId = ?";
try (PreparedStatement preparedStatement = connection.prepareStatement(sb)) {
// UniqueId needs to be placed in quotes
preparedStatement.setString(1, "\"" + uniqueId + "\"");
preparedStatement.execute();
return preparedStatement.getUpdateCount() > 0;
} catch (Exception e) {
plugin.logError("Could not delete object " + dataObject.getCanonicalName() + " " + uniqueId + " " + e.getMessage());
return false;
}
}
@Override
public void deleteObject(T instance) {
// Null check
@ -152,15 +168,9 @@ public class MySQLDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
plugin.logError("This class is not a DataObject: " + instance.getClass().getName());
return;
}
String sb = "DELETE FROM `" +
dataObject.getCanonicalName() +
"` WHERE uniqueId = ?";
try (PreparedStatement preparedStatement = connection.prepareStatement(sb)) {
try {
Method getUniqueId = dataObject.getMethod("getUniqueId");
String uniqueId = (String) getUniqueId.invoke(instance);
// UniqueId needs to be placed in quotes
preparedStatement.setString(1, "\"" + uniqueId + "\"");
preparedStatement.execute();
deleteID((String) getUniqueId.invoke(instance));
} catch (Exception e) {
plugin.logError("Could not delete object " + instance.getClass().getName() + " " + e.getMessage());
}

View File

@ -1,12 +1,12 @@
package world.bentobox.bentobox.database.objects;
import java.util.UUID;
import org.bukkit.Location;
import org.bukkit.World;
import com.google.gson.annotations.Expose;
import world.bentobox.bentobox.util.Util;
/**
* Data object to store islands in deletion
*
@ -17,7 +17,7 @@ public class DeletedIslandDO implements DataObject {
private String uniqueId = "";
@Expose
private World world;
private Location location;
@Expose
private int minXChunk;
@ -33,115 +33,24 @@ public class DeletedIslandDO implements DataObject {
public DeletedIslandDO() {}
public DeletedIslandDO(Location location, int minXChunk, int maxXChunk, int minZChunk, int maxZChunk) {
this.uniqueId = Util.getStringLocation(location);
this.world = location.getWorld();
this.minXChunk = minXChunk;
this.maxXChunk = maxXChunk;
this.minZChunk = minZChunk;
this.maxZChunk = maxZChunk;
}
public DeletedIslandDO(Island island) {
uniqueId = Util.getStringLocation(island.getCenter());
world = island.getCenter().getWorld();
minXChunk = island.getMinX() >> 4;
maxXChunk = (island.getRange() * 2 + island.getMinX() - 1) >> 4;
minZChunk = island.getMinZ() >> 4;
maxZChunk = (island.getRange() * 2 + island.getMinZ() - 1) >> 4;
uniqueId = UUID.randomUUID().toString();
location = island.getCenter();
minXChunk = (location.getBlockX() - island.getMaxEverProtectionRange()) >> 4;
maxXChunk = (island.getMaxEverProtectionRange() + location.getBlockX() - 1) >> 4;
minZChunk = (location.getBlockZ() - island.getMaxEverProtectionRange()) >> 4;
maxZChunk = (island.getMaxEverProtectionRange() + location.getBlockZ() - 1) >> 4;
}
@Override
public String getUniqueId() {
return uniqueId;
}
@Override
public void setUniqueId(String uniqueId) {
this.uniqueId = uniqueId;
}
/**
* @return the world
*/
public World getWorld() {
return world;
}
/**
* @return the minXChunk
*/
public int getMinXChunk() {
return minXChunk;
}
/**
* @return the maxXChunk
*/
public int getMaxXChunk() {
return maxXChunk;
}
/**
* @return the minZChunk
*/
public int getMinZChunk() {
return minZChunk;
}
/**
* @return the maxZChunk
*/
public int getMaxZChunk() {
return maxZChunk;
}
/**
* @param world the world to set
*/
public void setWorld(World world) {
this.world = world;
}
/**
* @param minXChunk the minXChunk to set
*/
public void setMinXChunk(int minXChunk) {
public DeletedIslandDO(Location location, int minXChunk, int maxXChunk, int minZChunk, int maxZChunk) {
this.uniqueId = UUID.randomUUID().toString();
this.location = location;
this.minXChunk = minXChunk;
}
/**
* @param maxXChunk the maxXChunk to set
*/
public void setMaxXChunk(int maxXChunk) {
this.maxXChunk = maxXChunk;
}
/**
* @param minZChunk the minZChunk to set
*/
public void setMinZChunk(int minZChunk) {
this.minZChunk = minZChunk;
}
/**
* @param maxZChunk the maxZChunk to set
*/
public void setMaxZChunk(int maxZChunk) {
this.maxZChunk = maxZChunk;
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((uniqueId == null) ? 0 : uniqueId.hashCode());
return result;
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@ -167,6 +76,104 @@ public class DeletedIslandDO implements DataObject {
return true;
}
/**
* @return the location
*/
public Location getLocation() {
return location;
}
/**
* @return the maxXChunk
*/
public int getMaxXChunk() {
return maxXChunk;
}
/**
* @return the maxZChunk
*/
public int getMaxZChunk() {
return maxZChunk;
}
/**
* @return the minXChunk
*/
public int getMinXChunk() {
return minXChunk;
}
/**
* @return the minZChunk
*/
public int getMinZChunk() {
return minZChunk;
}
@Override
public String getUniqueId() {
return uniqueId;
}
/**
* @return the world
*/
public World getWorld() {
return location.getWorld();
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((uniqueId == null) ? 0 : uniqueId.hashCode());
return result;
}
/**
* @param location the location to set
*/
public void setLocation(Location location) {
this.location = location;
}
/**
* @param maxXChunk the maxXChunk to set
*/
public void setMaxXChunk(int maxXChunk) {
this.maxXChunk = maxXChunk;
}
/**
* @param maxZChunk the maxZChunk to set
*/
public void setMaxZChunk(int maxZChunk) {
this.maxZChunk = maxZChunk;
}
/**
* @param minXChunk the minXChunk to set
*/
public void setMinXChunk(int minXChunk) {
this.minXChunk = minXChunk;
}
/**
* @param minZChunk the minZChunk to set
*/
public void setMinZChunk(int minZChunk) {
this.minZChunk = minZChunk;
}
@Override
public void setUniqueId(String uniqueId) {
this.uniqueId = uniqueId;
}
}

View File

@ -24,11 +24,11 @@ import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.configuration.WorldSettings;
import world.bentobox.bentobox.api.flags.Flag;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.logs.LogEntry;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.adapters.Adapter;
import world.bentobox.bentobox.database.objects.adapters.FlagSerializer;
import world.bentobox.bentobox.database.objects.adapters.LogEntryListAdapter;
import world.bentobox.bentobox.api.logs.LogEntry;
import world.bentobox.bentobox.lists.Flags;
import world.bentobox.bentobox.managers.RanksManager;
import world.bentobox.bentobox.util.Pair;
@ -60,6 +60,10 @@ public class Island implements DataObject {
@Expose
private int protectionRange;
// Maximum ever protection range - used in island deletion
@Expose
private int maxEverProtectionRange;
// World the island started in. This may be different from the island location
@Expose
private World world;
@ -113,6 +117,7 @@ public class Island implements DataObject {
center = new Location(location.getWorld(), location.getX(), location.getY(), location.getZ());
range = BentoBox.getInstance().getIWM().getIslandDistance(world);
this.protectionRange = protectionRange;
this.maxEverProtectionRange = protectionRange;
}
/**
@ -284,6 +289,20 @@ public class Island implements DataObject {
return protectionRange;
}
/**
* @return the maxEverProtectionRange or the protection range, whichever is larger
*/
public int getMaxEverProtectionRange() {
return Math.max(protectionRange, maxEverProtectionRange);
}
/**
* @param maxEverProtectionRange the maxEverProtectionRange to set
*/
public void setMaxEverProtectionRange(int maxEverProtectionRange) {
this.maxEverProtectionRange = maxEverProtectionRange;
}
/**
* @return true if the island is protected from the Purge, otherwise false
*/
@ -533,6 +552,10 @@ public class Island implements DataObject {
*/
public void setProtectionRange(int protectionRange) {
this.protectionRange = protectionRange;
// Ratchet up the maximum protection range
if (protectionRange > this.maxEverProtectionRange) {
this.maxEverProtectionRange = protectionRange;
}
}
/**
@ -660,14 +683,14 @@ public class Island implements DataObject {
// Fixes #getLastPlayed() returning 0 when it is the owner's first connection.
long lastPlayed = (plugin.getServer().getOfflinePlayer(owner).getLastPlayed() != 0) ?
plugin.getServer().getOfflinePlayer(owner).getLastPlayed() : plugin.getServer().getOfflinePlayer(owner).getFirstPlayed();
user.sendMessage("commands.admin.info.last-login","[date]", new Date(lastPlayed).toString());
user.sendMessage("commands.admin.info.last-login","[date]", new Date(lastPlayed).toString());
user.sendMessage("commands.admin.info.deaths", "[number]", String.valueOf(plugin.getPlayers().getDeaths(world, owner)));
String resets = String.valueOf(plugin.getPlayers().getResets(world, owner));
String total = plugin.getIWM().getResetLimit(world) < 0 ? "Unlimited" : String.valueOf(plugin.getIWM().getResetLimit(world));
user.sendMessage("commands.admin.info.resets-left", "[number]", resets, "[total]", total);
// Show team members
showMembers(user);
user.sendMessage("commands.admin.info.deaths", "[number]", String.valueOf(plugin.getPlayers().getDeaths(world, owner)));
String resets = String.valueOf(plugin.getPlayers().getResets(world, owner));
String total = plugin.getIWM().getResetLimit(world) < 0 ? "Unlimited" : String.valueOf(plugin.getIWM().getResetLimit(world));
user.sendMessage("commands.admin.info.resets-left", "[number]", resets, "[total]", total);
// Show team members
showMembers(user);
}
Vector location = center.toVector();
user.sendMessage("commands.admin.info.island-location", "[xyz]", Util.xyz(location));
@ -675,6 +698,7 @@ public class Island implements DataObject {
Vector to = center.toVector().add(new Vector(range-1, 0, range-1)).setY(center.getWorld().getMaxHeight());
user.sendMessage("commands.admin.info.island-coords", "[xz1]", Util.xyz(from), "[xz2]", Util.xyz(to));
user.sendMessage("commands.admin.info.protection-range", "[range]", String.valueOf(protectionRange));
user.sendMessage("commands.admin.info.max-protection-range", "[range]", String.valueOf(maxEverProtectionRange));
Vector pfrom = center.toVector().subtract(new Vector(protectionRange, 0, protectionRange)).setY(0);
Vector pto = center.toVector().add(new Vector(protectionRange-1, 0, protectionRange-1)).setY(center.getWorld().getMaxHeight());
user.sendMessage("commands.admin.info.protection-coords", "[xz1]", Util.xyz(pfrom), "[xz2]", Util.xyz(pto));

View File

@ -553,6 +553,28 @@ public class YamlDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
return value;
}
@Override
public boolean deleteID(String uniqueId) {
// The filename of the YAML file is the value of uniqueId field plus .yml. Sometimes the .yml is already appended.
if (!uniqueId.endsWith(YML)) {
uniqueId = uniqueId + YML;
}
// Get the database and table folders
File dataFolder = new File(plugin.getDataFolder(), DATABASE_FOLDER_NAME);
File tableFolder = new File(dataFolder, dataObject.getSimpleName());
if (tableFolder.exists()) {
// Obtain the file and delete it
File file = new File(tableFolder, uniqueId);
try {
Files.delete(file.toPath());
return true;
} catch (IOException e) {
plugin.logError("Could not delete yml database object! " + file.getName() + " - " + e.getMessage());
}
}
return false;
}
/* (non-Javadoc)
* @see world.bentobox.bentobox.database.AbstractDatabaseHandler#deleteObject(java.lang.Object)
*/
@ -571,23 +593,8 @@ public class YamlDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
// Obtain the value of uniqueId within the instance (which must be a DataObject)
PropertyDescriptor propertyDescriptor = new PropertyDescriptor("uniqueId", dataObject);
Method method = propertyDescriptor.getReadMethod();
String fileName = (String) method.invoke(instance);
// The filename of the YAML file is the value of uniqueId field plus .yml. Sometimes the .yml is already appended.
if (!fileName.endsWith(YML)) {
fileName = fileName + YML;
}
// Get the database and table folders
File dataFolder = new File(plugin.getDataFolder(), DATABASE_FOLDER_NAME);
File tableFolder = new File(dataFolder, dataObject.getSimpleName());
if (tableFolder.exists()) {
// Obtain the file and delete it
File file = new File(tableFolder, fileName);
try {
Files.delete(file.toPath());
} catch (IOException e) {
plugin.logError("Could not delete yml database object! " + file.getName() + " - " + e.getMessage());
}
}
deleteID((String) method.invoke(instance));
}
@Override

View File

@ -0,0 +1,84 @@
package world.bentobox.bentobox.managers;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.bukkit.Location;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.events.BentoBoxReadyEvent;
import world.bentobox.bentobox.api.events.island.IslandEvent.IslandDeleteChunksEvent;
import world.bentobox.bentobox.api.events.island.IslandEvent.IslandDeletedEvent;
import world.bentobox.bentobox.database.Database;
import world.bentobox.bentobox.database.objects.DeletedIslandDO;
import world.bentobox.bentobox.util.DeleteIslandChunks;
import world.bentobox.bentobox.util.Util;
/**
* Listens for island deletions and adds them to the database. Removes them when the island is deleted.
* @author tastybento
*
*/
public class IslandDeleteManager implements Listener {
/**
* Queue of islands to delete
*/
private BentoBox plugin;
private Database<DeletedIslandDO> handler;
private Set<Location> inDeletion;
public IslandDeleteManager(BentoBox plugin) {
this.plugin = plugin;
handler = new Database<>(plugin, DeletedIslandDO.class);
inDeletion = new HashSet<>();
}
/**
* When BentoBox is fully loaded, load the islands that still need to be deleted and kick them off
* @param e
*/
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onBentoBoxReady(BentoBoxReadyEvent e) {
// Load list of islands that were mid deletion and delete them
List<DeletedIslandDO> toBeDeleted = handler.loadObjects();
if (toBeDeleted != null && toBeDeleted.size() > 0) {
plugin.log("There are " + toBeDeleted.size() + " islands pending deletion.");
toBeDeleted.forEach(di -> {
plugin.log("Resuming deletion of island at " + di.getLocation().getWorld().getName() + " " + Util.xyz(di.getLocation().toVector()));
inDeletion.add(di.getLocation());
new DeleteIslandChunks(plugin, di);
});
}
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onIslandDelete(IslandDeleteChunksEvent e) {
// Store location
inDeletion.add(e.getDeletedIslandInfo().getLocation());
// Save to database
handler.saveObject(e.getDeletedIslandInfo());
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onIslandDeleted(IslandDeletedEvent e) {
// Delete
inDeletion.remove(e.getDeletedIslandInfo().getLocation());
// Delete from database
handler.deleteID(e.getDeletedIslandInfo().getUniqueId());
}
/**
* Check if an island location is in deletion
* @param location - center of location
* @return true if island is in the process of being deleted
*/
public boolean inDeletion(Location location) {
return inDeletion.contains(location);
}
}

View File

@ -64,9 +64,6 @@ public class IslandsManager {
// Island Cache
private IslandCache islandCache;
// Async database saving semaphore
private boolean midSave;
/**
* Islands Manager
* @param plugin - plugin
@ -694,8 +691,7 @@ public class IslandsManager {
* @param user - user
*/
public void removePlayer(World world, User user) {
islandCache.removePlayer(world, user.getUniqueId());
save(true);
removePlayer(world, user.getUniqueId());
}
/**
@ -704,8 +700,10 @@ public class IslandsManager {
* @param uuid - user's uuid
*/
public void removePlayer(World world, UUID uuid) {
islandCache.removePlayer(world, uuid);
save(true);
Island island = islandCache.removePlayer(world, uuid);
if (island != null) {
handler.saveObject(island);
}
}
/**
@ -736,36 +734,18 @@ public class IslandsManager {
}
/**
* Save the islands to the database
* @param async - if true, saving will be done async
* Save the all the islands to the database
*/
public void save(boolean async){
if (midSave) {
// If it's already saving, then do nothing
return;
}
public void saveAll(){
Collection<Island> collection = islandCache.getIslands();
if(async) {
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
midSave = true;
for(Island island : collection){
try {
handler.saveObject(island);
} catch (Exception e) {
plugin.logError("Could not save island to database when running async! " + e.getMessage());
}
}
midSave = false;
});
} else {
for(Island island : collection){
try {
handler.saveObject(island);
} catch (Exception e) {
plugin.logError("Could not save island to database when running sync! " + e.getMessage());
}
for(Island island : collection){
try {
handler.saveObject(island);
} catch (Exception e) {
plugin.logError("Could not save island to database when running sync! " + e.getMessage());
}
}
}
/**
@ -777,8 +757,8 @@ public class IslandsManager {
// Add player to new island
teamIsland.addMember(playerUUID);
islandCache.addPlayer(playerUUID, teamIsland);
// Save the database
save(false);
// Save the island
handler.saveObject(teamIsland);
}
public void setLast(Location last) {
@ -798,7 +778,7 @@ public class IslandsManager {
public void shutdown(){
// Remove all coop associations
islandCache.getIslands().stream().forEach(i -> i.getMembers().values().removeIf(p -> p == RanksManager.COOP_RANK));
save(false);
saveAll();
islandCache.clear();
handler.close();
}
@ -876,4 +856,13 @@ public class IslandsManager {
islandCache.getIslands().stream().forEach(i -> i.getMembers().entrySet().removeIf(e -> e.getKey().equals(uniqueId) && e.getValue() == rank));
}
/**
* Save the island to the database
* @param island - island
*/
public void save(Island island) {
handler.saveObject(island);
}
}

View File

@ -1,6 +1,5 @@
package world.bentobox.bentobox.managers;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@ -8,7 +7,6 @@ import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
@ -63,20 +61,13 @@ public class PlayersManager {
/**
* Save all players
* @param async - if true, save async
*/
public void save(boolean async){
Collection<Players> set = Collections.unmodifiableCollection(playerCache.values());
if(async) {
Runnable save = () -> set.forEach(handler::saveObject);
Bukkit.getScheduler().runTaskAsynchronously(plugin, save);
} else {
set.forEach(handler::saveObject);
}
public void saveAll(){
Collections.unmodifiableCollection(playerCache.values()).forEach(handler::saveObject);
}
public void shutdown(){
save(false);
saveAll();
playerCache.clear();
handler.close();
}

View File

@ -169,8 +169,9 @@ public class IslandCache {
* The island is removed from the islandsByUUID map, but kept in the location map.
* @param world - world
* @param uuid - player's UUID
* @return island player had or null if none
*/
public void removePlayer(World world, UUID uuid) {
public Island removePlayer(World world, UUID uuid) {
world = Util.getWorld(world);
islandsByUUID.putIfAbsent(world, new HashMap<>());
Island island = islandsByUUID.get(world).get(uuid);
@ -185,6 +186,7 @@ public class IslandCache {
}
}
islandsByUUID.get(world).remove(uuid);
return island;
}
/**

View File

@ -244,7 +244,7 @@ public class NewIsland {
*/
private Result isIsland(Location location){
location = Util.getClosestIsland(location);
if (plugin.getIslands().getIslandAt(location).isPresent()) {
if (plugin.getIslands().getIslandAt(location).isPresent() || plugin.getIslandDeletionManager().inDeletion(location)) {
return Result.ISLAND_FOUND;
}

View File

@ -16,34 +16,43 @@ import world.bentobox.bentobox.database.objects.DeletedIslandDO;
*/
public class DeleteIslandChunks {
/**
* This is how many chunks per world will be done in one tick.
*/
private final static int SPEED = 5;
private int x;
private int z;
private BukkitTask task;
@SuppressWarnings("deprecation")
public DeleteIslandChunks(BentoBox plugin, DeletedIslandDO di) {
// Fire event
IslandEvent.builder().deletedIslandInfo(di).reason(Reason.DELETE_CHUNKS).build();
x = di.getMinXChunk();
z = di.getMinZChunk();
task = Bukkit.getScheduler().runTaskTimer(plugin, () -> {
di.getWorld().regenerateChunk(x, z);
//System.out.println("regenerating = " + x + "," + z);
if (plugin.getIWM().isNetherGenerate(di.getWorld()) && plugin.getIWM().isNetherIslands(di.getWorld())) {
plugin.getIWM().getNetherWorld(di.getWorld()).regenerateChunk(x, z);
for (int i = 0; i < SPEED; i++) {
di.getWorld().regenerateChunk(x, z);
if (plugin.getIWM().isNetherGenerate(di.getWorld()) && plugin.getIWM().isNetherIslands(di.getWorld())) {
plugin.getIWM().getNetherWorld(di.getWorld()).regenerateChunk(x, z);
}
if (plugin.getIWM().isEndGenerate(di.getWorld()) && plugin.getIWM().isEndIslands(di.getWorld())) {
plugin.getIWM().getEndWorld(di.getWorld()).regenerateChunk(x, z);
}
z++;
if (z > di.getMaxZChunk()) {
z = di.getMinZChunk();
x++;
if (x > di.getMaxXChunk()) {
task.cancel();
// Fire event
IslandEvent.builder().location(Util.getLocationString(di.getUniqueId())).reason(Reason.DELETED).build();
}
if (plugin.getIWM().isEndGenerate(di.getWorld()) && plugin.getIWM().isEndIslands(di.getWorld())) {
plugin.getIWM().getEndWorld(di.getWorld()).regenerateChunk(x, z);
}
z++;
if (z > di.getMaxZChunk()) {
z = di.getMinZChunk();
x++;
if (x > di.getMaxXChunk()) {
task.cancel();
// Fire event
IslandEvent.builder().deletedIslandInfo(di).reason(Reason.DELETED).build();
}
}
}
}, 0L, 1L);
}}
}
}

View File

@ -109,6 +109,7 @@ commands:
registered-island: "&aRegistered player to island at [xyz]."
already-owned: "&cIsland is already owned by another player!"
no-island-here: "&cThere is no island here. Confirm to make one."
in-deletion: "&cThis island space is currently being deleted. Try later."
unregister:
parameters: "<owner>"
description: "unregister owner from island, but keep island blocks"
@ -128,6 +129,7 @@ commands:
island-location: "Island location: [xyz]"
island-coords: "Island coordinates: [xz1] to [xz2]"
protection-range: "Protection range: [range]"
max-protection-range: "Largest historical protection range: [range]"
protection-coords: "Protection coordinates: [xz1] to [xz2]"
is-spawn: "Island is a spawn island"
banned-players: "Banned players:"

View File

@ -30,6 +30,7 @@ import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.managers.CommandsManager;
import world.bentobox.bentobox.managers.IslandDeleteManager;
import world.bentobox.bentobox.managers.IslandWorldManager;
import world.bentobox.bentobox.managers.IslandsManager;
import world.bentobox.bentobox.managers.LocalesManager;
@ -50,6 +51,7 @@ public class AdminRegisterCommandTest {
private IslandsManager im;
private PlayersManager pm;
private UUID notUUID;
private IslandDeleteManager idm;
/**
* @throws java.lang.Exception
@ -111,11 +113,16 @@ public class AdminRegisterCommandTest {
LocalesManager lm = mock(LocalesManager.class);
when(lm.get(Mockito.any(), Mockito.any())).thenReturn("mock translation");
when(plugin.getLocalesManager()).thenReturn(lm);
// Deletion Manager
idm = mock(IslandDeleteManager.class);
when(idm.inDeletion(Mockito.any())).thenReturn(false);
when(plugin.getIslandDeletionManager()).thenReturn(idm);
}
/**
* Test method for .
* Test method for {@link AdminRegisterCommand#execute(org.bukkit.command.CommandSender, String, String[])}.
*/
@Test
public void testExecuteNoTarget() {
@ -125,7 +132,7 @@ public class AdminRegisterCommandTest {
}
/**
* Test method for .
* Test method for {@link AdminRegisterCommand#execute(org.bukkit.command.CommandSender, String, String[])}.
*/
@Test
public void testExecuteUnknownPlayer() {
@ -137,7 +144,7 @@ public class AdminRegisterCommandTest {
}
/**
* Test method for .
* Test method for {@link AdminRegisterCommand#execute(org.bukkit.command.CommandSender, String, String[])}.
*/
@Test
public void testExecutePlayerHasIsland() {
@ -151,7 +158,7 @@ public class AdminRegisterCommandTest {
}
/**
* Test method for .
* Test method for {@link AdminRegisterCommand#execute(org.bukkit.command.CommandSender, String, String[])}.
*/
@Test
public void testExecuteInTeam() {
@ -165,7 +172,7 @@ public class AdminRegisterCommandTest {
}
/**
* Test method for .
* Test method for {@link AdminRegisterCommand#execute(org.bukkit.command.CommandSender, String, String[])}.
*/
@Test
public void testExecuteAlreadyOwnedIsland() {
@ -186,6 +193,32 @@ public class AdminRegisterCommandTest {
Mockito.verify(user).sendMessage("commands.admin.register.already-owned");
}
/**
* Test method for {@link AdminRegisterCommand#execute(org.bukkit.command.CommandSender, String, String[])}.
*/
@Test
public void testExecuteInDeletionIsland() {
when(idm.inDeletion(Mockito.any())).thenReturn(true);
when(im.inTeam(Mockito.any(), Mockito.any())).thenReturn(false);
when(im.hasIsland(Mockito.any(), Mockito.any(UUID.class))).thenReturn(false);
String[] name = {"tastybento"};
when(pm.getUUID(Mockito.any())).thenReturn(notUUID);
Location loc = mock(Location.class);
// Island has owner
Island is = mock(Island.class);
when(is.getOwner()).thenReturn(uuid);
Optional<Island> opi = Optional.of(is);
when(im.getIslandAt(Mockito.any())).thenReturn(opi);
when(user.getLocation()).thenReturn(loc);
AdminRegisterCommand itl = new AdminRegisterCommand(ac);
assertFalse(itl.execute(user, itl.getLabel(), Arrays.asList(name)));
Mockito.verify(user).sendMessage("commands.admin.register.in-deletion");
}
/**
* Test method for {@link AdminRegisterCommand#execute(org.bukkit.command.CommandSender, String, String[])}.
*/
@Test
public void testExecuteSuccess() {
when(im.inTeam(Mockito.any(), Mockito.any())).thenReturn(false);

View File

@ -23,6 +23,7 @@ import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
@ -37,6 +38,7 @@ import org.bukkit.entity.Player;
import org.bukkit.entity.Slime;
import org.bukkit.entity.Wither;
import org.bukkit.entity.Zombie;
import org.bukkit.plugin.PluginManager;
import org.bukkit.scheduler.BukkitScheduler;
import org.junit.Before;
import org.junit.Test;
@ -54,6 +56,7 @@ import com.google.common.collect.ImmutableSet.Builder;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.Settings;
import world.bentobox.bentobox.api.configuration.WorldSettings;
import world.bentobox.bentobox.api.events.island.IslandEvent.IslandDeleteEvent;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.lists.Flags;
@ -79,6 +82,7 @@ public class IslandsManagerTest {
private IslandCache islandCache;
private Optional<Island> optionalIsland;
private Island is;
private PluginManager pim;
/**
* @throws java.lang.Exception
@ -122,8 +126,7 @@ public class IslandsManagerTest {
pm = mock(PlayersManager.class);
when(plugin.getPlayers()).thenReturn(pm);
// Server & Scheduler
// Scheduler
BukkitScheduler sch = mock(BukkitScheduler.class);
PowerMockito.mockStatic(Bukkit.class);
when(Bukkit.getScheduler()).thenReturn(sch);
@ -174,6 +177,11 @@ public class IslandsManagerTest {
// User location
when(user.getLocation()).thenReturn(location);
// Server for events
Server server = mock(Server.class);
when(Bukkit.getServer()).thenReturn(server);
pim = mock(PluginManager.class);
when(server.getPluginManager()).thenReturn(pim);
}
@ -432,16 +440,37 @@ public class IslandsManagerTest {
* Test method for {@link world.bentobox.bentobox.managers.IslandsManager#deleteIsland(world.bentobox.bentobox.database.objects.Island, boolean)}.
*/
@Test
public void testDeleteIslandIslandBoolean() {
public void testDeleteIslandIslandBooleanNull() {
IslandsManager im = new IslandsManager(plugin);
im.deleteIsland((Island)null, true);
Mockito.verify(pim, Mockito.never()).callEvent(Mockito.any());
}
/**
* Test method for {@link world.bentobox.bentobox.managers.IslandsManager#deleteIsland(world.bentobox.bentobox.database.objects.Island, boolean)}.
*/
@Test
public void testDeleteIslandIslandBooleanNoBlockRemoval() {
IslandsManager im = new IslandsManager(plugin);
UUID owner = UUID.randomUUID();
Island island = im.createIsland(location, owner);
im.deleteIsland(island, false);
island = im.createIsland(location, owner);
assertNull(island.getOwner());
Mockito.verify(pim, Mockito.times(2)).callEvent(Mockito.any(IslandDeleteEvent.class));
}
/**
* Test method for {@link world.bentobox.bentobox.managers.IslandsManager#deleteIsland(world.bentobox.bentobox.database.objects.Island, boolean)}.
*/
@Test
public void testDeleteIslandIslandBooleanRemoveBlocks() {
Mockito.verify(pim, Mockito.never()).callEvent(Mockito.any());
IslandsManager im = new IslandsManager(plugin);
UUID owner = UUID.randomUUID();
Island island = im.createIsland(location, owner);
im.deleteIsland(island, true);
assertNull(island);
assertNull(island.getOwner());
Mockito.verify(pim, Mockito.times(4)).callEvent(Mockito.any(IslandDeleteEvent.class));
}
/**

View File

@ -175,13 +175,12 @@ public class PlayersManagerTest {
}
/**
* Test method for {@link world.bentobox.bentobox.managers.PlayersManager#save(boolean)}.
* Test method for {@link world.bentobox.bentobox.managers.PlayersManager#saveAll()}.
*/
@Test
public void testSaveBoolean() {
PlayersManager pm = new PlayersManager(plugin);
pm.save(false);
pm.save(true);
pm.saveAll();
}
/**

View File

@ -1,152 +0,0 @@
/**
*
*/
package world.bentobox.bentobox.util;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.plugin.PluginManager;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.managers.IslandWorldManager;
/**
* Tests the island delete class
* @author tastybento
*
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest({Bukkit.class})
public class DeleteIslandChunksTest {
private BentoBox plugin;
private Island island;
private Location location;
private World world;
/**
* @throws java.lang.Exception
*/
@Before
public void setUp() throws Exception {
PowerMockito.mockStatic(Bukkit.class);
Server server = mock(Server.class);
PluginManager pim = mock(PluginManager.class);
when(server.getPluginManager()).thenReturn(pim);
when(Bukkit.getServer()).thenReturn(server);
plugin = mock(BentoBox.class);
IslandWorldManager iwm = mock(IslandWorldManager.class);
// No Nether or End by default
when(iwm.isNetherGenerate(Mockito.any())).thenReturn(false);
when(iwm.isNetherIslands(Mockito.any())).thenReturn(false);
when(iwm.isEndGenerate(Mockito.any())).thenReturn(false);
when(iwm.isEndIslands(Mockito.any())).thenReturn(false);
when(plugin.getIWM()).thenReturn(iwm);
// World
//world = mock(World.class, Mockito.withSettings().verboseLogging());
world = mock(World.class);
island = new Island();
island.setRange(64);
location = mock(Location.class);
when(location.getWorld()).thenReturn(world);
}
/**
* Test method for {@link world.bentobox.bentobox.util.DeleteIslandChunks#DeleteIslandChunks(world.bentobox.bentobox.BentoBox, world.bentobox.bentobox.database.objects.Island)}.
*/
@SuppressWarnings("deprecation")
@Test
public void testDeleteIslandChunksNegativeX() {
// Island adjacent to an island at 0,0
Location location2 = mock(Location.class);
when(location2.getWorld()).thenReturn(world);
when(location2.getBlockX()).thenReturn(-128);
when(location2.getBlockY()).thenReturn(120);
when(location2.getBlockZ()).thenReturn(0);
island.setCenter(location2);
new DeleteIslandChunks(plugin, island);
Mockito.verify(world, Mockito.times(64)).regenerateChunk(Mockito.anyInt(), Mockito.anyInt());
}
/**
* Test method for {@link world.bentobox.bentobox.util.DeleteIslandChunks#DeleteIslandChunks(world.bentobox.bentobox.BentoBox, world.bentobox.bentobox.database.objects.Island)}.
*/
@SuppressWarnings("deprecation")
@Test
public void testDeleteIslandChunksNegativeXX() {
// Island adjacent to an island at 0,0
Location location2 = mock(Location.class);
when(location2.getWorld()).thenReturn(world);
when(location2.getBlockX()).thenReturn(-256);
when(location2.getBlockY()).thenReturn(120);
when(location2.getBlockZ()).thenReturn(0);
island.setCenter(location2);
new DeleteIslandChunks(plugin, island);
Mockito.verify(world, Mockito.times(64)).regenerateChunk(Mockito.anyInt(), Mockito.anyInt());
}
/**
* Test method for {@link world.bentobox.bentobox.util.DeleteIslandChunks#DeleteIslandChunks(world.bentobox.bentobox.BentoBox, world.bentobox.bentobox.database.objects.Island)}.
*/
@SuppressWarnings("deprecation")
@Test
public void testDeleteIslandChunksIslandPositiveX() {
// Island adjacent to an island at 0,0
Location location2 = mock(Location.class);
when(location2.getWorld()).thenReturn(world);
when(location2.getBlockX()).thenReturn(0);
when(location2.getBlockY()).thenReturn(120);
when(location2.getBlockZ()).thenReturn(0);
island.setCenter(location2);
new DeleteIslandChunks(plugin, island);
Mockito.verify(world, Mockito.times(64)).regenerateChunk(Mockito.anyInt(), Mockito.anyInt());
}
/**
* Test method for {@link world.bentobox.bentobox.util.DeleteIslandChunks#DeleteIslandChunks(world.bentobox.bentobox.BentoBox, world.bentobox.bentobox.database.objects.Island)}.
*/
@SuppressWarnings("deprecation")
@Test
public void testDeleteIslandChunksPositiveXX() {
// Island adjacent to an island at 0,0
Location location2 = mock(Location.class);
when(location2.getWorld()).thenReturn(world);
when(location2.getBlockX()).thenReturn(256);
when(location2.getBlockY()).thenReturn(120);
when(location2.getBlockZ()).thenReturn(0);
island.setCenter(location2);
new DeleteIslandChunks(plugin, island);
Mockito.verify(world, Mockito.times(64)).regenerateChunk(Mockito.anyInt(), Mockito.anyInt());
}
}