From 501c3257edef316128e6890bf652f70d9b86c053 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 12 Jan 2019 17:12:30 -0800 Subject: [PATCH] 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. --- .../world/bentobox/bentobox/BentoBox.java | 29 ++- .../commands/admin/AdminRegisterCommand.java | 8 +- .../admin/range/AdminRangeResetCommand.java | 14 ++ .../admin/range/AdminRangeSetCommand.java | 14 ++ .../team/IslandTeamInviteAcceptCommand.java | 2 +- .../team/IslandTeamSetownerCommand.java | 7 +- .../bentobox/api/events/IslandBaseEvent.java | 2 +- .../api/events/island/IslandEvent.java | 47 +++- .../database/AbstractDatabaseHandler.java | 7 + .../bentobox/bentobox/database/Database.java | 11 + .../database/json/JSONDatabaseHandler.java | 42 ++-- .../mongodb/MongoDBDatabaseHandler.java | 16 +- .../database/mysql/MySQLDatabaseHandler.java | 26 ++- .../database/objects/DeletedIslandDO.java | 213 +++++++++--------- .../bentobox/database/objects/Island.java | 40 +++- .../database/yaml/YamlDatabaseHandler.java | 41 ++-- .../managers/IslandDeleteManager.java | 84 +++++++ .../bentobox/managers/IslandsManager.java | 61 ++--- .../bentobox/managers/PlayersManager.java | 15 +- .../bentobox/managers/island/IslandCache.java | 4 +- .../bentobox/managers/island/NewIsland.java | 2 +- .../bentobox/util/DeleteIslandChunks.java | 43 ++-- src/main/resources/locales/en-US.yml | 2 + .../admin/AdminRegisterCommandTest.java | 43 +++- .../bentobox/managers/IslandsManagerTest.java | 41 +++- .../bentobox/managers/PlayersManagerTest.java | 5 +- .../bentobox/util/DeleteIslandChunksTest.java | 152 ------------- 27 files changed, 562 insertions(+), 409 deletions(-) create mode 100644 src/main/java/world/bentobox/bentobox/managers/IslandDeleteManager.java delete mode 100644 src/test/java/world/bentobox/bentobox/util/DeleteIslandChunksTest.java diff --git a/src/main/java/world/bentobox/bentobox/BentoBox.java b/src/main/java/world/bentobox/bentobox/BentoBox.java index 89529ddb0..eda37b7cb 100644 --- a/src/main/java/world/bentobox/bentobox/BentoBox.java +++ b/src/main/java/world/bentobox/bentobox/BentoBox.java @@ -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; + } } diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminRegisterCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminRegisterCommand.java index 46a3654d0..a8c0a0461 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminRegisterCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminRegisterCommand.java @@ -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 = 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())); diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/range/AdminRangeResetCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/range/AdminRangeResetCommand.java index e835559a5..e89da5cce 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/admin/range/AdminRangeResetCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/range/AdminRangeResetCommand.java @@ -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> tabComplete(User user, String alias, List 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 options = new ArrayList<>(Util.getOnlinePlayerList(user)); + return Optional.of(Util.tabLimit(options, lastArg)); + } } diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/range/AdminRangeSetCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/range/AdminRangeSetCommand.java index db420855a..7c28e3b68 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/admin/range/AdminRangeSetCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/range/AdminRangeSetCommand.java @@ -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> tabComplete(User user, String alias, List 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 options = new ArrayList<>(Util.getOnlinePlayerList(user)); + return Optional.of(Util.tabLimit(options, lastArg)); + } } diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteAcceptCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteAcceptCommand.java index cd6b84aad..cd324876d 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteAcceptCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteAcceptCommand.java @@ -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; } diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamSetownerCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamSetownerCommand.java index 43c0e889c..d743818fc 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamSetownerCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamSetownerCommand.java @@ -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; } diff --git a/src/main/java/world/bentobox/bentobox/api/events/IslandBaseEvent.java b/src/main/java/world/bentobox/bentobox/api/events/IslandBaseEvent.java index c0002f2fc..eb8395394 100644 --- a/src/main/java/world/bentobox/bentobox/api/events/IslandBaseEvent.java +++ b/src/main/java/world/bentobox/bentobox/api/events/IslandBaseEvent.java @@ -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; diff --git a/src/main/java/world/bentobox/bentobox/api/events/island/IslandEvent.java b/src/main/java/world/bentobox/bentobox/api/events/island/IslandEvent.java index 84fced873..70152b0b1 100644 --- a/src/main/java/world/bentobox/bentobox/api/events/island/IslandEvent.java +++ b/src/main/java/world/bentobox/bentobox/api/events/island/IslandEvent.java @@ -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: diff --git a/src/main/java/world/bentobox/bentobox/database/AbstractDatabaseHandler.java b/src/main/java/world/bentobox/bentobox/database/AbstractDatabaseHandler.java index a56eae483..95772b0ad 100644 --- a/src/main/java/world/bentobox/bentobox/database/AbstractDatabaseHandler.java +++ b/src/main/java/world/bentobox/bentobox/database/AbstractDatabaseHandler.java @@ -110,4 +110,11 @@ public abstract class AbstractDatabaseHandler { * 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); } diff --git a/src/main/java/world/bentobox/bentobox/database/Database.java b/src/main/java/world/bentobox/bentobox/database/Database.java index 9e97be8dc..21d1739e0 100644 --- a/src/main/java/world/bentobox/bentobox/database/Database.java +++ b/src/main/java/world/bentobox/bentobox/database/Database.java @@ -100,6 +100,15 @@ public class Database { 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 { handler.close(); } + + } \ No newline at end of file diff --git a/src/main/java/world/bentobox/bentobox/database/json/JSONDatabaseHandler.java b/src/main/java/world/bentobox/bentobox/database/json/JSONDatabaseHandler.java index 8f0ce46f2..4f3f8e9cf 100644 --- a/src/main/java/world/bentobox/bentobox/database/json/JSONDatabaseHandler.java +++ b/src/main/java/world/bentobox/bentobox/database/json/JSONDatabaseHandler.java @@ -122,6 +122,28 @@ public class JSONDatabaseHandler extends AbstractJSONDatabaseHandler { } } + @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 extends AbstractJSONDatabaseHandler { // 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 diff --git a/src/main/java/world/bentobox/bentobox/database/mongodb/MongoDBDatabaseHandler.java b/src/main/java/world/bentobox/bentobox/database/mongodb/MongoDBDatabaseHandler.java index 05e031f61..b5a60efdb 100644 --- a/src/main/java/world/bentobox/bentobox/database/mongodb/MongoDBDatabaseHandler.java +++ b/src/main/java/world/bentobox/bentobox/database/mongodb/MongoDBDatabaseHandler.java @@ -106,6 +106,16 @@ public class MongoDBDatabaseHandler extends AbstractJSONDatabaseHandler { } } + @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 extends AbstractJSONDatabaseHandler { 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 diff --git a/src/main/java/world/bentobox/bentobox/database/mysql/MySQLDatabaseHandler.java b/src/main/java/world/bentobox/bentobox/database/mysql/MySQLDatabaseHandler.java index 6f1f5bf72..3ec2d216c 100644 --- a/src/main/java/world/bentobox/bentobox/database/mysql/MySQLDatabaseHandler.java +++ b/src/main/java/world/bentobox/bentobox/database/mysql/MySQLDatabaseHandler.java @@ -141,6 +141,22 @@ public class MySQLDatabaseHandler extends AbstractJSONDatabaseHandler { } } + @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 extends AbstractJSONDatabaseHandler { 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()); } diff --git a/src/main/java/world/bentobox/bentobox/database/objects/DeletedIslandDO.java b/src/main/java/world/bentobox/bentobox/database/objects/DeletedIslandDO.java index 22de17b59..57bdd621d 100644 --- a/src/main/java/world/bentobox/bentobox/database/objects/DeletedIslandDO.java +++ b/src/main/java/world/bentobox/bentobox/database/objects/DeletedIslandDO.java @@ -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; + } + } diff --git a/src/main/java/world/bentobox/bentobox/database/objects/Island.java b/src/main/java/world/bentobox/bentobox/database/objects/Island.java index f354de540..943fa68e1 100644 --- a/src/main/java/world/bentobox/bentobox/database/objects/Island.java +++ b/src/main/java/world/bentobox/bentobox/database/objects/Island.java @@ -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)); diff --git a/src/main/java/world/bentobox/bentobox/database/yaml/YamlDatabaseHandler.java b/src/main/java/world/bentobox/bentobox/database/yaml/YamlDatabaseHandler.java index 3a80910e1..78292f23e 100644 --- a/src/main/java/world/bentobox/bentobox/database/yaml/YamlDatabaseHandler.java +++ b/src/main/java/world/bentobox/bentobox/database/yaml/YamlDatabaseHandler.java @@ -553,6 +553,28 @@ public class YamlDatabaseHandler extends AbstractDatabaseHandler { 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 extends AbstractDatabaseHandler { // 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 diff --git a/src/main/java/world/bentobox/bentobox/managers/IslandDeleteManager.java b/src/main/java/world/bentobox/bentobox/managers/IslandDeleteManager.java new file mode 100644 index 000000000..49d9ca58e --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/managers/IslandDeleteManager.java @@ -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 handler; + private Set 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 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); + } +} diff --git a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java index d8530c71b..6e3675946 100644 --- a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java @@ -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 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); + + } + } diff --git a/src/main/java/world/bentobox/bentobox/managers/PlayersManager.java b/src/main/java/world/bentobox/bentobox/managers/PlayersManager.java index 2a731a127..ddacfc833 100644 --- a/src/main/java/world/bentobox/bentobox/managers/PlayersManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/PlayersManager.java @@ -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 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(); } diff --git a/src/main/java/world/bentobox/bentobox/managers/island/IslandCache.java b/src/main/java/world/bentobox/bentobox/managers/island/IslandCache.java index aa697d170..046e4dd63 100644 --- a/src/main/java/world/bentobox/bentobox/managers/island/IslandCache.java +++ b/src/main/java/world/bentobox/bentobox/managers/island/IslandCache.java @@ -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; } /** diff --git a/src/main/java/world/bentobox/bentobox/managers/island/NewIsland.java b/src/main/java/world/bentobox/bentobox/managers/island/NewIsland.java index 9b4281fd3..e26702ab9 100644 --- a/src/main/java/world/bentobox/bentobox/managers/island/NewIsland.java +++ b/src/main/java/world/bentobox/bentobox/managers/island/NewIsland.java @@ -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; } diff --git a/src/main/java/world/bentobox/bentobox/util/DeleteIslandChunks.java b/src/main/java/world/bentobox/bentobox/util/DeleteIslandChunks.java index 7aa610498..2a56105de 100644 --- a/src/main/java/world/bentobox/bentobox/util/DeleteIslandChunks.java +++ b/src/main/java/world/bentobox/bentobox/util/DeleteIslandChunks.java @@ -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); - }} \ No newline at end of file + } + +} \ No newline at end of file diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 35bf52dbd..1823bb7a5 100644 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -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: "" 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:" diff --git a/src/test/java/world/bentobox/bentobox/api/commands/admin/AdminRegisterCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/admin/AdminRegisterCommandTest.java index 95e8e5cc8..131b47908 100644 --- a/src/test/java/world/bentobox/bentobox/api/commands/admin/AdminRegisterCommandTest.java +++ b/src/test/java/world/bentobox/bentobox/api/commands/admin/AdminRegisterCommandTest.java @@ -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 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); diff --git a/src/test/java/world/bentobox/bentobox/managers/IslandsManagerTest.java b/src/test/java/world/bentobox/bentobox/managers/IslandsManagerTest.java index e04e2575b..fc0b020c3 100644 --- a/src/test/java/world/bentobox/bentobox/managers/IslandsManagerTest.java +++ b/src/test/java/world/bentobox/bentobox/managers/IslandsManagerTest.java @@ -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 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)); } /** diff --git a/src/test/java/world/bentobox/bentobox/managers/PlayersManagerTest.java b/src/test/java/world/bentobox/bentobox/managers/PlayersManagerTest.java index b42d47e79..e73adcd43 100644 --- a/src/test/java/world/bentobox/bentobox/managers/PlayersManagerTest.java +++ b/src/test/java/world/bentobox/bentobox/managers/PlayersManagerTest.java @@ -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(); } /** diff --git a/src/test/java/world/bentobox/bentobox/util/DeleteIslandChunksTest.java b/src/test/java/world/bentobox/bentobox/util/DeleteIslandChunksTest.java deleted file mode 100644 index c914f493f..000000000 --- a/src/test/java/world/bentobox/bentobox/util/DeleteIslandChunksTest.java +++ /dev/null @@ -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()); - } - -}