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

View File

@ -51,6 +51,12 @@ public class AdminRegisterCommand extends ConfirmableCommand {
return false; 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 // Check if island is owned
Optional<Island> island = getIslands().getIslandAt(user.getLocation()); Optional<Island> island = getIslands().getIslandAt(user.getLocation());
if (island.map(i -> i.getOwner() != null).orElse(false)) { 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"); user.sendMessage("commands.admin.register.no-island-here");
this.askConfirmation(user, () -> { this.askConfirmation(user, () -> {
// Make island here // Make island here
Island i = getIslands().createIsland(getClosestIsland(user.getLocation()), targetUUID); Island i = getIslands().createIsland(closestIsland, targetUUID);
getIslands().setOwner(user, targetUUID, i); getIslands().setOwner(user, targetUUID, i);
getWorld().getBlockAt(i.getCenter()).setType(Material.BEDROCK); getWorld().getBlockAt(i.getCenter()).setType(Material.BEDROCK);
user.sendMessage("commands.admin.register.registered-island", "[xyz]", Util.xyz(i.getCenter().toVector())); 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; package world.bentobox.bentobox.api.commands.admin.range;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import world.bentobox.bentobox.api.commands.CompositeCommand; import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.localization.TextVariables; import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island; import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.util.Util;
public class AdminRangeResetCommand extends CompositeCommand { public class AdminRangeResetCommand extends CompositeCommand {
@ -50,4 +53,15 @@ public class AdminRangeResetCommand extends CompositeCommand {
return true; 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; package world.bentobox.bentobox.api.commands.admin.range;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import org.apache.commons.lang.StringUtils; 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.localization.TextVariables;
import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island; import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.util.Util;
public class AdminRangeSetCommand extends CompositeCommand { public class AdminRangeSetCommand extends CompositeCommand {
@ -72,4 +75,15 @@ public class AdminRangeSetCommand extends CompositeCommand {
return true; 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) { if (inviter != null) {
inviter.sendMessage("commands.island.team.invite.accept.name-joined-your-island", TextVariables.NAME, user.getName()); inviter.sendMessage("commands.island.team.invite.accept.name-joined-your-island", TextVariables.NAME, user.getName());
} }
getIslands().save(false); getIslands().save(island);
return true; 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.events.team.TeamEvent;
import world.bentobox.bentobox.api.localization.TextVariables; import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.util.Util; import world.bentobox.bentobox.util.Util;
public class IslandTeamSetownerCommand extends CompositeCommand { public class IslandTeamSetownerCommand extends CompositeCommand {
@ -59,9 +60,9 @@ public class IslandTeamSetownerCommand extends CompositeCommand {
return false; return false;
} }
// Fire event so add-ons can run commands, etc. // Fire event so add-ons can run commands, etc.
Island island = getIslands().getIsland(getWorld(), playerUUID);
IslandBaseEvent event = TeamEvent.builder() IslandBaseEvent event = TeamEvent.builder()
.island(getIslands() .island(island)
.getIsland(getWorld(), playerUUID))
.reason(TeamEvent.Reason.SETOWNER) .reason(TeamEvent.Reason.SETOWNER)
.involvedPlayer(targetUUID) .involvedPlayer(targetUUID)
.build(); .build();
@ -70,7 +71,7 @@ public class IslandTeamSetownerCommand extends CompositeCommand {
return false; return false;
} }
getIslands().setOwner(getWorld(), user, targetUUID); getIslands().setOwner(getWorld(), user, targetUUID);
getIslands().save(true); getIslands().save(island);
return true; 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(){ public Island getIsland(){
return island; return island;

View File

@ -6,6 +6,7 @@ import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import world.bentobox.bentobox.api.events.IslandBaseEvent; 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.database.objects.Island;
import world.bentobox.bentobox.lists.Flags; import world.bentobox.bentobox.lists.Flags;
@ -58,9 +59,14 @@ public class IslandEvent extends IslandBaseEvent {
*/ */
CREATED, 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, 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 * 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); 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. * Fired when an island is deleted.
* *
*/ */
public static class IslandDeletedEvent extends IslandBaseEvent { 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 // Final variables have to be declared in the constructor
super(island, player, admin, location); 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 Reason reason = Reason.UNKNOWN;
private boolean admin; private boolean admin;
private Location location; private Location location;
private DeletedIslandDO deletedIslandInfo;
public IslandEventBuilder island(Island island) { public IslandEventBuilder island(Island island) {
this.island = island; this.island = island;
@ -295,6 +327,11 @@ public class IslandEvent extends IslandBaseEvent {
return this; return this;
} }
public IslandEventBuilder deletedIslandInfo(DeletedIslandDO deletedIslandInfo) {
this.deletedIslandInfo = deletedIslandInfo;
return this;
}
public IslandBaseEvent build() { public IslandBaseEvent build() {
// Call the generic event for developers who just want one event and use the Reason enum // 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)); 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); IslandDeleteEvent delete = new IslandDeleteEvent(island, player, admin, location);
Bukkit.getServer().getPluginManager().callEvent(delete); Bukkit.getServer().getPluginManager().callEvent(delete);
return delete; return delete;
case DELETE_CHUNKS:
IslandDeleteChunksEvent deleteChunks = new IslandDeleteChunksEvent(island, player, admin, location, deletedIslandInfo);
Bukkit.getServer().getPluginManager().callEvent(deleteChunks);
return deleteChunks;
case DELETED: case DELETED:
IslandDeletedEvent deleted = new IslandDeletedEvent(island, player, admin, location); IslandDeletedEvent deleted = new IslandDeletedEvent(island, player, admin, location, deletedIslandInfo);
Bukkit.getServer().getPluginManager().callEvent(deleted); Bukkit.getServer().getPluginManager().callEvent(deleted);
return deleted; return deleted;
case ENTER: case ENTER:

View File

@ -110,4 +110,11 @@ public abstract class AbstractDatabaseHandler<T> {
* Closes the database * Closes the database
*/ */
public abstract void close(); 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); 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 * Delete object from database
* @param object - object to delete * @param object - object to delete
@ -120,4 +129,6 @@ public class Database<T> {
handler.close(); 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 @Override
public void deleteObject(T instance) throws IllegalAccessException, InvocationTargetException, IntrospectionException { public void deleteObject(T instance) throws IllegalAccessException, InvocationTargetException, IntrospectionException {
// Null check // 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) // Obtain the value of uniqueId within the instance (which must be a DataObject)
PropertyDescriptor propertyDescriptor = new PropertyDescriptor("uniqueId", dataObject); PropertyDescriptor propertyDescriptor = new PropertyDescriptor("uniqueId", dataObject);
Method method = propertyDescriptor.getReadMethod(); Method method = propertyDescriptor.getReadMethod();
String fileName = (String) method.invoke(instance); deleteID((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());
}
}
} }
@Override @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 @Override
public void deleteObject(T instance) { public void deleteObject(T instance) {
// Null check // Null check
@ -117,11 +127,7 @@ public class MongoDBDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
plugin.logError("This class is not a DataObject: " + instance.getClass().getName()); plugin.logError("This class is not a DataObject: " + instance.getClass().getName());
return; return;
} }
try { deleteID(((DataObject)instance).getUniqueId());
collection.findOneAndDelete(new Document(MONGO_ID, ((DataObject)instance).getUniqueId()));
} catch (Exception e) {
plugin.logError("Could not delete object " + instance.getClass().getName() + " " + e.getMessage());
}
} }
@Override @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 @Override
public void deleteObject(T instance) { public void deleteObject(T instance) {
// Null check // Null check
@ -152,15 +168,9 @@ public class MySQLDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
plugin.logError("This class is not a DataObject: " + instance.getClass().getName()); plugin.logError("This class is not a DataObject: " + instance.getClass().getName());
return; return;
} }
String sb = "DELETE FROM `" + try {
dataObject.getCanonicalName() +
"` WHERE uniqueId = ?";
try (PreparedStatement preparedStatement = connection.prepareStatement(sb)) {
Method getUniqueId = dataObject.getMethod("getUniqueId"); Method getUniqueId = dataObject.getMethod("getUniqueId");
String uniqueId = (String) getUniqueId.invoke(instance); deleteID((String) getUniqueId.invoke(instance));
// UniqueId needs to be placed in quotes
preparedStatement.setString(1, "\"" + uniqueId + "\"");
preparedStatement.execute();
} catch (Exception e) { } catch (Exception e) {
plugin.logError("Could not delete object " + instance.getClass().getName() + " " + e.getMessage()); plugin.logError("Could not delete object " + instance.getClass().getName() + " " + e.getMessage());
} }

View File

@ -1,12 +1,12 @@
package world.bentobox.bentobox.database.objects; package world.bentobox.bentobox.database.objects;
import java.util.UUID;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.World; import org.bukkit.World;
import com.google.gson.annotations.Expose; import com.google.gson.annotations.Expose;
import world.bentobox.bentobox.util.Util;
/** /**
* Data object to store islands in deletion * Data object to store islands in deletion
* *
@ -17,7 +17,7 @@ public class DeletedIslandDO implements DataObject {
private String uniqueId = ""; private String uniqueId = "";
@Expose @Expose
private World world; private Location location;
@Expose @Expose
private int minXChunk; private int minXChunk;
@ -33,115 +33,24 @@ public class DeletedIslandDO implements DataObject {
public DeletedIslandDO() {} 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) { public DeletedIslandDO(Island island) {
uniqueId = Util.getStringLocation(island.getCenter()); uniqueId = UUID.randomUUID().toString();
world = island.getCenter().getWorld(); location = island.getCenter();
minXChunk = island.getMinX() >> 4; minXChunk = (location.getBlockX() - island.getMaxEverProtectionRange()) >> 4;
maxXChunk = (island.getRange() * 2 + island.getMinX() - 1) >> 4; maxXChunk = (island.getMaxEverProtectionRange() + location.getBlockX() - 1) >> 4;
minZChunk = island.getMinZ() >> 4; minZChunk = (location.getBlockZ() - island.getMaxEverProtectionRange()) >> 4;
maxZChunk = (island.getRange() * 2 + island.getMinZ() - 1) >> 4; maxZChunk = (island.getMaxEverProtectionRange() + location.getBlockZ() - 1) >> 4;
} }
@Override public DeletedIslandDO(Location location, int minXChunk, int maxXChunk, int minZChunk, int maxZChunk) {
public String getUniqueId() { this.uniqueId = UUID.randomUUID().toString();
return uniqueId; this.location = location;
}
@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) {
this.minXChunk = minXChunk; this.minXChunk = minXChunk;
}
/**
* @param maxXChunk the maxXChunk to set
*/
public void setMaxXChunk(int maxXChunk) {
this.maxXChunk = maxXChunk; this.maxXChunk = maxXChunk;
}
/**
* @param minZChunk the minZChunk to set
*/
public void setMinZChunk(int minZChunk) {
this.minZChunk = minZChunk; this.minZChunk = minZChunk;
}
/**
* @param maxZChunk the maxZChunk to set
*/
public void setMaxZChunk(int maxZChunk) {
this.maxZChunk = 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) /* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object) * @see java.lang.Object#equals(java.lang.Object)
*/ */
@ -167,6 +76,104 @@ public class DeletedIslandDO implements DataObject {
return true; 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.configuration.WorldSettings;
import world.bentobox.bentobox.api.flags.Flag; import world.bentobox.bentobox.api.flags.Flag;
import world.bentobox.bentobox.api.localization.TextVariables; 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.api.user.User;
import world.bentobox.bentobox.database.objects.adapters.Adapter; import world.bentobox.bentobox.database.objects.adapters.Adapter;
import world.bentobox.bentobox.database.objects.adapters.FlagSerializer; import world.bentobox.bentobox.database.objects.adapters.FlagSerializer;
import world.bentobox.bentobox.database.objects.adapters.LogEntryListAdapter; 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.lists.Flags;
import world.bentobox.bentobox.managers.RanksManager; import world.bentobox.bentobox.managers.RanksManager;
import world.bentobox.bentobox.util.Pair; import world.bentobox.bentobox.util.Pair;
@ -60,6 +60,10 @@ public class Island implements DataObject {
@Expose @Expose
private int protectionRange; 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 // World the island started in. This may be different from the island location
@Expose @Expose
private World world; private World world;
@ -113,6 +117,7 @@ public class Island implements DataObject {
center = new Location(location.getWorld(), location.getX(), location.getY(), location.getZ()); center = new Location(location.getWorld(), location.getX(), location.getY(), location.getZ());
range = BentoBox.getInstance().getIWM().getIslandDistance(world); range = BentoBox.getInstance().getIWM().getIslandDistance(world);
this.protectionRange = protectionRange; this.protectionRange = protectionRange;
this.maxEverProtectionRange = protectionRange;
} }
/** /**
@ -284,6 +289,20 @@ public class Island implements DataObject {
return protectionRange; 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 * @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) { public void setProtectionRange(int protectionRange) {
this.protectionRange = protectionRange; this.protectionRange = protectionRange;
// Ratchet up the maximum protection range
if (protectionRange > this.maxEverProtectionRange) {
this.maxEverProtectionRange = protectionRange;
}
} }
/** /**
@ -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()); 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.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.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 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()); 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)); 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; 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) /* (non-Javadoc)
* @see world.bentobox.bentobox.database.AbstractDatabaseHandler#deleteObject(java.lang.Object) * @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) // Obtain the value of uniqueId within the instance (which must be a DataObject)
PropertyDescriptor propertyDescriptor = new PropertyDescriptor("uniqueId", dataObject); PropertyDescriptor propertyDescriptor = new PropertyDescriptor("uniqueId", dataObject);
Method method = propertyDescriptor.getReadMethod(); Method method = propertyDescriptor.getReadMethod();
String fileName = (String) method.invoke(instance); deleteID((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());
}
}
} }
@Override @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 // Island Cache
private IslandCache islandCache; private IslandCache islandCache;
// Async database saving semaphore
private boolean midSave;
/** /**
* Islands Manager * Islands Manager
* @param plugin - plugin * @param plugin - plugin
@ -694,8 +691,7 @@ public class IslandsManager {
* @param user - user * @param user - user
*/ */
public void removePlayer(World world, User user) { public void removePlayer(World world, User user) {
islandCache.removePlayer(world, user.getUniqueId()); removePlayer(world, user.getUniqueId());
save(true);
} }
/** /**
@ -704,8 +700,10 @@ public class IslandsManager {
* @param uuid - user's uuid * @param uuid - user's uuid
*/ */
public void removePlayer(World world, UUID uuid) { public void removePlayer(World world, UUID uuid) {
islandCache.removePlayer(world, uuid); Island island = islandCache.removePlayer(world, uuid);
save(true); if (island != null) {
handler.saveObject(island);
}
} }
/** /**
@ -736,28 +734,10 @@ public class IslandsManager {
} }
/** /**
* Save the islands to the database * Save the all the islands to the database
* @param async - if true, saving will be done async
*/ */
public void save(boolean async){ public void saveAll(){
if (midSave) {
// If it's already saving, then do nothing
return;
}
Collection<Island> collection = islandCache.getIslands(); 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){ for(Island island : collection){
try { try {
handler.saveObject(island); handler.saveObject(island);
@ -765,7 +745,7 @@ public class IslandsManager {
plugin.logError("Could not save island to database when running sync! " + e.getMessage()); 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 // Add player to new island
teamIsland.addMember(playerUUID); teamIsland.addMember(playerUUID);
islandCache.addPlayer(playerUUID, teamIsland); islandCache.addPlayer(playerUUID, teamIsland);
// Save the database // Save the island
save(false); handler.saveObject(teamIsland);
} }
public void setLast(Location last) { public void setLast(Location last) {
@ -798,7 +778,7 @@ public class IslandsManager {
public void shutdown(){ public void shutdown(){
// Remove all coop associations // Remove all coop associations
islandCache.getIslands().stream().forEach(i -> i.getMembers().values().removeIf(p -> p == RanksManager.COOP_RANK)); islandCache.getIslands().stream().forEach(i -> i.getMembers().values().removeIf(p -> p == RanksManager.COOP_RANK));
save(false); saveAll();
islandCache.clear(); islandCache.clear();
handler.close(); 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)); 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; package world.bentobox.bentobox.managers;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@ -8,7 +7,6 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.World; import org.bukkit.World;
@ -63,20 +61,13 @@ public class PlayersManager {
/** /**
* Save all players * Save all players
* @param async - if true, save async
*/ */
public void save(boolean async){ public void saveAll(){
Collection<Players> set = Collections.unmodifiableCollection(playerCache.values()); Collections.unmodifiableCollection(playerCache.values()).forEach(handler::saveObject);
if(async) {
Runnable save = () -> set.forEach(handler::saveObject);
Bukkit.getScheduler().runTaskAsynchronously(plugin, save);
} else {
set.forEach(handler::saveObject);
}
} }
public void shutdown(){ public void shutdown(){
save(false); saveAll();
playerCache.clear(); playerCache.clear();
handler.close(); 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. * The island is removed from the islandsByUUID map, but kept in the location map.
* @param world - world * @param world - world
* @param uuid - player's UUID * @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); world = Util.getWorld(world);
islandsByUUID.putIfAbsent(world, new HashMap<>()); islandsByUUID.putIfAbsent(world, new HashMap<>());
Island island = islandsByUUID.get(world).get(uuid); Island island = islandsByUUID.get(world).get(uuid);
@ -185,6 +186,7 @@ public class IslandCache {
} }
} }
islandsByUUID.get(world).remove(uuid); islandsByUUID.get(world).remove(uuid);
return island;
} }
/** /**

View File

@ -244,7 +244,7 @@ public class NewIsland {
*/ */
private Result isIsland(Location location){ private Result isIsland(Location location){
location = Util.getClosestIsland(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; return Result.ISLAND_FOUND;
} }

View File

@ -16,17 +16,23 @@ import world.bentobox.bentobox.database.objects.DeletedIslandDO;
*/ */
public class DeleteIslandChunks { 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 x;
private int z; private int z;
private BukkitTask task; private BukkitTask task;
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public DeleteIslandChunks(BentoBox plugin, DeletedIslandDO di) { public DeleteIslandChunks(BentoBox plugin, DeletedIslandDO di) {
// Fire event
IslandEvent.builder().deletedIslandInfo(di).reason(Reason.DELETE_CHUNKS).build();
x = di.getMinXChunk(); x = di.getMinXChunk();
z = di.getMinZChunk(); z = di.getMinZChunk();
task = Bukkit.getScheduler().runTaskTimer(plugin, () -> { task = Bukkit.getScheduler().runTaskTimer(plugin, () -> {
for (int i = 0; i < SPEED; i++) {
di.getWorld().regenerateChunk(x, z); di.getWorld().regenerateChunk(x, z);
//System.out.println("regenerating = " + x + "," + z);
if (plugin.getIWM().isNetherGenerate(di.getWorld()) && plugin.getIWM().isNetherIslands(di.getWorld())) { if (plugin.getIWM().isNetherGenerate(di.getWorld()) && plugin.getIWM().isNetherIslands(di.getWorld())) {
plugin.getIWM().getNetherWorld(di.getWorld()).regenerateChunk(x, z); plugin.getIWM().getNetherWorld(di.getWorld()).regenerateChunk(x, z);
@ -41,9 +47,12 @@ public class DeleteIslandChunks {
if (x > di.getMaxXChunk()) { if (x > di.getMaxXChunk()) {
task.cancel(); task.cancel();
// Fire event // Fire event
IslandEvent.builder().location(Util.getLocationString(di.getUniqueId())).reason(Reason.DELETED).build(); IslandEvent.builder().deletedIslandInfo(di).reason(Reason.DELETED).build();
}
} }
} }
}, 0L, 1L); }, 0L, 1L);
}} }
}

View File

@ -109,6 +109,7 @@ commands:
registered-island: "&aRegistered player to island at [xyz]." registered-island: "&aRegistered player to island at [xyz]."
already-owned: "&cIsland is already owned by another player!" already-owned: "&cIsland is already owned by another player!"
no-island-here: "&cThere is no island here. Confirm to make one." no-island-here: "&cThere is no island here. Confirm to make one."
in-deletion: "&cThis island space is currently being deleted. Try later."
unregister: unregister:
parameters: "<owner>" parameters: "<owner>"
description: "unregister owner from island, but keep island blocks" description: "unregister owner from island, but keep island blocks"
@ -128,6 +129,7 @@ commands:
island-location: "Island location: [xyz]" island-location: "Island location: [xyz]"
island-coords: "Island coordinates: [xz1] to [xz2]" island-coords: "Island coordinates: [xz1] to [xz2]"
protection-range: "Protection range: [range]" protection-range: "Protection range: [range]"
max-protection-range: "Largest historical protection range: [range]"
protection-coords: "Protection coordinates: [xz1] to [xz2]" protection-coords: "Protection coordinates: [xz1] to [xz2]"
is-spawn: "Island is a spawn island" is-spawn: "Island is a spawn island"
banned-players: "Banned players:" 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.api.user.User;
import world.bentobox.bentobox.database.objects.Island; import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.managers.CommandsManager; import world.bentobox.bentobox.managers.CommandsManager;
import world.bentobox.bentobox.managers.IslandDeleteManager;
import world.bentobox.bentobox.managers.IslandWorldManager; import world.bentobox.bentobox.managers.IslandWorldManager;
import world.bentobox.bentobox.managers.IslandsManager; import world.bentobox.bentobox.managers.IslandsManager;
import world.bentobox.bentobox.managers.LocalesManager; import world.bentobox.bentobox.managers.LocalesManager;
@ -50,6 +51,7 @@ public class AdminRegisterCommandTest {
private IslandsManager im; private IslandsManager im;
private PlayersManager pm; private PlayersManager pm;
private UUID notUUID; private UUID notUUID;
private IslandDeleteManager idm;
/** /**
* @throws java.lang.Exception * @throws java.lang.Exception
@ -111,11 +113,16 @@ public class AdminRegisterCommandTest {
LocalesManager lm = mock(LocalesManager.class); LocalesManager lm = mock(LocalesManager.class);
when(lm.get(Mockito.any(), Mockito.any())).thenReturn("mock translation"); when(lm.get(Mockito.any(), Mockito.any())).thenReturn("mock translation");
when(plugin.getLocalesManager()).thenReturn(lm); 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 @Test
public void testExecuteNoTarget() { 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 @Test
public void testExecuteUnknownPlayer() { 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 @Test
public void testExecutePlayerHasIsland() { 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 @Test
public void testExecuteInTeam() { 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 @Test
public void testExecuteAlreadyOwnedIsland() { public void testExecuteAlreadyOwnedIsland() {
@ -186,6 +193,32 @@ public class AdminRegisterCommandTest {
Mockito.verify(user).sendMessage("commands.admin.register.already-owned"); 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 @Test
public void testExecuteSuccess() { public void testExecuteSuccess() {
when(im.inTeam(Mockito.any(), Mockito.any())).thenReturn(false); 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.GameMode;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.Server;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.block.BlockFace; import org.bukkit.block.BlockFace;
@ -37,6 +38,7 @@ import org.bukkit.entity.Player;
import org.bukkit.entity.Slime; import org.bukkit.entity.Slime;
import org.bukkit.entity.Wither; import org.bukkit.entity.Wither;
import org.bukkit.entity.Zombie; import org.bukkit.entity.Zombie;
import org.bukkit.plugin.PluginManager;
import org.bukkit.scheduler.BukkitScheduler; import org.bukkit.scheduler.BukkitScheduler;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -54,6 +56,7 @@ import com.google.common.collect.ImmutableSet.Builder;
import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.Settings; import world.bentobox.bentobox.Settings;
import world.bentobox.bentobox.api.configuration.WorldSettings; 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.api.user.User;
import world.bentobox.bentobox.database.objects.Island; import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.lists.Flags; import world.bentobox.bentobox.lists.Flags;
@ -79,6 +82,7 @@ public class IslandsManagerTest {
private IslandCache islandCache; private IslandCache islandCache;
private Optional<Island> optionalIsland; private Optional<Island> optionalIsland;
private Island is; private Island is;
private PluginManager pim;
/** /**
* @throws java.lang.Exception * @throws java.lang.Exception
@ -122,8 +126,7 @@ public class IslandsManagerTest {
pm = mock(PlayersManager.class); pm = mock(PlayersManager.class);
when(plugin.getPlayers()).thenReturn(pm); when(plugin.getPlayers()).thenReturn(pm);
// Server & Scheduler // Scheduler
BukkitScheduler sch = mock(BukkitScheduler.class); BukkitScheduler sch = mock(BukkitScheduler.class);
PowerMockito.mockStatic(Bukkit.class); PowerMockito.mockStatic(Bukkit.class);
when(Bukkit.getScheduler()).thenReturn(sch); when(Bukkit.getScheduler()).thenReturn(sch);
@ -174,6 +177,11 @@ public class IslandsManagerTest {
// User location // User location
when(user.getLocation()).thenReturn(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 method for {@link world.bentobox.bentobox.managers.IslandsManager#deleteIsland(world.bentobox.bentobox.database.objects.Island, boolean)}.
*/ */
@Test @Test
public void testDeleteIslandIslandBoolean() { public void testDeleteIslandIslandBooleanNull() {
IslandsManager im = new IslandsManager(plugin); IslandsManager im = new IslandsManager(plugin);
im.deleteIsland((Island)null, true); 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(); UUID owner = UUID.randomUUID();
Island island = im.createIsland(location, owner); Island island = im.createIsland(location, owner);
im.deleteIsland(island, false); 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); 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 @Test
public void testSaveBoolean() { public void testSaveBoolean() {
PlayersManager pm = new PlayersManager(plugin); PlayersManager pm = new PlayersManager(plugin);
pm.save(false); pm.saveAll();
pm.save(true);
} }
/** /**

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());
}
}