diff --git a/src/main/java/world/bentobox/bentobox/api/panels/reader/ItemTemplateRecord.java b/src/main/java/world/bentobox/bentobox/api/panels/reader/ItemTemplateRecord.java index e5911c802..c1c157124 100644 --- a/src/main/java/world/bentobox/bentobox/api/panels/reader/ItemTemplateRecord.java +++ b/src/main/java/world/bentobox/bentobox/api/panels/reader/ItemTemplateRecord.java @@ -17,6 +17,8 @@ import org.bukkit.inventory.ItemStack; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; +import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord.ActionRecords; + /** * This Record contains all necessary information about Item Template that can be used to craft panel item. * diff --git a/src/main/java/world/bentobox/bentobox/api/panels/reader/PanelTemplateRecord.java b/src/main/java/world/bentobox/bentobox/api/panels/reader/PanelTemplateRecord.java index f260fb564..18d5506f1 100644 --- a/src/main/java/world/bentobox/bentobox/api/panels/reader/PanelTemplateRecord.java +++ b/src/main/java/world/bentobox/bentobox/api/panels/reader/PanelTemplateRecord.java @@ -15,6 +15,7 @@ import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import world.bentobox.bentobox.api.panels.Panel; +import world.bentobox.bentobox.api.panels.reader.PanelTemplateRecord.TemplateItem; /** * This is template object for the panel reader. It contains data that can exist in the panel. 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 338e5c5c1..49668d108 100644 --- a/src/main/java/world/bentobox/bentobox/database/objects/Island.java +++ b/src/main/java/world/bentobox/bentobox/database/objects/Island.java @@ -22,6 +22,7 @@ import org.bukkit.World; import org.bukkit.World.Environment; import org.bukkit.entity.Player; import org.bukkit.util.BoundingBox; +import org.bukkit.util.Vector; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; @@ -55,1827 +56,1835 @@ import world.bentobox.bentobox.util.Util; @Table(name = "Islands") public class Island implements DataObject, MetaDataAble { - @Expose - private boolean primary; - - /** - * Set to true if this data object has been changed since being loaded from the database - */ - private boolean changed; - - // True if this island is deleted and pending deletion from the database - @Expose - private boolean deleted = false; - - @Expose - @NonNull - private String uniqueId = UUID.randomUUID().toString(); - - //// Island //// - // The center of the island space - @Expose - private Location center; - - /** - * The center location of the protection area - */ - @Expose - @Nullable - private Location location; - - - // Island range - @Expose - private int range; - - // Protection size - @Expose - private int protectionRange; - - /** - * Bonuses to protection range - * @since 1.20.0 - */ - @Expose - private List bonusRanges = new ArrayList<>(); - - // 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; - - /** - * Name of the {@link world.bentobox.bentobox.api.addons.GameModeAddon GameModeAddon} this island is handled by. - * @since 1.5.0 - */ - @Expose - private String gameMode; - - // Display name - @Expose - @Nullable - private String name; - - // Time parameters - @Expose - private long createdDate; - @Expose - private long updatedDate; - - //// Team //// - /** - * Owner of the island. - * There can only be one per island. - * If it is {@code null}, then the island is considered as unowned. - */ - @Expose - @Nullable - private UUID owner; - - /** - * Members of the island. - * It contains any player which has one of the following rank on this island: {@link RanksManager#COOP_RANK COOP}, - * {@link RanksManager#TRUSTED_RANK TRUSTED}, {@link RanksManager#MEMBER_RANK MEMBER}, {@link RanksManager#SUB_OWNER_RANK SUB_OWNER}, - * {@link RanksManager#OWNER_RANK OWNER}. - */ - @Expose - private Map members = new HashMap<>(); - - /** - * Maximum number of members allowed in this island. - * Key is rank, value is number - * @since 1.16.0 - */ - @Expose - private Map maxMembers; - - //// State //// - @Expose - private boolean spawn = false; - @Expose - private boolean purgeProtected = false; - - //// Protection flags //// - @Expose - private Map flags = new HashMap<>(); - - //// Island History //// - @Adapter(LogEntryListAdapter.class) - @Expose - private List history = new LinkedList<>(); - - @Expose - private Map spawnPoint = new EnumMap<>(Environment.class); - - /** - * This flag is used to quarantine islands that cannot be loaded and should be purged at some point - */ - @Expose - private boolean doNotLoad; - - /** - * Used to store flag cooldowns for this island - */ - @Expose - private Map cooldowns = new HashMap<>(); - - /** - * Commands and the rank required to use them for this island - */ - @Expose - private Map commandRanks; - - /** - * If true then this space is reserved for the owner and when they teleport there they will be asked to make an island - * @since 1.6.0 - */ - @Expose - @Nullable - private Boolean reserved = null; - - /** - * A place to store metadata for this island. - * @since 1.15.4 - */ - @Expose - private Map metaData; - - /** - * Island homes. Replaces player homes - * @since 1.16.0 - */ - @Expose - private Map homes; - - /** - * The maximum number of homes allowed on this island. If null, then the world default is used. - */ - @Expose - private Integer maxHomes; - - /* - * *************************** Constructors ****************************** - */ - - public Island() {} - - public Island(@NonNull Location location, UUID owner, int protectionRange) { - setOwner(owner); - createdDate = System.currentTimeMillis(); - updatedDate = System.currentTimeMillis(); - world = location.getWorld(); - // Make a copy of the location - center = new Location(location.getWorld(), location.getX(), location.getY(), location.getZ()); - range = BentoBox.getInstance().getIWM().getIslandDistance(world); - this.protectionRange = protectionRange; - this.maxEverProtectionRange = protectionRange; - this.setChanged(); - } - - - /** - * Clones an island object - * @param island - island to clone - */ - public Island(Island island) { - this.center = island.getCenter().clone(); - this.createdDate = island.getCreatedDate(); - Optional.ofNullable(island.getCommandRanks()).ifPresent(cr -> { - this.commandRanks = new HashMap<>(); - this.commandRanks.putAll(cr); - }); - Optional.ofNullable(island.getCooldowns()).ifPresent(c -> { - this.cooldowns = new HashMap<>(); - this.cooldowns.putAll(c); - }); - this.createdDate = island.getCreatedDate(); - this.deleted = island.isDeleted(); - this.doNotLoad = island.isDoNotLoad(); - this.flags.putAll(island.getFlags()); - this.gameMode = island.getGameMode(); - this.homes = new HashMap<>(island.getHomes()); - this.history.addAll(island.getHistory()); - this.location = island.getProtectionCenter(); - this.maxEverProtectionRange = island.getMaxEverProtectionRange(); - this.maxHomes = island.getMaxHomes(); - this.maxMembers = new HashMap<>(island.getMaxMembers()); - this.members.putAll(island.getMembers()); - island.getMetaData().ifPresent(m -> { - this.metaData = new HashMap<>(); - this.metaData.putAll(m); - }); - this.name = island.getName(); - this.owner = island.getOwner(); - this.protectionRange = island.getProtectionRange(); - this.purgeProtected = island.getPurgeProtected(); - this.range = island.getRange(); - this.reserved = island.isReserved(); - this.spawn = island.isSpawn(); - island.getSpawnPoint().forEach((k,v) -> island.spawnPoint.put(k, v.clone())); - this.uniqueId = island.getUniqueId(); - this.updatedDate = island.getUpdatedDate(); - this.world = island.getWorld(); - this.bonusRanges.addAll(island.getBonusRanges()); - this.setChanged(); - } - - /* - * *************************** Methods ****************************** - */ - - /** - * Adds a team member. If player is on banned list, they will be removed from it. - * @param playerUUID - the player's UUID - */ - public void addMember(@NonNull UUID playerUUID) { - setRank(playerUUID, RanksManager.MEMBER_RANK); - setChanged(); - } - - /** - * Bans the target player from this Island. - * If the player is a member, coop or trustee, they will be removed from those lists. - *
- * Calling this method won't call the {@link world.bentobox.bentobox.api.events.island.IslandBanEvent}. - * @param issuer UUID of the issuer, may be null. - * Whenever possible, one should be provided. - * @param target UUID of the target, must be provided. - * @return {@code true} - */ - public boolean ban(@NonNull UUID issuer, @NonNull UUID target) { - setRank(target, RanksManager.BANNED_RANK); - log(new LogEntry.Builder("BAN").data("player", target.toString()).data("issuer", issuer.toString()).build()); - setChanged(); - return true; - } - - /** - * @return the banned - */ - public Set getBanned() { - Set result = new HashSet<>(); - for (Entry member: members.entrySet()) { - if (member.getValue() <= RanksManager.BANNED_RANK) { - result.add(member.getKey()); - } - } - return result; - } - - /** - * Unbans the target player from this Island. - *
- * Calling this method won't call the {@link world.bentobox.bentobox.api.events.island.IslandUnbanEvent}. - * @param issuer UUID of the issuer, may be null. - * Whenever possible, one should be provided. - * @param target UUID of the target, must be provided. - * @return {@code true} if the target is successfully unbanned, {@code false} otherwise. - */ - public boolean unban(@NonNull UUID issuer, @NonNull UUID target) { - if (members.remove(target) != null) { - log(new LogEntry.Builder("UNBAN").data("player", target.toString()).data("issuer", issuer.toString()).build()); - return true; - } - return false; - } - - /** - * Returns a clone of the location of the center of this island. - * @return clone of the center Location - */ - @NonNull - public Location getCenter(){ - return Objects.requireNonNull(center, "Island getCenter requires a non-null center").clone(); - } - - /** - * @return the date when the island was created - */ - public long getCreatedDate(){ - return createdDate; - } - - /** - * Gets the Island Guard flag's setting. If this is a protection flag, then this will be the - * rank needed to bypass this flag. If it is a Settings flag, any non-zero value means the - * setting is allowed. - * @param flag - flag - * @return flag value - */ - public int getFlag(@NonNull Flag flag) { - return flags.computeIfAbsent(flag.getID(), k -> flag.getDefaultRank()); - } - - /** - * @return the flags - */ - public Map getFlags() { - return flags; - } - - /** - * Returns the members of this island. - * It contains all players that have any rank on this island, including {@link RanksManager#BANNED_RANK BANNED}, - * {@link RanksManager#TRUSTED_RANK TRUSTED}, {@link RanksManager#MEMBER_RANK MEMBER}, {@link RanksManager#SUB_OWNER_RANK SUB_OWNER}, - * {@link RanksManager#OWNER_RANK OWNER}, etc. - * - * @return the members - key is the UUID, value is the RanksManager enum, e.g. {@link RanksManager#MEMBER_RANK}. - * @see #getMemberSet() - */ - public Map getMembers() { - return members; - } - - /** - * Returns an immutable set containing the UUIDs of players that are truly members of this island. - * This includes any player which has one of the following rank on this island: {@link RanksManager#MEMBER_RANK MEMBER}, - * {@link RanksManager#SUB_OWNER_RANK SUB_OWNER}, {@link RanksManager#OWNER_RANK OWNER}. - * @return the members of the island (owner included) - * @see #getMembers() - */ - public ImmutableSet getMemberSet(){ - return getMemberSet(RanksManager.MEMBER_RANK); - } - - /** - * Returns an immutable set containing the UUIDs of players with rank above that requested rank inclusive - * @param minimumRank minimum rank (inclusive) of members - * @return immutable set of UUIDs - * @see #getMembers() - * @since 1.5.0 - */ - public @NonNull ImmutableSet getMemberSet(int minimumRank) { - Builder result = new ImmutableSet.Builder<>(); - members.entrySet().stream().filter(e -> e.getValue() >= minimumRank).map(Map.Entry::getKey).forEach(result::add); - return result.build(); - } - - /** - * Returns an immutable set containing the UUIDs of players with rank equal or above that requested rank (inclusive). - * @param rank rank to request - * @param includeAboveRanks whether including players with rank above the requested rank or not - * @return immutable set of UUIDs - * @see #getMemberSet(int) - * @see #getMembers() - * @since 1.5.0 - */ - public @NonNull ImmutableSet getMemberSet(int rank, boolean includeAboveRanks) { - if (includeAboveRanks) { - return getMemberSet(rank); - } - Builder result = new ImmutableSet.Builder<>(); - members.entrySet().stream().filter(e -> e.getValue() == rank).map(Map.Entry::getKey).forEach(result::add); - return result.build(); - } - - /** - * Get the minimum protected X block coordinate based on the island location. - * It will never be less than {@link #getMinX()} - * @return the minProtectedX - */ - public int getMinProtectedX() { - return Math.max(getMinX(), getProtectionCenter().getBlockX() - protectionRange); - } - - /** - * Get the maximum protected X block coordinate based on the island location. - * It will never be more than {@link #getMaxX()} - * @return the maxProtectedX - * @since 1.5.2 - */ - public int getMaxProtectedX() { - return Math.min(getMaxX(), getProtectionCenter().getBlockX() + protectionRange); - } - - /** - * Get the minimum protected Z block coordinate based on the island location. - * It will never be less than {@link #getMinZ()} - * @return the minProtectedZ - */ - public int getMinProtectedZ() { - return Math.max(getMinZ(), getProtectionCenter().getBlockZ() - protectionRange); - } - - /** - * Get the maximum protected Z block coordinate based on the island location. - * It will never be more than {@link #getMinZ()} - * @return the maxProtectedZ - * @since 1.5.2 - */ - public int getMaxProtectedZ() { - return Math.min(getMaxZ(), getProtectionCenter().getBlockZ() + protectionRange); - } - - /** - * @return the minX - */ - public int getMinX() { - return center.getBlockX() - range; - } - - /** - * @return the maxX - * @since 1.5.2 - */ - public int getMaxX() { - return center.getBlockX() + range; - } - - /** - * @return the minZ - */ - public int getMinZ() { - return center.getBlockZ() - range; - } - - /** - * @return the maxZ - * @since 1.5.2 - */ - public int getMaxZ() { - return center.getBlockZ() + range; - } - - /** - * @return the island display name. Might be {@code null} if none is set. - */ - @Nullable - public String getName() { - return name; - } - - /** - * Returns the owner of this island. - * @return the owner, may be null. - * @see #isOwned() - * @see #isUnowned() - */ - @Nullable - public UUID getOwner(){ - return owner; - } - - /** - * Returns whether this island is owned or not. - * @return {@code true} if this island has an owner, {@code false} otherwise. - * @since 1.9.1 - * @see #getOwner() - * @see #isUnowned() - */ - public boolean isOwned() { - return owner != null; - } - - /** - * Returns whether this island does not have an owner. - * @return {@code true} if this island does not have an owner, {@code false} otherwise. - * @since 1.9.1 - * @see #getOwner() - * @see #isOwned() - */ - public boolean isUnowned() { - return owner == null; - } - - /** - * Returns the protection range of this Island plus any bonuses. Will not be greater than getRange(). - * This represents half of the length of the side of a theoretical square around the island center inside which flags are enforced. - * @return the protection range of this island, strictly positive integer. - * @see #getRange() - */ - public int getProtectionRange() { - return Math.min(this.getRange(), getRawProtectionRange() + this.getBonusRanges().stream().mapToInt(BonusRangeRecord::getRange).sum()); - } - - /** - * Returns the protection range of this Island without any bonuses - * This represents half of the length of the side of a theoretical square around the island center inside which flags are enforced. - * @return the protection range of this island, strictly positive integer. - * @since 1.20.0 - */ - public int getRawProtectionRange() { - return protectionRange; - } - - /** - * @return the maxEverProtectionRange or the protection range, whichever is larger - */ - public int getMaxEverProtectionRange() { - if (maxEverProtectionRange > this.getRange()) { - maxEverProtectionRange = this.getRange(); - setChanged(); - } - return Math.max(this.getProtectionRange(), maxEverProtectionRange); - } - - /** - * Sets the maximum protection range. This can be used to optimize - * island deletion. Setting this values to a lower value than the current value - * will have no effect. - * @param maxEverProtectionRange the maxEverProtectionRange to set - */ - public void setMaxEverProtectionRange(int maxEverProtectionRange) { - if (maxEverProtectionRange > this.maxEverProtectionRange) { - this.maxEverProtectionRange = maxEverProtectionRange; - } - if (maxEverProtectionRange > this.range) { - this.maxEverProtectionRange = this.range; - } - setChanged(); - } - - /** - * @return true if the island is protected from the Purge, otherwise false - */ - public boolean getPurgeProtected(){ - return purgeProtected; - } - - /** - * Returns the island range. - * It is a convenience method that returns the exact same value than island range, although it has been saved into the Island object for easier access. - * @return the island range - * @see #getProtectionRange() - */ - public int getRange(){ - return range; - } - - /** - * Get the rank of user for this island - * @param user - the User - * @return rank integer - */ - public int getRank(User user) { - if (user.isOp()) { - return RanksManager.ADMIN_RANK; - } - return members.getOrDefault(user.getUniqueId(), RanksManager.VISITOR_RANK); - } - - /** - * Get the rank of user for this island - * @param userUUID - the User's UUID - * @return rank integer - * @since 1.14.0 - */ - public int getRank(UUID userUUID) { - return members.getOrDefault(userUUID, RanksManager.VISITOR_RANK); - } - - @Override - public @NonNull String getUniqueId() { - return uniqueId; - } - - /** - * @return the date when the island was updated (team member connection, etc...) - */ - public long getUpdatedDate(){ - return updatedDate; - } - - /** - * @return the world - */ - public World getWorld() { - return world; - } - - - /** - * @return the nether world - */ - @Nullable - public World getNetherWorld() - { - return this.getWorld(Environment.NETHER); - } - - - /** - * @return the end world - */ - @Nullable - public World getEndWorld() - { - return this.getWorld(Environment.THE_END); - } - - - /** - * This method returns this island world in given environment. This method can return {@code null} if dimension is - * disabled. - * @param environment The environment of the island world. - * @return the world in given environment. - */ - @Nullable - public World getWorld(Environment environment) - { - if (Environment.NORMAL.equals(environment)) - { - return this.world; - } - else if (Environment.THE_END.equals(environment) && this.isEndIslandEnabled()) - { - return this.getPlugin().getIWM().getEndWorld(this.world); - } - else if (Environment.NETHER.equals(environment) && this.isNetherIslandEnabled()) - { - return this.getPlugin().getIWM().getNetherWorld(this.world); - } - else - { - return null; - } - } - - - /** - * @return the x coordinate of the island center - */ - public int getX(){ - return center.getBlockX(); - } - - /** - * @return the y coordinate of the island center - */ - public int getY(){ - return center.getBlockY(); - } - - /** - * @return the z coordinate of the island center - */ - public int getZ(){ - return center.getBlockZ(); - } - - /** - * Checks if coords are in the island space - * @param x - x coordinate - * @param z - z coordinate - * @return true if in the island space - */ - public boolean inIslandSpace(int x, int z) { - return x >= getMinX() && x < getMinX() + range*2 && z >= getMinZ() && z < getMinZ() + range*2; - } - - /** - * Checks if location is in full island space, not just protected space - * @param location - location - * @return true if in island space - */ - public boolean inIslandSpace(Location location) { - return Util.sameWorld(this.world, location.getWorld()) && - (location.getWorld().getEnvironment().equals(Environment.NORMAL) || - this.getPlugin().getIWM().isIslandNether(location.getWorld()) || - this.getPlugin().getIWM().isIslandEnd(location.getWorld())) && - this.inIslandSpace(location.getBlockX(), location.getBlockZ()); - } - - /** - * Checks if the coordinates are in full island space, not just protected space - * @param blockCoordinates - Pair(x,z) coordinates of block - * @return true or false - */ - public boolean inIslandSpace(Pair blockCoordinates) { - return inIslandSpace(blockCoordinates.x, blockCoordinates.z); - } - - /** - * Returns a {@link BoundingBox} of the full island space for overworld. - * @return a {@link BoundingBox} of the full island space. - * @since 1.5.2 - */ - @NonNull - public BoundingBox getBoundingBox() { - return this.getBoundingBox(Environment.NORMAL); - } - - - /** - * Returns a {@link BoundingBox} of this island's space area in requested dimension. - * @param environment the requested dimension. - * @return a {@link BoundingBox} of this island's space area or {@code null} if island is not created in requested dimension. - * @since 1.21.0 - */ - @Nullable - public BoundingBox getBoundingBox(Environment environment) - { - BoundingBox boundingBox; - - if (Environment.NORMAL.equals(environment)) - { - // Return normal world bounding box. - boundingBox = new BoundingBox(this.getMinX(), - this.world.getMinHeight(), - this.getMinZ(), - this.getMaxX(), - this.world.getMaxHeight(), - this.getMaxZ()); - } - else if (Environment.THE_END.equals(environment) && this.isEndIslandEnabled()) - { - // If end world is generated, return end island bounding box. - boundingBox = new BoundingBox(this.getMinX(), - this.getEndWorld().getMinHeight(), - this.getMinZ(), - this.getMaxX(), - this.getEndWorld().getMaxHeight(), - this.getMaxZ()); - } - else if (Environment.NETHER.equals(environment) && this.isNetherIslandEnabled()) - { - // If nether world is generated, return nether island bounding box. - boundingBox = new BoundingBox(this.getMinX(), - this.getNetherWorld().getMinHeight(), - this.getMinZ(), - this.getMaxX(), - this.getNetherWorld().getMaxHeight(), - this.getMaxZ()); - } - else - { - boundingBox = null; - } - - return boundingBox; - } - - - /** - * Using this method in the filtering for getVisitors and hasVisitors - * @param player The player that must be checked. - * @return true if player is a visitor - */ - private boolean playerIsVisitor(Player player) { - if (player.getGameMode() == GameMode.SPECTATOR) { - return false; - } - - return onIsland(player.getLocation()) && getRank(User.getInstance(player)) == RanksManager.VISITOR_RANK; - } - - /** - * Returns a list of players that are physically inside the island's protection range and that are visitors. - * @return list of visitors - * @since 1.3.0 - */ - @NonNull - public List getVisitors() { - return Bukkit.getOnlinePlayers().stream().filter(this::playerIsVisitor).collect(Collectors.toList()); - } - - /** - * Returns whether this Island has visitors inside its protection range. - * Note this is equivalent to {@code !island.getVisitors().isEmpty()}. - * @return {@code true} if there are visitors inside this Island's protection range, {@code false} otherwise. - * - * @since 1.3.0 - * @see #getVisitors() - */ - public boolean hasVisitors() { - return Bukkit.getOnlinePlayers().stream().anyMatch(this::playerIsVisitor); - } - - /** - * Returns a list of players that are physically inside the island's protection range - * @return list of players - * @since 1.6.0 - */ - @NonNull - public List getPlayersOnIsland() { - return Bukkit.getOnlinePlayers().stream() - .filter(player -> onIsland(player.getLocation())) - .collect(Collectors.toList()); - } - - /** - * Returns whether this Island has players inside its protection range. - * Note this is equivalent to {@code !island.getPlayersOnIsland().isEmpty()}. - * @return {@code true} if there are players inside this Island's protection range, {@code false} otherwise. - * - * @since 1.6.0 - * @see #getPlayersOnIsland() - */ - public boolean hasPlayersOnIsland() { - return Bukkit.getOnlinePlayers().stream().anyMatch(player -> onIsland(player.getLocation())); - } - - /** - * Check if the flag is allowed or not - * For flags that are for the island in general and not related to rank. - * @param flag - flag - * @return true if allowed, false if not - */ - public boolean isAllowed(Flag flag) { - // A negative value means not allowed - return getFlag(flag) >= 0; - } - - /** - * Check if a user is allowed to bypass the flag or not - * @param user - the User - user - * @param flag - flag - * @return true if allowed, false if not - */ - public boolean isAllowed(User user, Flag flag) { - return getRank(user) >= getFlag(flag); - } - - /** - * Check if banned - * @param targetUUID - the target's UUID - * @return Returns true if target is banned on this island - */ - public boolean isBanned(UUID targetUUID) { - return members.containsKey(targetUUID) && members.get(targetUUID).equals(RanksManager.BANNED_RANK); - } - - /** - * Returns whether the island is a spawn or not. - * @return {@code true} if the island is a spawn, {@code false} otherwise. - */ - public boolean isSpawn() { - return spawn; - } - - /** - * Checks if a location is within this island's protected area. - * - * @param target location to check, not null - * @return {@code true} if this location is within this island's protected area, {@code false} otherwise. - */ - public boolean onIsland(@NonNull Location target) { - return Util.sameWorld(this.world, target.getWorld()) && - (target.getWorld().getEnvironment().equals(Environment.NORMAL) || - this.getPlugin().getIWM().isIslandNether(target.getWorld()) || - this.getPlugin().getIWM().isIslandEnd(target.getWorld())) && - target.getBlockX() >= this.getMinProtectedX() && - target.getBlockX() < (this.getMinProtectedX() + this.protectionRange * 2) && - target.getBlockZ() >= this.getMinProtectedZ() && - target.getBlockZ() < (this.getMinProtectedZ() + this.protectionRange * 2); - } - - /** - * Returns a {@link BoundingBox} of this island's protected area for overworld. - * @return a {@link BoundingBox} of this island's protected area. - * @since 1.5.2 - */ - @NonNull - public BoundingBox getProtectionBoundingBox() { - return this.getProtectionBoundingBox(Environment.NORMAL); - } - - - /** - * Returns a {@link BoundingBox} of this island's protected area. - * @param environment an environment of bounding box area. - * @return a {@link BoundingBox} of this island's protected area or {@code null} if island is not created in required dimension. - * in required dimension. - * @since 1.21.0 - */ - @Nullable - public BoundingBox getProtectionBoundingBox(Environment environment) - { - BoundingBox boundingBox; - - if (Environment.NORMAL.equals(environment)) - { - // Return normal world bounding box. - boundingBox = new BoundingBox(this.getMinProtectedX(), - this.world.getMinHeight(), - this.getMinProtectedZ(), - this.getMaxProtectedX(), - this.world.getMaxHeight(), - this.getMaxProtectedZ()); - } - else if (Environment.THE_END.equals(environment) && this.isEndIslandEnabled()) - { - // If end world is generated, return end island bounding box. - boundingBox = new BoundingBox(this.getMinProtectedX(), - this.getEndWorld().getMinHeight(), - this.getMinProtectedZ(), - this.getMaxProtectedX(), - this.getEndWorld().getMaxHeight(), - this.getMaxProtectedZ()); - } - else if (Environment.NETHER.equals(environment) && this.isNetherIslandEnabled()) - { - // If nether world is generated, return nether island bounding box. - boundingBox = new BoundingBox(this.getMinProtectedX(), - this.getNetherWorld().getMinHeight(), - this.getMinProtectedZ(), - this.getMaxProtectedX(), - this.getNetherWorld().getMaxHeight(), - this.getMaxProtectedZ()); - } - else - { - boundingBox = null; - } - - return boundingBox; - } - - - /** - * Removes a player from the team member map. Generally, you should - * use {@link world.bentobox.bentobox.managers.IslandsManager#removePlayer(World, UUID)} - * @param playerUUID - uuid of player - */ - public void removeMember(UUID playerUUID) { - members.remove(playerUUID); - setChanged(); - } - - /** - * @param center the center to set - */ - public void setCenter(@NonNull Location center) { - this.world = center.getWorld(); - this.center = center; - setChanged(); - } - - /** - * @param createdDate - the createdDate to sets - */ - public void setCreatedDate(long createdDate){ - this.createdDate = createdDate; - setChanged(); - } - - /** - * Set the Island Guard flag rank - * This method affects subflags (if the given flag is a parent flag) - * @param flag - flag - * @param value - Use RanksManager settings, e.g. RanksManager.MEMBER - */ - public void setFlag(Flag flag, int value) { - setFlag(flag, value, true); - } - - /** - * Set the Island Guard flag rank - * Also specify whether subflags are affected by this method call - * @param flag - flag - * @param value - Use RanksManager settings, e.g. RanksManager.MEMBER - * @param doSubflags - whether to set subflags - */ - public void setFlag(Flag flag, int value, boolean doSubflags) { - flags.put(flag.getID(), value); - // Subflag support - if (doSubflags && flag.hasSubflags()) { - // Ensure that a subflag isn't a subflag of itself or else we're in trouble! - flag.getSubflags().forEach(subflag -> setFlag(subflag, value, true)); - } - setChanged(); - } - - /** - * @param flags the flags to set - */ - public void setFlags(Map flags) { - this.flags = flags; - setChanged(); - } - - /** - * Resets the flags to their default as set in config.yml for this island. - * If flags are missing from the config, the default hard-coded value is used and set - */ - public void setFlagsDefaults() { - BentoBox plugin = BentoBox.getInstance(); - Map result = new HashMap<>(); - plugin.getFlagsManager().getFlags().stream(). - filter(f -> f.getType().equals(Flag.Type.PROTECTION)). - forEach(f -> result.put(f.getID(), plugin.getIWM().getDefaultIslandFlags(world).getOrDefault(f, f.getDefaultRank()))); - plugin.getFlagsManager().getFlags().stream(). - filter(f -> f.getType().equals(Flag.Type.SETTING)). - forEach(f -> result.put(f.getID(), plugin.getIWM().getDefaultIslandSettings(world).getOrDefault(f, f.getDefaultRank()))); - this.setFlags(result); - setChanged(); - } - - /** - * @param members the members to set - */ - public void setMembers(Map members) { - this.members = members; - setChanged(); - } - - /** - * Sets the display name of this Island. - *

- * An empty String or {@code null} will remove the display name. - * @param name The display name to set. - */ - public void setName(String name){ - this.name = (name != null && !name.equals("")) ? name : null; - setChanged(); - } - - /** - * Sets the owner of the island. - * @param owner the island owner - the owner to set - */ - public void setOwner(@Nullable UUID owner){ - if (this.owner == owner) { - return; //No need to update anything - } - - this.owner = owner; - if (owner == null) { - log(new LogEntry.Builder("UNOWNED").build()); - return; - } - // Defensive code: demote any previous owner - for (Entry en : members.entrySet()) { - if (en.getValue().equals(RanksManager.OWNER_RANK)) { - setRank(en.getKey(), RanksManager.MEMBER_RANK); - } - } - setRank(owner, RanksManager.OWNER_RANK); - setChanged(); - } - - /** - * @param protectionRange the protectionRange to set - */ - public void setProtectionRange(int protectionRange) { - this.protectionRange = protectionRange; - this.updateMaxEverProtectionRange(); - setChanged(); - } - - /** - * Updates the maxEverProtectionRange based on the current protectionRange - */ - public void updateMaxEverProtectionRange() { - // Ratchet up the maximum protection range - // Distance from maxes - int diffMinX = Math.abs(Objects.requireNonNull(getCenter()).getBlockX() - this.getMinProtectedX()); - int diffMaxX = Math.abs(getCenter().getBlockX() - this.getMaxProtectedX()); - int diffMinZ = Math.abs(getCenter().getBlockZ() - this.getMinProtectedZ()); - int diffMaxZ = Math.abs(getCenter().getBlockZ() - this.getMaxProtectedZ()); - if (diffMinX > this.maxEverProtectionRange) { - this.maxEverProtectionRange = diffMinX; - } - if (diffMaxX > this.maxEverProtectionRange) { - this.maxEverProtectionRange = diffMaxX; - } - if (diffMinZ > this.maxEverProtectionRange) { - this.maxEverProtectionRange = diffMinZ; - } - if (diffMaxZ > this.maxEverProtectionRange) { - this.maxEverProtectionRange = diffMaxZ; - } - - } - - /** - * @param purgeProtected - if the island is protected from the Purge - */ - public void setPurgeProtected(boolean purgeProtected){ - this.purgeProtected = purgeProtected; - setChanged(); - } - - /** - * Sets the island range. - * This method should NEVER be used except for testing purposes. - *
- * The range value is a copy of {@link WorldSettings#getIslandDistance()} made when the Island - * got created in order to allow easier access to this value and must therefore remain - * AS IS. - * @param range the range to set - * @see #setProtectionRange(int) - */ - public void setRange(int range){ - this.range = range; - setChanged(); - } - - /** - * Set user's rank to an arbitrary rank value - * @param user the User - * @param rank rank value - */ - public void setRank(User user, int rank) { - setRank(user.getUniqueId(), rank); - setChanged(); - } - - /** - * Sets player's rank to an arbitrary rank value. - * Calling this method won't call the {@link world.bentobox.bentobox.api.events.island.IslandRankChangeEvent}. - * @param uuid UUID of the player - * @param rank rank value - * @since 1.1 - */ - public void setRank(@Nullable UUID uuid, int rank) { - if (uuid == null) { - return; // Defensive code - } - members.put(uuid, rank); - setChanged(); - } - - /** - * @param ranks the ranks to set - */ - public void setRanks(Map ranks) { - members = ranks; - setChanged(); - } - - /** - * Sets whether this island is a spawn or not. - *
- * If {@code true}, the members and the owner will be removed from this island. - * The flags will also be reset to default values. - * @param isSpawn {@code true} if the island is a spawn, {@code false} otherwise. - */ - public void setSpawn(boolean isSpawn){ - if (spawn == isSpawn) { - return; // No need to update anything - } - - spawn = isSpawn; - if (isSpawn) { - setOwner(null); - members.clear(); - setFlagsDefaults(); - setFlag(Flags.LOCK, RanksManager.VISITOR_RANK); - } - log(new LogEntry.Builder("SPAWN").data("value", String.valueOf(isSpawn)).build()); - setChanged(); - } - - /** - * Get the default spawn location for this island. Note that this may only be valid - * after the initial pasting because the player can change the island after that point - * @return the spawnPoint - */ - public Map getSpawnPoint() { - return spawnPoint; - } - - /** - * Set when island is pasted - * @param spawnPoint the spawnPoint to set - */ - public void setSpawnPoint(Map spawnPoint) { - this.spawnPoint = spawnPoint; - setChanged(); - } - - @Override - public void setUniqueId(String uniqueId) { - this.uniqueId = uniqueId; - setChanged(); - } - - /** - * @param updatedDate - the updatedDate to sets - */ - public void setUpdatedDate(long updatedDate){ - this.updatedDate = updatedDate; - setChanged(); - } - - /** - * @param world the world to set - */ - public void setWorld(World world) { - this.world = world; - setChanged(); - } - - /** - * Toggles a settings flag - * This method affects subflags (if the given flag is a parent flag) - * @param flag - flag - */ - public void toggleFlag(Flag flag) { - toggleFlag(flag, true); - } - - /** - * Toggles a settings flag - * Also specify whether subflags are affected by this method call - * @param flag - flag - */ - public void toggleFlag(Flag flag, boolean doSubflags) { - boolean newToggleValue = !isAllowed(flag); // Use for subflags - if (flag.getType().equals(Flag.Type.SETTING) || flag.getType().equals(Flag.Type.WORLD_SETTING)) { - setSettingsFlag(flag, newToggleValue, doSubflags); - } - setChanged(); - } - - /** - * Sets the state of a settings flag - * This method affects subflags (if the given flag is a parent flag) - * @param flag - flag - * @param state - true or false - */ - public void setSettingsFlag(Flag flag, boolean state) { - setSettingsFlag(flag, state, true); - } - - /** - * Sets the state of a settings flag - * Also specify whether subflags are affected by this method call - * @param flag - flag - * @param state - true or false - */ - public void setSettingsFlag(Flag flag, boolean state, boolean doSubflags) { - int newState = state ? 1 : -1; - if (flag.getType().equals(Flag.Type.SETTING) || flag.getType().equals(Flag.Type.WORLD_SETTING)) { - flags.put(flag.getID(), newState); - if (doSubflags && flag.hasSubflags()) { - // If we have circular subflags or a flag is a subflag of itself we are in trouble! - flag.getSubflags().forEach(subflag -> setSettingsFlag(subflag, state, true)); - } - } - setChanged(); - } - - /** - * Set the spawn location for this island type - * @param islandType - island type - * @param l - location - */ - public void setSpawnPoint(Environment islandType, Location l) { - spawnPoint.put(islandType, l); - setChanged(); - } - - /** - * Get the spawn point for this island type - * @param islandType - island type - * @return - location or null if one does not exist - */ - @Nullable - public Location getSpawnPoint(Environment islandType) { - return spawnPoint.get(islandType); - } - - /** - * Removes all of a specified rank from the member list - * @param rank rank value - */ - public void removeRank(Integer rank) { - members.values().removeIf(rank::equals); - setChanged(); - } - - /** - * Gets the history of the island. - * @return the list of {@link LogEntry} for this island. - */ - public List getHistory() { - return history; - } - - /** - * Adds a {@link LogEntry} to the history of this island. - * @param logEntry the LogEntry to add. - */ - public void log(LogEntry logEntry) { - history.add(logEntry); - setChanged(); - } - - /** - * Sets the history of the island. - * @param history the list of {@link LogEntry} to set for this island. - */ - public void setHistory(List history) { - this.history = history; - setChanged(); - } - - /** - * @return the doNotLoad - */ - public boolean isDoNotLoad() { - return doNotLoad; - } - - /** - * @param doNotLoad the doNotLoad to set - */ - public void setDoNotLoad(boolean doNotLoad) { - this.doNotLoad = doNotLoad; - setChanged(); - } - - /** - * @return the deleted - */ - public boolean isDeleted() { - return deleted; - } - - /** - * @param deleted the deleted to set - */ - public void setDeleted(boolean deleted) { - this.deleted = deleted; - setChanged(); - } - - /** - * Returns the name of the {@link world.bentobox.bentobox.api.addons.GameModeAddon GameModeAddon} this island is handled by. - * @return the name of the {@link world.bentobox.bentobox.api.addons.GameModeAddon GameModeAddon} this island is handled by. - * @since 1.5.0 - */ - public String getGameMode() { - return gameMode; - } - - /** - * Sets the name of the {@link world.bentobox.bentobox.api.addons.GameModeAddon GameModeAddon} this island is handled by. - * Note this has no effect over the actual location of the island, however this may cause issues with addons using this data. - * @since 1.5.0 - */ - public void setGameMode(String gameMode) { - this.gameMode = gameMode; - setChanged(); - } - - /** - * Checks whether this island has its nether island generated or not. - * @return {@code true} if this island has its nether island generated, {@code false} otherwise. - * @since 1.5.0 - */ - public boolean hasNetherIsland() { - World nether = BentoBox.getInstance().getIWM().getNetherWorld(getWorld()); - return nether != null && !getCenter().toVector().toLocation(nether).getBlock().getType().isAir(); - } - - /** - * Checks whether this island has its nether island mode enabled or not. - * @return {@code true} if this island has its nether island enabled, {@code false} otherwise. - * @since 1.21.0 - */ - public boolean isNetherIslandEnabled() { - return this.getPlugin().getIWM().isNetherGenerate(this.world) && this.getPlugin().getIWM().isNetherIslands(this.world); - } - - /** - * Checks whether this island has its end island generated or not. - * @return {@code true} if this island has its end island generated, {@code false} otherwise. - * @since 1.5.0 - */ - public boolean hasEndIsland() { - World end = BentoBox.getInstance().getIWM().getEndWorld(getWorld()); - return end != null && !getCenter().toVector().toLocation(end).getBlock().getType().isAir(); - } - - - /** - * Checks whether this island has its end island mode enabled or not. - * @return {@code true} if this island has its end island enabled, {@code false} otherwise. - * @since 1.21.0 - */ - public boolean isEndIslandEnabled() { - return this.getPlugin().getIWM().isEndGenerate(this.world) && this.getPlugin().getIWM().isEndIslands(this.world); - } - - - /** - * Checks if a flag is on cooldown. Only stored in memory so a server restart will reset the cooldown. - * @param flag - flag - * @return true if on cooldown, false if not - * @since 1.6.0 - */ - public boolean isCooldown(Flag flag) { - if (cooldowns.containsKey(flag.getID()) && cooldowns.get(flag.getID()) > System.currentTimeMillis()) { - return true; - } - cooldowns.remove(flag.getID()); - setChanged(); - return false; - } - - /** - * Sets a cooldown for this flag on this island. - * @param flag - Flag to cooldown - */ - public void setCooldown(Flag flag) { - cooldowns.put(flag.getID(), flag.getCooldown() * 1000L + System.currentTimeMillis()); - setChanged(); - } - - /** - * @return the cooldowns - */ - public Map getCooldowns() { - return cooldowns; - } - - /** - * @param cooldowns the cooldowns to set - */ - public void setCooldowns(Map cooldowns) { - this.cooldowns = cooldowns; - setChanged(); - } - - /** - * @return the commandRanks - */ - public Map getCommandRanks() { - return commandRanks; - } - - /** - * @param commandRanks the commandRanks to set - */ - public void setCommandRanks(Map commandRanks) { - this.commandRanks = commandRanks; - setChanged(); - } - - /** - * Get the rank required to run command on this island. - * The command must have been registered with a rank. - * @param command - the string given by {@link CompositeCommand#getUsage()} - * @return Rank value required, or if command is not set {@link CompositeCommand#getDefaultCommandRank()} - */ - public int getRankCommand(String command) { - - if (this.commandRanks == null) { - this.commandRanks = new HashMap<>(); - } - - // Return or calculate default rank for a command. - return this.commandRanks.computeIfAbsent(command, key -> { - - // Need to find default value for the command. - String[] labels = key.replaceFirst("/", "").split(" "); - - // Get first command label. - CompositeCommand compositeCommand = this.getPlugin().getCommandsManager().getCommand(labels[0]); - - for (int i = 1; i < labels.length && compositeCommand != null; i++) - { - compositeCommand = compositeCommand.getSubCommand(labels[i]).orElse(null); - } - - // Return default command rank or owner rank, if command does not exist. - return compositeCommand == null ? RanksManager.OWNER_RANK : compositeCommand.getDefaultCommandRank(); - }); - } - - /** - * - * @param command - the string given by {@link CompositeCommand#getUsage()} - * @param rank value as used by {@link RanksManager} - */ - public void setRankCommand(String command, int rank) { - if (this.commandRanks == null) this.commandRanks = new HashMap<>(); - this.commandRanks.put(command, rank); - setChanged(); - } - - /** - * Returns whether this Island is currently reserved or not. - * If {@code true}, this means no blocks, except a bedrock one at the center of the island, exist. - * @return {@code true} if this Island is reserved, {@code false} otherwise. - * @since 1.6.0 - */ - public boolean isReserved() { - return reserved != null && reserved; - } - - /** - * @param reserved the reserved to set - * @since 1.6.0 - */ - public void setReserved(boolean reserved) { - this.reserved = reserved; - setChanged(); - } - - /** - * @return the metaData - * @since 1.15.5 - */ - @Override - public Optional> getMetaData() { - if (metaData == null) { - metaData = new HashMap<>(); - } - return Optional.of(metaData); - } - - /** - * @param metaData the metaData to set - * @since 1.15.4 - */ - @Override - public void setMetaData(Map metaData) { - this.metaData = metaData; - setChanged(); - } - - /** - * @return changed state - */ - public boolean isChanged() { - return changed; - } - - /** - * Indicates the fields have been changed. Used to optimize saving on shutdown. - */ - public void setChanged() { - this.changed = true; - } - - /** - * @param changed the changed to set - */ - public void setChanged(boolean changed) { - this.changed = changed; - } - - /** - * Get the center location of the protection zone. - * This can be anywhere within the island space and can move. - * Unless explicitly set, it will return the same as {@link #getCenter()}. - * @return a clone of the protection center location - * @since 1.16.0 - */ - @NonNull - public Location getProtectionCenter() { - return location == null ? getCenter() : location.clone(); - } - - /** - * Sets the protection center location of the island within the island space. - * @param location the location to set - * @throws IOException if the location is not in island space - * @since 1.16.0 - */ - public void setProtectionCenter(Location location) throws IOException { - if (!this.inIslandSpace(location)) { - throw new IOException("Location must be in island space"); - } - this.location = location; - this.updateMaxEverProtectionRange(); - setChanged(); - } - - /** - * @return the homes - * @since 1.16.0 - */ - @NonNull - public Map getHomes() { - if (homes == null) { - homes = new HashMap<>(); - } - return homes; - } - - /** - * Get the location of a named home - * @param name home name case insensitive (name is forced to lower case) - * @return the home location or if none found the protection center of the island is returned. - * @since 1.16.0 - */ - @NonNull - public Location getHome(String name) { - Location l = getHomes().get(name.toLowerCase()); - return l == null ? getProtectionCenter() : l; - } - - /** - * @param homes the homes to set - * @since 1.16.0 - */ - public void setHomes(Map homes) { - this.homes = homes; - setChanged(); - } - - /** - * @param name the name of the home - * @since 1.16.0 - */ - public void addHome(String name, Location location) { - getHomes().put(name.toLowerCase(), location); - setChanged(); - } - - /** - * Remove a named home from this island - * @param name - home name to remove - * @return true if home removed successfully - * @since 1.16.0 - */ - public boolean removeHome(String name) { - setChanged(); - return getHomes().remove(name.toLowerCase()) != null; - } - - /** - * Remove all homes from this island except the default home - * @return true if any non-default homes removed - * @since 1.20.0 - */ - public boolean removeHomes() { - setChanged(); - return getHomes().keySet().removeIf(k -> !k.isEmpty()); - } - - /** - * Rename a home - * @param oldName - old name of home - * @param newName - new name of home - * @return true if successful, false if oldName does not exist, already exists - * @since 1.16.0 - */ - public boolean renameHome(String oldName, String newName) { - if (getHomes().containsKey(oldName.toLowerCase()) && !getHomes().containsKey(newName.toLowerCase())) { - this.addHome(newName, this.getHome(oldName)); - this.removeHome(oldName); - return true; - } - return false; - } - - /** - * Get the max homes. You shouldn't access this directly. - * Use {@link world.bentobox.bentobox.managers.IslandsManager#getMaxHomes(Island)} - * @return the maxHomes. If null, then the world default should be used. - * @since 1.16.0 - */ - @Nullable - public Integer getMaxHomes() { - return maxHomes; - } - - /** - * @param maxHomes the maxHomes to set. If null then the world default will be used. - * You shouldn't access this directly. - * Use {@link world.bentobox.bentobox.managers.IslandsManager#setMaxHomes(Island, Integer)} - * @since 1.16.0 - */ - public void setMaxHomes(@Nullable Integer maxHomes) { - this.maxHomes = maxHomes; - setChanged(); - } - - /** - * @return the maxMembers - * @since 1.16.0 - */ - public Map getMaxMembers() { - if (maxMembers == null) { - maxMembers = new HashMap<>(); - } - return maxMembers; - } - - /** - * @param maxMembers the maxMembers to set - * @since 1.16.0 - */ - public void setMaxMembers(Map maxMembers) { - this.maxMembers = maxMembers; - setChanged(); - } - - /** - * Get the maximum number of island members - * @param rank island rank value from {@link RanksManager} - * @return the maxMembers for the rank given - if null then the world default should be used. Negative values = unlimited. - * @since 1.16.0 - */ - @Nullable - public Integer getMaxMembers(int rank) { - return getMaxMembers().get(rank); - } - - - /** - * Set the maximum number of island members - * @param rank island rank value from {@link RanksManager} - * @param maxMembers the maxMembers to set. If null then the world default applies. Negative values = unlimited. - * @since 1.16.0 - */ - public void setMaxMembers(int rank, Integer maxMembers) { - getMaxMembers().put(rank, maxMembers); - } - - - /** - * @return the bonusRanges - */ - public List getBonusRanges() { - if (bonusRanges == null) { - this.setBonusRanges(new ArrayList<>()); - } - return bonusRanges; - } - - /** - * @param bonusRanges the bonusRanges to set - */ - public void setBonusRanges(List bonusRanges) { - this.bonusRanges = bonusRanges; - setChanged(); - } - - /** - * Get the bonus range provided by all settings of the range giver - * @param id an id to identify this bonus - * @return bonus range, or 0 if unknown - */ - public int getBonusRange(String id) { - return this.getBonusRanges().stream().filter(r -> r.getUniqueId().equals(id)).mapToInt(BonusRangeRecord::getRange).sum(); - } - - /** - * Get the BonusRangeRecord for uniqueId - * @param uniqueId a unique id to identify this bonus - * @return optional BonusRangeRecord - */ - public Optional getBonusRangeRecord(String uniqueId) { - return this.getBonusRanges().stream().filter(r -> r.getUniqueId().equals(uniqueId)).findFirst(); - } - - /** - * Add a bonus range amount to the island for this addon or plugin. - * Note, this will not replace any range set already with the same id - * @param id an id to identify this bonus - * @param range range to add to the island protected range - * @param message the reference key to a locale message related to this bonus. May be blank. - */ - public void addBonusRange(String id, int range, String message) { - this.getBonusRanges().add(new BonusRangeRecord(id, range, message)); - setMaxEverProtectionRange(this.getProtectionRange()); - setChanged(); - } - - /** - * Clear the bonus ranges for a unique ID - * @param id id to identify this bonus - */ - public void clearBonusRange(String id) { - this.getBonusRanges().removeIf(r -> r.getUniqueId().equals(id)); - setChanged(); - } - - /** - * Clear all bonus ranges for this island - */ - public void clearAllBonusRanges() { - this.getBonusRanges().clear(); - setChanged(); - } - - /** - * @return the primary - */ - public boolean isPrimary() { - return primary; - } - - /** - * @param primary the primary to set - */ - public void setPrimary(boolean primary) { - this.primary = primary; - setChanged(); - } - - /* (non-Javadoc) - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - return "Island [changed=" + changed + ", deleted=" + deleted + ", uniqueId=" + uniqueId + ", center=" + center - + ", location=" + location + ", range=" + range + ", protectionRange=" + protectionRange - + ", maxEverProtectionRange=" + maxEverProtectionRange + ", world=" + world + ", gameMode=" + gameMode - + ", name=" + name + ", createdDate=" + createdDate + ", updatedDate=" + updatedDate + ", owner=" - + owner + ", members=" + members + ", maxMembers=" + maxMembers + ", spawn=" + spawn - + ", purgeProtected=" + purgeProtected + ", flags=" + flags + ", history=" + history - + ", spawnPoint=" + spawnPoint + ", doNotLoad=" + doNotLoad - + ", cooldowns=" + cooldowns + ", commandRanks=" + commandRanks + ", reserved=" + reserved - + ", metaData=" + metaData + ", homes=" + homes + ", maxHomes=" + maxHomes + "]"; - } + @Expose + private boolean primary; + + /** + * Set to true if this data object has been changed since being loaded from the database + */ + private boolean changed; + + // True if this island is deleted and pending deletion from the database + @Expose + private boolean deleted = false; + + @Expose + @NonNull + private String uniqueId = UUID.randomUUID().toString(); + + //// Island //// + // The center of the island space + @Expose + private Location center; + + /** + * The center location of the protection area + */ + @Expose + @Nullable + private Location location; + + + // Island range + @Expose + private int range; + + // Protection size + @Expose + private int protectionRange; + + /** + * Bonuses to protection range + * @since 1.20.0 + */ + @Expose + private List bonusRanges = new ArrayList<>(); + + // 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; + + /** + * Name of the {@link world.bentobox.bentobox.api.addons.GameModeAddon GameModeAddon} this island is handled by. + * @since 1.5.0 + */ + @Expose + private String gameMode; + + // Display name + @Expose + @Nullable + private String name; + + // Time parameters + @Expose + private long createdDate; + @Expose + private long updatedDate; + + //// Team //// + /** + * Owner of the island. + * There can only be one per island. + * If it is {@code null}, then the island is considered as unowned. + */ + @Expose + @Nullable + private UUID owner; + + /** + * Members of the island. + * It contains any player which has one of the following rank on this island: {@link RanksManager#COOP_RANK COOP}, + * {@link RanksManager#TRUSTED_RANK TRUSTED}, {@link RanksManager#MEMBER_RANK MEMBER}, {@link RanksManager#SUB_OWNER_RANK SUB_OWNER}, + * {@link RanksManager#OWNER_RANK OWNER}. + */ + @Expose + private Map members = new HashMap<>(); + + /** + * Maximum number of members allowed in this island. + * Key is rank, value is number + * @since 1.16.0 + */ + @Expose + private Map maxMembers; + + //// State //// + @Expose + private boolean spawn = false; + @Expose + private boolean purgeProtected = false; + + //// Protection flags //// + @Expose + private Map flags = new HashMap<>(); + + //// Island History //// + @Adapter(LogEntryListAdapter.class) + @Expose + private List history = new LinkedList<>(); + + @Expose + private Map spawnPoint = new EnumMap<>(Environment.class); + + /** + * This flag is used to quarantine islands that cannot be loaded and should be purged at some point + */ + @Expose + private boolean doNotLoad; + + /** + * Used to store flag cooldowns for this island + */ + @Expose + private Map cooldowns = new HashMap<>(); + + /** + * Commands and the rank required to use them for this island + */ + @Expose + private Map commandRanks; + + /** + * If true then this space is reserved for the owner and when they teleport there they will be asked to make an island + * @since 1.6.0 + */ + @Expose + @Nullable + private Boolean reserved = null; + + /** + * A place to store metadata for this island. + * @since 1.15.4 + */ + @Expose + private Map metaData; + + /** + * Island homes. Replaces player homes + * @since 1.16.0 + */ + @Expose + private Map homes; + + /** + * The maximum number of homes allowed on this island. If null, then the world default is used. + */ + @Expose + private Integer maxHomes; + + /* + * *************************** Constructors ****************************** + */ + + public Island() {} + + public Island(@NonNull Location location, UUID owner, int protectionRange) { + setOwner(owner); + createdDate = System.currentTimeMillis(); + updatedDate = System.currentTimeMillis(); + world = location.getWorld(); + // Make a copy of the location + center = new Location(location.getWorld(), location.getX(), location.getY(), location.getZ()); + range = BentoBox.getInstance().getIWM().getIslandDistance(world); + this.protectionRange = protectionRange; + this.maxEverProtectionRange = protectionRange; + this.setChanged(); + } + + + /** + * Clones an island object + * @param island - island to clone + */ + public Island(Island island) { + this.center = island.getCenter().clone(); + this.createdDate = island.getCreatedDate(); + Optional.ofNullable(island.getCommandRanks()).ifPresent(cr -> { + this.commandRanks = new HashMap<>(); + this.commandRanks.putAll(cr); + }); + Optional.ofNullable(island.getCooldowns()).ifPresent(c -> { + this.cooldowns = new HashMap<>(); + this.cooldowns.putAll(c); + }); + this.createdDate = island.getCreatedDate(); + this.deleted = island.isDeleted(); + this.doNotLoad = island.isDoNotLoad(); + this.flags.putAll(island.getFlags()); + this.gameMode = island.getGameMode(); + this.homes = new HashMap<>(island.getHomes()); + this.history.addAll(island.getHistory()); + this.location = island.getProtectionCenter(); + this.maxEverProtectionRange = island.getMaxEverProtectionRange(); + this.maxHomes = island.getMaxHomes(); + this.maxMembers = new HashMap<>(island.getMaxMembers()); + this.members.putAll(island.getMembers()); + island.getMetaData().ifPresent(m -> { + this.metaData = new HashMap<>(); + this.metaData.putAll(m); + }); + this.name = island.getName(); + this.owner = island.getOwner(); + this.protectionRange = island.getProtectionRange(); + this.purgeProtected = island.getPurgeProtected(); + this.range = island.getRange(); + this.reserved = island.isReserved(); + this.spawn = island.isSpawn(); + island.getSpawnPoint().forEach((k,v) -> island.spawnPoint.put(k, v.clone())); + this.uniqueId = island.getUniqueId(); + this.updatedDate = island.getUpdatedDate(); + this.world = island.getWorld(); + this.bonusRanges.addAll(island.getBonusRanges()); + this.setChanged(); + } + + /* + * *************************** Methods ****************************** + */ + + /** + * Adds a team member. If player is on banned list, they will be removed from it. + * @param playerUUID - the player's UUID + */ + public void addMember(@NonNull UUID playerUUID) { + setRank(playerUUID, RanksManager.MEMBER_RANK); + setChanged(); + } + + /** + * Bans the target player from this Island. + * If the player is a member, coop or trustee, they will be removed from those lists. + *
+ * Calling this method won't call the {@link world.bentobox.bentobox.api.events.island.IslandBanEvent}. + * @param issuer UUID of the issuer, may be null. + * Whenever possible, one should be provided. + * @param target UUID of the target, must be provided. + * @return {@code true} + */ + public boolean ban(@NonNull UUID issuer, @NonNull UUID target) { + setRank(target, RanksManager.BANNED_RANK); + log(new LogEntry.Builder("BAN").data("player", target.toString()).data("issuer", issuer.toString()).build()); + setChanged(); + return true; + } + + /** + * @return the banned + */ + public Set getBanned() { + Set result = new HashSet<>(); + for (Entry member: members.entrySet()) { + if (member.getValue() <= RanksManager.BANNED_RANK) { + result.add(member.getKey()); + } + } + return result; + } + + /** + * Unbans the target player from this Island. + *
+ * Calling this method won't call the {@link world.bentobox.bentobox.api.events.island.IslandUnbanEvent}. + * @param issuer UUID of the issuer, may be null. + * Whenever possible, one should be provided. + * @param target UUID of the target, must be provided. + * @return {@code true} if the target is successfully unbanned, {@code false} otherwise. + */ + public boolean unban(@NonNull UUID issuer, @NonNull UUID target) { + if (members.remove(target) != null) { + log(new LogEntry.Builder("UNBAN").data("player", target.toString()).data("issuer", issuer.toString()).build()); + return true; + } + return false; + } + + /** + * Returns a clone of the location of the center of this island. + * @return clone of the center Location + */ + @NonNull + public Location getCenter(){ + return Objects.requireNonNull(center, "Island getCenter requires a non-null center").clone(); + } + + /** + * @return the date when the island was created + */ + public long getCreatedDate(){ + return createdDate; + } + + /** + * Gets the Island Guard flag's setting. If this is a protection flag, then this will be the + * rank needed to bypass this flag. If it is a Settings flag, any non-zero value means the + * setting is allowed. + * @param flag - flag + * @return flag value + */ + public int getFlag(@NonNull Flag flag) { + return flags.computeIfAbsent(flag.getID(), k -> flag.getDefaultRank()); + } + + /** + * @return the flags + */ + public Map getFlags() { + return flags; + } + + /** + * Returns the members of this island. + * It contains all players that have any rank on this island, including {@link RanksManager#BANNED_RANK BANNED}, + * {@link RanksManager#TRUSTED_RANK TRUSTED}, {@link RanksManager#MEMBER_RANK MEMBER}, {@link RanksManager#SUB_OWNER_RANK SUB_OWNER}, + * {@link RanksManager#OWNER_RANK OWNER}, etc. + * + * @return the members - key is the UUID, value is the RanksManager enum, e.g. {@link RanksManager#MEMBER_RANK}. + * @see #getMemberSet() + */ + public Map getMembers() { + return members; + } + + /** + * Returns an immutable set containing the UUIDs of players that are truly members of this island. + * This includes any player which has one of the following rank on this island: {@link RanksManager#MEMBER_RANK MEMBER}, + * {@link RanksManager#SUB_OWNER_RANK SUB_OWNER}, {@link RanksManager#OWNER_RANK OWNER}. + * @return the members of the island (owner included) + * @see #getMembers() + */ + public ImmutableSet getMemberSet(){ + return getMemberSet(RanksManager.MEMBER_RANK); + } + + /** + * Returns an immutable set containing the UUIDs of players with rank above that requested rank inclusive + * @param minimumRank minimum rank (inclusive) of members + * @return immutable set of UUIDs + * @see #getMembers() + * @since 1.5.0 + */ + public @NonNull ImmutableSet getMemberSet(int minimumRank) { + Builder result = new ImmutableSet.Builder<>(); + members.entrySet().stream().filter(e -> e.getValue() >= minimumRank).map(Map.Entry::getKey).forEach(result::add); + return result.build(); + } + + /** + * Returns an immutable set containing the UUIDs of players with rank equal or above that requested rank (inclusive). + * @param rank rank to request + * @param includeAboveRanks whether including players with rank above the requested rank or not + * @return immutable set of UUIDs + * @see #getMemberSet(int) + * @see #getMembers() + * @since 1.5.0 + */ + public @NonNull ImmutableSet getMemberSet(int rank, boolean includeAboveRanks) { + if (includeAboveRanks) { + return getMemberSet(rank); + } + Builder result = new ImmutableSet.Builder<>(); + members.entrySet().stream().filter(e -> e.getValue() == rank).map(Map.Entry::getKey).forEach(result::add); + return result.build(); + } + + /** + * Get the minimum protected X block coordinate based on the island location. + * It will never be less than {@link #getMinX()} + * @return the minProtectedX + */ + public int getMinProtectedX() { + return Math.max(getMinX(), getProtectionCenter().getBlockX() - protectionRange); + } + + /** + * Get the maximum protected X block coordinate based on the island location. + * It will never be more than {@link #getMaxX()} + * @return the maxProtectedX + * @since 1.5.2 + */ + public int getMaxProtectedX() { + return Math.min(getMaxX(), getProtectionCenter().getBlockX() + protectionRange); + } + + /** + * Get the minimum protected Z block coordinate based on the island location. + * It will never be less than {@link #getMinZ()} + * @return the minProtectedZ + */ + public int getMinProtectedZ() { + return Math.max(getMinZ(), getProtectionCenter().getBlockZ() - protectionRange); + } + + /** + * Get the maximum protected Z block coordinate based on the island location. + * It will never be more than {@link #getMinZ()} + * @return the maxProtectedZ + * @since 1.5.2 + */ + public int getMaxProtectedZ() { + return Math.min(getMaxZ(), getProtectionCenter().getBlockZ() + protectionRange); + } + + /** + * @return the minX + */ + public int getMinX() { + return center.getBlockX() - range; + } + + /** + * @return the maxX + * @since 1.5.2 + */ + public int getMaxX() { + return center.getBlockX() + range; + } + + /** + * @return the minZ + */ + public int getMinZ() { + return center.getBlockZ() - range; + } + + /** + * @return the maxZ + * @since 1.5.2 + */ + public int getMaxZ() { + return center.getBlockZ() + range; + } + + /** + * @return the island display name. Might be {@code null} if none is set. + */ + @Nullable + public String getName() { + return name; + } + + /** + * Returns the owner of this island. + * @return the owner, may be null. + * @see #isOwned() + * @see #isUnowned() + */ + @Nullable + public UUID getOwner(){ + return owner; + } + + /** + * Returns whether this island is owned or not. + * @return {@code true} if this island has an owner, {@code false} otherwise. + * @since 1.9.1 + * @see #getOwner() + * @see #isUnowned() + */ + public boolean isOwned() { + return owner != null; + } + + /** + * Returns whether this island does not have an owner. + * @return {@code true} if this island does not have an owner, {@code false} otherwise. + * @since 1.9.1 + * @see #getOwner() + * @see #isOwned() + */ + public boolean isUnowned() { + return owner == null; + } + + /** + * Returns the protection range of this Island plus any bonuses. Will not be greater than getRange(). + * This represents half of the length of the side of a theoretical square around the island center inside which flags are enforced. + * @return the protection range of this island, strictly positive integer. + * @see #getRange() + */ + public int getProtectionRange() { + return Math.min(this.getRange(), getRawProtectionRange() + this.getBonusRanges().stream().mapToInt(BonusRangeRecord::getRange).sum()); + } + + /** + * Returns the protection range of this Island without any bonuses + * This represents half of the length of the side of a theoretical square around the island center inside which flags are enforced. + * @return the protection range of this island, strictly positive integer. + * @since 1.20.0 + */ + public int getRawProtectionRange() { + return protectionRange; + } + + /** + * @return the maxEverProtectionRange or the protection range, whichever is larger + */ + public int getMaxEverProtectionRange() { + if (maxEverProtectionRange > this.getRange()) { + maxEverProtectionRange = this.getRange(); + setChanged(); + } + return Math.max(this.getProtectionRange(), maxEverProtectionRange); + } + + /** + * Sets the maximum protection range. This can be used to optimize + * island deletion. Setting this values to a lower value than the current value + * will have no effect. + * @param maxEverProtectionRange the maxEverProtectionRange to set + */ + public void setMaxEverProtectionRange(int maxEverProtectionRange) { + if (maxEverProtectionRange > this.maxEverProtectionRange) { + this.maxEverProtectionRange = maxEverProtectionRange; + } + if (maxEverProtectionRange > this.range) { + this.maxEverProtectionRange = this.range; + } + setChanged(); + } + + /** + * @return true if the island is protected from the Purge, otherwise false + */ + public boolean getPurgeProtected(){ + return purgeProtected; + } + + /** + * Returns the island range. + * It is a convenience method that returns the exact same value than island range, although it has been saved into the Island object for easier access. + * @return the island range + * @see #getProtectionRange() + */ + public int getRange(){ + return range; + } + + /** + * Get the rank of user for this island + * @param user - the User + * @return rank integer + */ + public int getRank(User user) { + if (user.isOp()) { + return RanksManager.ADMIN_RANK; + } + return members.getOrDefault(user.getUniqueId(), RanksManager.VISITOR_RANK); + } + + /** + * Get the rank of user for this island + * @param userUUID - the User's UUID + * @return rank integer + * @since 1.14.0 + */ + public int getRank(UUID userUUID) { + return members.getOrDefault(userUUID, RanksManager.VISITOR_RANK); + } + + @Override + public @NonNull String getUniqueId() { + return uniqueId; + } + + /** + * @return the date when the island was updated (team member connection, etc...) + */ + public long getUpdatedDate(){ + return updatedDate; + } + + /** + * @return the world + */ + public World getWorld() { + return world; + } + + + /** + * @return the nether world + */ + @Nullable + public World getNetherWorld() + { + return this.getWorld(Environment.NETHER); + } + + + /** + * @return the end world + */ + @Nullable + public World getEndWorld() + { + return this.getWorld(Environment.THE_END); + } + + + /** + * This method returns this island world in given environment. This method can return {@code null} if dimension is + * disabled. + * @param environment The environment of the island world. + * @return the world in given environment. + */ + @Nullable + public World getWorld(Environment environment) + { + if (Environment.NORMAL.equals(environment)) + { + return this.world; + } + else if (Environment.THE_END.equals(environment) && this.isEndIslandEnabled()) + { + return this.getPlugin().getIWM().getEndWorld(this.world); + } + else if (Environment.NETHER.equals(environment) && this.isNetherIslandEnabled()) + { + return this.getPlugin().getIWM().getNetherWorld(this.world); + } + else + { + return null; + } + } + + + /** + * @return the x coordinate of the island center + */ + public int getX(){ + return center.getBlockX(); + } + + /** + * @return the y coordinate of the island center + */ + public int getY(){ + return center.getBlockY(); + } + + /** + * @return the z coordinate of the island center + */ + public int getZ(){ + return center.getBlockZ(); + } + + /** + * Checks if coords are in the island space + * @param x - x coordinate + * @param z - z coordinate + * @return true if in the island space + */ + public boolean inIslandSpace(int x, int z) { + return x >= getMinX() && x < getMinX() + range*2 && z >= getMinZ() && z < getMinZ() + range*2; + } + + /** + * Checks if location is in full island space, not just protected space + * @param location - location + * @return true if in island space + */ + public boolean inIslandSpace(Location location) { + return Util.sameWorld(this.world, location.getWorld()) && + (location.getWorld().getEnvironment().equals(Environment.NORMAL) || + this.getPlugin().getIWM().isIslandNether(location.getWorld()) || + this.getPlugin().getIWM().isIslandEnd(location.getWorld())) && + this.inIslandSpace(location.getBlockX(), location.getBlockZ()); + } + + /** + * Checks if the coordinates are in full island space, not just protected space + * @param blockCoordinates - Pair(x,z) coordinates of block + * @return true or false + */ + public boolean inIslandSpace(Pair blockCoordinates) { + return inIslandSpace(blockCoordinates.x, blockCoordinates.z); + } + + /** + * Returns a {@link BoundingBox} of the full island space for overworld. + * @return a {@link BoundingBox} of the full island space. + * @since 1.5.2 + */ + @NonNull + public BoundingBox getBoundingBox() { + return this.getBoundingBox(Environment.NORMAL); + } + + + /** + * Returns a {@link BoundingBox} of this island's space area in requested dimension. + * @param environment the requested dimension. + * @return a {@link BoundingBox} of this island's space area or {@code null} if island is not created in requested dimension. + * @since 1.21.0 + */ + @Nullable + public BoundingBox getBoundingBox(Environment environment) + { + BoundingBox boundingBox; + + if (Environment.NORMAL.equals(environment)) + { + // Return normal world bounding box. + boundingBox = new BoundingBox(this.getMinX(), + this.world.getMinHeight(), + this.getMinZ(), + this.getMaxX(), + this.world.getMaxHeight(), + this.getMaxZ()); + } + else if (Environment.THE_END.equals(environment) && this.isEndIslandEnabled()) + { + // If end world is generated, return end island bounding box. + boundingBox = new BoundingBox(this.getMinX(), + this.getEndWorld().getMinHeight(), + this.getMinZ(), + this.getMaxX(), + this.getEndWorld().getMaxHeight(), + this.getMaxZ()); + } + else if (Environment.NETHER.equals(environment) && this.isNetherIslandEnabled()) + { + // If nether world is generated, return nether island bounding box. + boundingBox = new BoundingBox(this.getMinX(), + this.getNetherWorld().getMinHeight(), + this.getMinZ(), + this.getMaxX(), + this.getNetherWorld().getMaxHeight(), + this.getMaxZ()); + } + else + { + boundingBox = null; + } + + return boundingBox; + } + + + /** + * Using this method in the filtering for getVisitors and hasVisitors + * @param player The player that must be checked. + * @return true if player is a visitor + */ + private boolean playerIsVisitor(Player player) { + if (player.getGameMode() == GameMode.SPECTATOR) { + return false; + } + + return onIsland(player.getLocation()) && getRank(User.getInstance(player)) == RanksManager.VISITOR_RANK; + } + + /** + * Returns a list of players that are physically inside the island's protection range and that are visitors. + * @return list of visitors + * @since 1.3.0 + */ + @NonNull + public List getVisitors() { + return Bukkit.getOnlinePlayers().stream().filter(this::playerIsVisitor).collect(Collectors.toList()); + } + + /** + * Returns whether this Island has visitors inside its protection range. + * Note this is equivalent to {@code !island.getVisitors().isEmpty()}. + * @return {@code true} if there are visitors inside this Island's protection range, {@code false} otherwise. + * + * @since 1.3.0 + * @see #getVisitors() + */ + public boolean hasVisitors() { + return Bukkit.getOnlinePlayers().stream().anyMatch(this::playerIsVisitor); + } + + /** + * Returns a list of players that are physically inside the island's protection range + * @return list of players + * @since 1.6.0 + */ + @NonNull + public List getPlayersOnIsland() { + return Bukkit.getOnlinePlayers().stream() + .filter(player -> onIsland(player.getLocation())) + .collect(Collectors.toList()); + } + + /** + * Returns whether this Island has players inside its protection range. + * Note this is equivalent to {@code !island.getPlayersOnIsland().isEmpty()}. + * @return {@code true} if there are players inside this Island's protection range, {@code false} otherwise. + * + * @since 1.6.0 + * @see #getPlayersOnIsland() + */ + public boolean hasPlayersOnIsland() { + return Bukkit.getOnlinePlayers().stream().anyMatch(player -> onIsland(player.getLocation())); + } + + /** + * Check if the flag is allowed or not + * For flags that are for the island in general and not related to rank. + * @param flag - flag + * @return true if allowed, false if not + */ + public boolean isAllowed(Flag flag) { + // A negative value means not allowed + return getFlag(flag) >= 0; + } + + /** + * Check if a user is allowed to bypass the flag or not + * @param user - the User - user + * @param flag - flag + * @return true if allowed, false if not + */ + public boolean isAllowed(User user, Flag flag) { + return getRank(user) >= getFlag(flag); + } + + /** + * Check if banned + * @param targetUUID - the target's UUID + * @return Returns true if target is banned on this island + */ + public boolean isBanned(UUID targetUUID) { + return members.containsKey(targetUUID) && members.get(targetUUID).equals(RanksManager.BANNED_RANK); + } + + /** + * Returns whether the island is a spawn or not. + * @return {@code true} if the island is a spawn, {@code false} otherwise. + */ + public boolean isSpawn() { + return spawn; + } + + /** + * Checks if a location is within this island's protected area. + * + * @param target location to check, not null + * @return {@code true} if this location is within this island's protected area, {@code false} otherwise. + */ + public boolean onIsland(@NonNull Location target) { + return Util.sameWorld(this.world, target.getWorld()) && + (target.getWorld().getEnvironment().equals(Environment.NORMAL) || + this.getPlugin().getIWM().isIslandNether(target.getWorld()) || + this.getPlugin().getIWM().isIslandEnd(target.getWorld())) && + target.getBlockX() >= this.getMinProtectedX() && + target.getBlockX() < (this.getMinProtectedX() + this.protectionRange * 2) && + target.getBlockZ() >= this.getMinProtectedZ() && + target.getBlockZ() < (this.getMinProtectedZ() + this.protectionRange * 2); + } + + /** + * Returns a {@link BoundingBox} of this island's protected area for overworld. + * @return a {@link BoundingBox} of this island's protected area. + * @since 1.5.2 + */ + @NonNull + public BoundingBox getProtectionBoundingBox() { + return this.getProtectionBoundingBox(Environment.NORMAL); + } + + + /** + * Returns a {@link BoundingBox} of this island's protected area. + * @param environment an environment of bounding box area. + * @return a {@link BoundingBox} of this island's protected area or {@code null} if island is not created in required dimension. + * in required dimension. + * @since 1.21.0 + */ + @Nullable + public BoundingBox getProtectionBoundingBox(Environment environment) + { + BoundingBox boundingBox; + + if (Environment.NORMAL.equals(environment)) + { + // Return normal world bounding box. + boundingBox = new BoundingBox(this.getMinProtectedX(), + this.world.getMinHeight(), + this.getMinProtectedZ(), + this.getMaxProtectedX(), + this.world.getMaxHeight(), + this.getMaxProtectedZ()); + } + else if (Environment.THE_END.equals(environment) && this.isEndIslandEnabled()) + { + // If end world is generated, return end island bounding box. + boundingBox = new BoundingBox(this.getMinProtectedX(), + this.getEndWorld().getMinHeight(), + this.getMinProtectedZ(), + this.getMaxProtectedX(), + this.getEndWorld().getMaxHeight(), + this.getMaxProtectedZ()); + } + else if (Environment.NETHER.equals(environment) && this.isNetherIslandEnabled()) + { + // If nether world is generated, return nether island bounding box. + boundingBox = new BoundingBox(this.getMinProtectedX(), + this.getNetherWorld().getMinHeight(), + this.getMinProtectedZ(), + this.getMaxProtectedX(), + this.getNetherWorld().getMaxHeight(), + this.getMaxProtectedZ()); + } + else + { + boundingBox = null; + } + + return boundingBox; + } + + + /** + * Removes a player from the team member map. Generally, you should + * use {@link world.bentobox.bentobox.managers.IslandsManager#removePlayer(World, UUID)} + * @param playerUUID - uuid of player + */ + public void removeMember(UUID playerUUID) { + members.remove(playerUUID); + setChanged(); + } + + /** + * @param center the center to set + */ + public void setCenter(@NonNull Location center) { + this.world = center.getWorld(); + this.center = center; + setChanged(); + } + + /** + * @param createdDate - the createdDate to sets + */ + public void setCreatedDate(long createdDate){ + this.createdDate = createdDate; + setChanged(); + } + + /** + * Set the Island Guard flag rank + * This method affects subflags (if the given flag is a parent flag) + * @param flag - flag + * @param value - Use RanksManager settings, e.g. RanksManager.MEMBER + */ + public void setFlag(Flag flag, int value) { + setFlag(flag, value, true); + } + + /** + * Set the Island Guard flag rank + * Also specify whether subflags are affected by this method call + * @param flag - flag + * @param value - Use RanksManager settings, e.g. RanksManager.MEMBER + * @param doSubflags - whether to set subflags + */ + public void setFlag(Flag flag, int value, boolean doSubflags) { + flags.put(flag.getID(), value); + // Subflag support + if (doSubflags && flag.hasSubflags()) { + // Ensure that a subflag isn't a subflag of itself or else we're in trouble! + flag.getSubflags().forEach(subflag -> setFlag(subflag, value, true)); + } + setChanged(); + } + + /** + * @param flags the flags to set + */ + public void setFlags(Map flags) { + this.flags = flags; + setChanged(); + } + + /** + * Resets the flags to their default as set in config.yml for this island. + * If flags are missing from the config, the default hard-coded value is used and set + */ + public void setFlagsDefaults() { + BentoBox plugin = BentoBox.getInstance(); + Map result = new HashMap<>(); + plugin.getFlagsManager().getFlags().stream(). + filter(f -> f.getType().equals(Flag.Type.PROTECTION)). + forEach(f -> result.put(f.getID(), plugin.getIWM().getDefaultIslandFlags(world).getOrDefault(f, f.getDefaultRank()))); + plugin.getFlagsManager().getFlags().stream(). + filter(f -> f.getType().equals(Flag.Type.SETTING)). + forEach(f -> result.put(f.getID(), plugin.getIWM().getDefaultIslandSettings(world).getOrDefault(f, f.getDefaultRank()))); + this.setFlags(result); + setChanged(); + } + + /** + * @param members the members to set + */ + public void setMembers(Map members) { + this.members = members; + setChanged(); + } + + /** + * Sets the display name of this Island. + *

+ * An empty String or {@code null} will remove the display name. + * @param name The display name to set. + */ + public void setName(String name){ + this.name = (name != null && !name.equals("")) ? name : null; + setChanged(); + } + + /** + * Sets the owner of the island. + * @param owner the island owner - the owner to set + */ + public void setOwner(@Nullable UUID owner){ + if (this.owner == owner) { + return; //No need to update anything + } + + this.owner = owner; + if (owner == null) { + log(new LogEntry.Builder("UNOWNED").build()); + return; + } + // Defensive code: demote any previous owner + for (Entry en : members.entrySet()) { + if (en.getValue().equals(RanksManager.OWNER_RANK)) { + setRank(en.getKey(), RanksManager.MEMBER_RANK); + } + } + setRank(owner, RanksManager.OWNER_RANK); + setChanged(); + } + + /** + * @param protectionRange the protectionRange to set + */ + public void setProtectionRange(int protectionRange) { + this.protectionRange = protectionRange; + this.updateMaxEverProtectionRange(); + setChanged(); + } + + /** + * Updates the maxEverProtectionRange based on the current protectionRange + */ + public void updateMaxEverProtectionRange() { + // Ratchet up the maximum protection range + // Distance from maxes + int diffMinX = Math.abs(Objects.requireNonNull(getCenter()).getBlockX() - this.getMinProtectedX()); + int diffMaxX = Math.abs(getCenter().getBlockX() - this.getMaxProtectedX()); + int diffMinZ = Math.abs(getCenter().getBlockZ() - this.getMinProtectedZ()); + int diffMaxZ = Math.abs(getCenter().getBlockZ() - this.getMaxProtectedZ()); + if (diffMinX > this.maxEverProtectionRange) { + this.maxEverProtectionRange = diffMinX; + } + if (diffMaxX > this.maxEverProtectionRange) { + this.maxEverProtectionRange = diffMaxX; + } + if (diffMinZ > this.maxEverProtectionRange) { + this.maxEverProtectionRange = diffMinZ; + } + if (diffMaxZ > this.maxEverProtectionRange) { + this.maxEverProtectionRange = diffMaxZ; + } + + } + + /** + * @param purgeProtected - if the island is protected from the Purge + */ + public void setPurgeProtected(boolean purgeProtected){ + this.purgeProtected = purgeProtected; + setChanged(); + } + + /** + * Sets the island range. + * This method should NEVER be used except for testing purposes. + *
+ * The range value is a copy of {@link WorldSettings#getIslandDistance()} made when the Island + * got created in order to allow easier access to this value and must therefore remain + * AS IS. + * @param range the range to set + * @see #setProtectionRange(int) + */ + public void setRange(int range){ + this.range = range; + setChanged(); + } + + /** + * Set user's rank to an arbitrary rank value + * @param user the User + * @param rank rank value + */ + public void setRank(User user, int rank) { + setRank(user.getUniqueId(), rank); + setChanged(); + } + + /** + * Sets player's rank to an arbitrary rank value. + * Calling this method won't call the {@link world.bentobox.bentobox.api.events.island.IslandRankChangeEvent}. + * @param uuid UUID of the player + * @param rank rank value + * @since 1.1 + */ + public void setRank(@Nullable UUID uuid, int rank) { + if (uuid == null) { + return; // Defensive code + } + members.put(uuid, rank); + setChanged(); + } + + /** + * @param ranks the ranks to set + */ + public void setRanks(Map ranks) { + members = ranks; + setChanged(); + } + + /** + * Sets whether this island is a spawn or not. + *
+ * If {@code true}, the members and the owner will be removed from this island. + * The flags will also be reset to default values. + * @param isSpawn {@code true} if the island is a spawn, {@code false} otherwise. + */ + public void setSpawn(boolean isSpawn){ + if (spawn == isSpawn) { + return; // No need to update anything + } + + spawn = isSpawn; + if (isSpawn) { + setOwner(null); + members.clear(); + setFlagsDefaults(); + setFlag(Flags.LOCK, RanksManager.VISITOR_RANK); + } + log(new LogEntry.Builder("SPAWN").data("value", String.valueOf(isSpawn)).build()); + setChanged(); + } + + /** + * Get the default spawn location for this island. Note that this may only be valid + * after the initial pasting because the player can change the island after that point + * @return the spawnPoint + */ + public Map getSpawnPoint() { + return spawnPoint; + } + + /** + * Set when island is pasted + * @param spawnPoint the spawnPoint to set + */ + public void setSpawnPoint(Map spawnPoint) { + this.spawnPoint = spawnPoint; + setChanged(); + } + + @Override + public void setUniqueId(String uniqueId) { + this.uniqueId = uniqueId; + setChanged(); + } + + /** + * @param updatedDate - the updatedDate to sets + */ + public void setUpdatedDate(long updatedDate){ + this.updatedDate = updatedDate; + setChanged(); + } + + /** + * @param world the world to set + */ + public void setWorld(World world) { + this.world = world; + setChanged(); + } + + /** + * Toggles a settings flag + * This method affects subflags (if the given flag is a parent flag) + * @param flag - flag + */ + public void toggleFlag(Flag flag) { + toggleFlag(flag, true); + } + + /** + * Toggles a settings flag + * Also specify whether subflags are affected by this method call + * @param flag - flag + */ + public void toggleFlag(Flag flag, boolean doSubflags) { + boolean newToggleValue = !isAllowed(flag); // Use for subflags + if (flag.getType().equals(Flag.Type.SETTING) || flag.getType().equals(Flag.Type.WORLD_SETTING)) { + setSettingsFlag(flag, newToggleValue, doSubflags); + } + setChanged(); + } + + /** + * Sets the state of a settings flag + * This method affects subflags (if the given flag is a parent flag) + * @param flag - flag + * @param state - true or false + */ + public void setSettingsFlag(Flag flag, boolean state) { + setSettingsFlag(flag, state, true); + } + + /** + * Sets the state of a settings flag + * Also specify whether subflags are affected by this method call + * @param flag - flag + * @param state - true or false + */ + public void setSettingsFlag(Flag flag, boolean state, boolean doSubflags) { + int newState = state ? 1 : -1; + if (flag.getType().equals(Flag.Type.SETTING) || flag.getType().equals(Flag.Type.WORLD_SETTING)) { + flags.put(flag.getID(), newState); + if (doSubflags && flag.hasSubflags()) { + // If we have circular subflags or a flag is a subflag of itself we are in trouble! + flag.getSubflags().forEach(subflag -> setSettingsFlag(subflag, state, true)); + } + } + setChanged(); + } + + /** + * Set the spawn location for this island type + * @param islandType - island type + * @param l - location + */ + public void setSpawnPoint(Environment islandType, Location l) { + spawnPoint.put(islandType, l); + setChanged(); + } + + /** + * Get the spawn point for this island type + * @param islandType - island type + * @return - location or null if one does not exist + */ + @Nullable + public Location getSpawnPoint(Environment islandType) { + return spawnPoint.get(islandType); + } + + /** + * Removes all of a specified rank from the member list + * @param rank rank value + */ + public void removeRank(Integer rank) { + members.values().removeIf(rank::equals); + setChanged(); + } + + /** + * Gets the history of the island. + * @return the list of {@link LogEntry} for this island. + */ + public List getHistory() { + return history; + } + + /** + * Adds a {@link LogEntry} to the history of this island. + * @param logEntry the LogEntry to add. + */ + public void log(LogEntry logEntry) { + history.add(logEntry); + setChanged(); + } + + /** + * Sets the history of the island. + * @param history the list of {@link LogEntry} to set for this island. + */ + public void setHistory(List history) { + this.history = history; + setChanged(); + } + + /** + * @return the doNotLoad + */ + public boolean isDoNotLoad() { + return doNotLoad; + } + + /** + * @param doNotLoad the doNotLoad to set + */ + public void setDoNotLoad(boolean doNotLoad) { + this.doNotLoad = doNotLoad; + setChanged(); + } + + /** + * @return the deleted + */ + public boolean isDeleted() { + return deleted; + } + + /** + * @param deleted the deleted to set + */ + public void setDeleted(boolean deleted) { + this.deleted = deleted; + setChanged(); + } + + /** + * Returns the name of the {@link world.bentobox.bentobox.api.addons.GameModeAddon GameModeAddon} this island is handled by. + * @return the name of the {@link world.bentobox.bentobox.api.addons.GameModeAddon GameModeAddon} this island is handled by. + * @since 1.5.0 + */ + public String getGameMode() { + return gameMode; + } + + /** + * Sets the name of the {@link world.bentobox.bentobox.api.addons.GameModeAddon GameModeAddon} this island is handled by. + * Note this has no effect over the actual location of the island, however this may cause issues with addons using this data. + * @since 1.5.0 + */ + public void setGameMode(String gameMode) { + this.gameMode = gameMode; + setChanged(); + } + + /** + * Checks whether this island has its nether island generated or not. + * @return {@code true} if this island has its nether island generated, {@code false} otherwise. + * @since 1.5.0 + */ + public boolean hasNetherIsland() { + World nether = BentoBox.getInstance().getIWM().getNetherWorld(getWorld()); + return nether != null && !getCenter().toVector().toLocation(nether).getBlock().getType().isAir(); + } + + /** + * Checks whether this island has its nether island mode enabled or not. + * @return {@code true} if this island has its nether island enabled, {@code false} otherwise. + * @since 1.21.0 + */ + public boolean isNetherIslandEnabled() { + return this.getPlugin().getIWM().isNetherGenerate(this.world) && this.getPlugin().getIWM().isNetherIslands(this.world); + } + + /** + * Checks whether this island has its end island generated or not. + * @return {@code true} if this island has its end island generated, {@code false} otherwise. + * @since 1.5.0 + */ + public boolean hasEndIsland() { + World end = BentoBox.getInstance().getIWM().getEndWorld(getWorld()); + return end != null && !getCenter().toVector().toLocation(end).getBlock().getType().isAir(); + } + + + /** + * Checks whether this island has its end island mode enabled or not. + * @return {@code true} if this island has its end island enabled, {@code false} otherwise. + * @since 1.21.0 + */ + public boolean isEndIslandEnabled() { + return this.getPlugin().getIWM().isEndGenerate(this.world) && this.getPlugin().getIWM().isEndIslands(this.world); + } + + + /** + * Checks if a flag is on cooldown. Only stored in memory so a server restart will reset the cooldown. + * @param flag - flag + * @return true if on cooldown, false if not + * @since 1.6.0 + */ + public boolean isCooldown(Flag flag) { + if (cooldowns.containsKey(flag.getID()) && cooldowns.get(flag.getID()) > System.currentTimeMillis()) { + return true; + } + cooldowns.remove(flag.getID()); + setChanged(); + return false; + } + + /** + * Sets a cooldown for this flag on this island. + * @param flag - Flag to cooldown + */ + public void setCooldown(Flag flag) { + cooldowns.put(flag.getID(), flag.getCooldown() * 1000L + System.currentTimeMillis()); + setChanged(); + } + + /** + * @return the cooldowns + */ + public Map getCooldowns() { + return cooldowns; + } + + /** + * @param cooldowns the cooldowns to set + */ + public void setCooldowns(Map cooldowns) { + this.cooldowns = cooldowns; + setChanged(); + } + + /** + * @return the commandRanks + */ + public Map getCommandRanks() { + return commandRanks; + } + + /** + * @param commandRanks the commandRanks to set + */ + public void setCommandRanks(Map commandRanks) { + this.commandRanks = commandRanks; + setChanged(); + } + + /** + * Get the rank required to run command on this island. + * The command must have been registered with a rank. + * @param command - the string given by {@link CompositeCommand#getUsage()} + * @return Rank value required, or if command is not set {@link CompositeCommand#getDefaultCommandRank()} + */ + public int getRankCommand(String command) { + + if (this.commandRanks == null) { + this.commandRanks = new HashMap<>(); + } + + // Return or calculate default rank for a command. + return this.commandRanks.computeIfAbsent(command, key -> { + + // Need to find default value for the command. + String[] labels = key.replaceFirst("/", "").split(" "); + + // Get first command label. + CompositeCommand compositeCommand = this.getPlugin().getCommandsManager().getCommand(labels[0]); + + for (int i = 1; i < labels.length && compositeCommand != null; i++) + { + compositeCommand = compositeCommand.getSubCommand(labels[i]).orElse(null); + } + + // Return default command rank or owner rank, if command does not exist. + return compositeCommand == null ? RanksManager.OWNER_RANK : compositeCommand.getDefaultCommandRank(); + }); + } + + /** + * + * @param command - the string given by {@link CompositeCommand#getUsage()} + * @param rank value as used by {@link RanksManager} + */ + public void setRankCommand(String command, int rank) { + if (this.commandRanks == null) this.commandRanks = new HashMap<>(); + this.commandRanks.put(command, rank); + setChanged(); + } + + /** + * Returns whether this Island is currently reserved or not. + * If {@code true}, this means no blocks, except a bedrock one at the center of the island, exist. + * @return {@code true} if this Island is reserved, {@code false} otherwise. + * @since 1.6.0 + */ + public boolean isReserved() { + return reserved != null && reserved; + } + + /** + * @param reserved the reserved to set + * @since 1.6.0 + */ + public void setReserved(boolean reserved) { + this.reserved = reserved; + setChanged(); + } + + /** + * @return the metaData + * @since 1.15.5 + */ + @Override + public Optional> getMetaData() { + if (metaData == null) { + metaData = new HashMap<>(); + } + return Optional.of(metaData); + } + + /** + * @param metaData the metaData to set + * @since 1.15.4 + */ + @Override + public void setMetaData(Map metaData) { + this.metaData = metaData; + setChanged(); + } + + /** + * @return changed state + */ + public boolean isChanged() { + return changed; + } + + /** + * Indicates the fields have been changed. Used to optimize saving on shutdown. + */ + public void setChanged() { + this.changed = true; + } + + /** + * @param changed the changed to set + */ + public void setChanged(boolean changed) { + this.changed = changed; + } + + /** + * Get the center location of the protection zone. + * This can be anywhere within the island space and can move. + * Unless explicitly set, it will return the same as {@link #getCenter()}. + * @return a clone of the protection center location + * @since 1.16.0 + */ + @NonNull + public Location getProtectionCenter() { + return location == null ? getCenter() : location.clone(); + } + + /** + * Sets the protection center location of the island within the island space. + * @param location the location to set + * @throws IOException if the location is not in island space + * @since 1.16.0 + */ + public void setProtectionCenter(Location location) throws IOException { + if (!this.inIslandSpace(location)) { + throw new IOException("Location must be in island space"); + } + this.location = location; + this.updateMaxEverProtectionRange(); + setChanged(); + } + + /** + * @return the homes + * @since 1.16.0 + */ + @NonNull + public Map getHomes() { + if (homes == null) { + homes = new HashMap<>(); + } + return homes; + } + + /** + * Get the location of a named home + * @param name home name case insensitive (name is forced to lower case) + * @return the home location or if none found the protection center of the island is returned. + * @since 1.16.0 + */ + @NonNull + public Location getHome(String name) { + Location l = getHomes().get(name.toLowerCase()); + return l == null ? getProtectionCenter() : l; + } + + /** + * @param homes the homes to set + * @since 1.16.0 + */ + public void setHomes(Map homes) { + this.homes = homes; + setChanged(); + } + + /** + * @param name the name of the home + * @since 1.16.0 + */ + public void addHome(String name, Location location) { + if (location != null) { + Vector v = location.toVector(); + if (!this.getBoundingBox().contains(v)) { + BentoBox.getInstance().logWarning("Tried to set a home location " + location + " outside of the island. This generally should not happen."); + BentoBox.getInstance().logWarning("Island is at " + this.getCenter() + ". The island file may need editing to remove this home."); + BentoBox.getInstance().logWarning("Please report this issue and logs around this item to BentoBox"); + } + } + getHomes().put(name.toLowerCase(), location); + setChanged(); + } + + /** + * Remove a named home from this island + * @param name - home name to remove + * @return true if home removed successfully + * @since 1.16.0 + */ + public boolean removeHome(String name) { + setChanged(); + return getHomes().remove(name.toLowerCase()) != null; + } + + /** + * Remove all homes from this island except the default home + * @return true if any non-default homes removed + * @since 1.20.0 + */ + public boolean removeHomes() { + setChanged(); + return getHomes().keySet().removeIf(k -> !k.isEmpty()); + } + + /** + * Rename a home + * @param oldName - old name of home + * @param newName - new name of home + * @return true if successful, false if oldName does not exist, already exists + * @since 1.16.0 + */ + public boolean renameHome(String oldName, String newName) { + if (getHomes().containsKey(oldName.toLowerCase()) && !getHomes().containsKey(newName.toLowerCase())) { + this.addHome(newName, this.getHome(oldName)); + this.removeHome(oldName); + return true; + } + return false; + } + + /** + * Get the max homes. You shouldn't access this directly. + * Use {@link world.bentobox.bentobox.managers.IslandsManager#getMaxHomes(Island)} + * @return the maxHomes. If null, then the world default should be used. + * @since 1.16.0 + */ + @Nullable + public Integer getMaxHomes() { + return maxHomes; + } + + /** + * @param maxHomes the maxHomes to set. If null then the world default will be used. + * You shouldn't access this directly. + * Use {@link world.bentobox.bentobox.managers.IslandsManager#setMaxHomes(Island, Integer)} + * @since 1.16.0 + */ + public void setMaxHomes(@Nullable Integer maxHomes) { + this.maxHomes = maxHomes; + setChanged(); + } + + /** + * @return the maxMembers + * @since 1.16.0 + */ + public Map getMaxMembers() { + if (maxMembers == null) { + maxMembers = new HashMap<>(); + } + return maxMembers; + } + + /** + * @param maxMembers the maxMembers to set + * @since 1.16.0 + */ + public void setMaxMembers(Map maxMembers) { + this.maxMembers = maxMembers; + setChanged(); + } + + /** + * Get the maximum number of island members + * @param rank island rank value from {@link RanksManager} + * @return the maxMembers for the rank given - if null then the world default should be used. Negative values = unlimited. + * @since 1.16.0 + */ + @Nullable + public Integer getMaxMembers(int rank) { + return getMaxMembers().get(rank); + } + + + /** + * Set the maximum number of island members + * @param rank island rank value from {@link RanksManager} + * @param maxMembers the maxMembers to set. If null then the world default applies. Negative values = unlimited. + * @since 1.16.0 + */ + public void setMaxMembers(int rank, Integer maxMembers) { + getMaxMembers().put(rank, maxMembers); + } + + + /** + * @return the bonusRanges + */ + public List getBonusRanges() { + if (bonusRanges == null) { + this.setBonusRanges(new ArrayList<>()); + } + return bonusRanges; + } + + /** + * @param bonusRanges the bonusRanges to set + */ + public void setBonusRanges(List bonusRanges) { + this.bonusRanges = bonusRanges; + setChanged(); + } + + /** + * Get the bonus range provided by all settings of the range giver + * @param id an id to identify this bonus + * @return bonus range, or 0 if unknown + */ + public int getBonusRange(String id) { + return this.getBonusRanges().stream().filter(r -> r.getUniqueId().equals(id)).mapToInt(BonusRangeRecord::getRange).sum(); + } + + /** + * Get the BonusRangeRecord for uniqueId + * @param uniqueId a unique id to identify this bonus + * @return optional BonusRangeRecord + */ + public Optional getBonusRangeRecord(String uniqueId) { + return this.getBonusRanges().stream().filter(r -> r.getUniqueId().equals(uniqueId)).findFirst(); + } + + /** + * Add a bonus range amount to the island for this addon or plugin. + * Note, this will not replace any range set already with the same id + * @param id an id to identify this bonus + * @param range range to add to the island protected range + * @param message the reference key to a locale message related to this bonus. May be blank. + */ + public void addBonusRange(String id, int range, String message) { + this.getBonusRanges().add(new BonusRangeRecord(id, range, message)); + setMaxEverProtectionRange(this.getProtectionRange()); + setChanged(); + } + + /** + * Clear the bonus ranges for a unique ID + * @param id id to identify this bonus + */ + public void clearBonusRange(String id) { + this.getBonusRanges().removeIf(r -> r.getUniqueId().equals(id)); + setChanged(); + } + + /** + * Clear all bonus ranges for this island + */ + public void clearAllBonusRanges() { + this.getBonusRanges().clear(); + setChanged(); + } + + /** + * @return the primary + */ + public boolean isPrimary() { + return primary; + } + + /** + * @param primary the primary to set + */ + public void setPrimary(boolean primary) { + this.primary = primary; + setChanged(); + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "Island [changed=" + changed + ", deleted=" + deleted + ", uniqueId=" + uniqueId + ", center=" + center + + ", location=" + location + ", range=" + range + ", protectionRange=" + protectionRange + + ", maxEverProtectionRange=" + maxEverProtectionRange + ", world=" + world + ", gameMode=" + gameMode + + ", name=" + name + ", createdDate=" + createdDate + ", updatedDate=" + updatedDate + ", owner=" + + owner + ", members=" + members + ", maxMembers=" + maxMembers + ", spawn=" + spawn + + ", purgeProtected=" + purgeProtected + ", flags=" + flags + ", history=" + history + + ", spawnPoint=" + spawnPoint + ", doNotLoad=" + doNotLoad + + ", cooldowns=" + cooldowns + ", commandRanks=" + commandRanks + ", reserved=" + reserved + + ", metaData=" + metaData + ", homes=" + homes + ", maxHomes=" + maxHomes + "]"; + } } diff --git a/src/main/java/world/bentobox/bentobox/listeners/PrimaryIslandListener.java b/src/main/java/world/bentobox/bentobox/listeners/PrimaryIslandListener.java index 122b42f33..69064b87f 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/PrimaryIslandListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/PrimaryIslandListener.java @@ -50,7 +50,7 @@ public class PrimaryIslandListener implements Listener { private void setIsland(Player player, Location location) { im.getIslandAt(location) - .filter(i -> i.getOwner() != null && i.getOwner().equals(player.getUniqueId())) + .filter(i -> player.getUniqueId().equals(i.getOwner())) .ifPresent(i -> im.setPrimaryIsland(player.getUniqueId(), i)); } 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 c32ed9f1b..0994315bf 100644 --- a/src/main/java/world/bentobox/bentobox/managers/island/NewIsland.java +++ b/src/main/java/world/bentobox/bentobox/managers/island/NewIsland.java @@ -198,7 +198,8 @@ public class NewIsland { // Do nothing of other cases } } - + // Set the player's primary island + plugin.getIslands().setPrimaryIsland(user.getUniqueId(), island); // Run task to run after creating the island in one tick if island is not being pasted if (noPaste) { Bukkit.getScheduler().runTask(plugin, () -> postCreationTask(oldIsland)); @@ -244,13 +245,11 @@ public class NewIsland { /** * Cleans up a user before moving them to a new island. - * Removes any old home locations. Sets the next home location. Resets deaths. + * Resets deaths. * Checks range permissions and saves the player to the database. * @param loc - the new island location */ private void cleanUpUser(Location loc) { - // Set home location - plugin.getIslands().setHomeLocation(user, new Location(loc.getWorld(), loc.getX() + 0.5D, loc.getY(), loc.getZ() + 0.5D)); // Reset deaths if (plugin.getIWM().isDeathsResetOnNewIsland(world)) { plugin.getPlayers().setDeaths(world, user.getUniqueId(), 0); diff --git a/src/test/java/world/bentobox/bentobox/database/objects/IslandTest.java b/src/test/java/world/bentobox/bentobox/database/objects/IslandTest.java index eb9be5d71..fbcfc4ef9 100644 --- a/src/test/java/world/bentobox/bentobox/database/objects/IslandTest.java +++ b/src/test/java/world/bentobox/bentobox/database/objects/IslandTest.java @@ -6,6 +6,9 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.io.IOException; @@ -21,6 +24,7 @@ import org.bukkit.Material; import org.bukkit.World; import org.bukkit.World.Environment; import org.bukkit.block.Block; +import org.bukkit.util.Vector; import org.eclipse.jdt.annotation.NonNull; import org.junit.After; import org.junit.Before; @@ -85,6 +89,7 @@ public class IslandTest { // Location when(location.clone()).thenReturn(location); + when(location.toVector()).thenReturn(new Vector(0,0,0)); when(world.getName()).thenReturn("bskyblock_world"); when(location.getWorld()).thenReturn(world); when(world.getEnvironment()).thenReturn(Environment.NORMAL); @@ -1173,6 +1178,18 @@ public class IslandTest { i.addHome("backyard", location); assertEquals(location, i.getHome("backyard")); } + + /** + * Test method for {@link world.bentobox.bentobox.database.objects.Island#addHome(java.lang.String, org.bukkit.Location)}. + */ + @Test + public void testAddHomeOutsideIsland() { + when(location.toVector()).thenReturn(new Vector(1000000, 0, 10000000)); + i.addHome("backyard", location); + // Check there is a warning about this home being outside of the island + verify(plugin, times(3)).logWarning(anyString()); + assertEquals(location, i.getHome("backyard")); + } /** * Test method for {@link world.bentobox.bentobox.database.objects.Island#removeHome(java.lang.String)}. diff --git a/src/test/java/world/bentobox/bentobox/managers/island/NewIslandTest.java b/src/test/java/world/bentobox/bentobox/managers/island/NewIslandTest.java index 3171015df..1197e5637 100644 --- a/src/test/java/world/bentobox/bentobox/managers/island/NewIslandTest.java +++ b/src/test/java/world/bentobox/bentobox/managers/island/NewIslandTest.java @@ -198,7 +198,7 @@ public class NewIslandTest { verify(bpb).getUniqueId(); verify(ice).getBlueprintBundle(); verify(pm).setDeaths(eq(world), eq(uuid), eq(0)); - verify(im).setHomeLocation(eq(user), any()); + verify(im, never()).setHomeLocation(eq(user), any()); verify(island).setProtectionRange(eq(20)); } @@ -218,7 +218,7 @@ public class NewIslandTest { verify(ice, never()).getBlueprintBundle(); verify(ire).getBlueprintBundle(); verify(pm).setDeaths(eq(world), eq(uuid), eq(0)); - verify(im).setHomeLocation(eq(user), any()); + verify(im, never()).setHomeLocation(eq(user), any()); } /** @@ -235,7 +235,7 @@ public class NewIslandTest { verify(bpb).getUniqueId(); verify(ice).getBlueprintBundle(); verify(pm).setDeaths(eq(world), eq(uuid), eq(0)); - verify(im).setHomeLocation(eq(user), any()); + verify(im, never()).setHomeLocation(eq(user), any()); } /** @@ -252,7 +252,7 @@ public class NewIslandTest { verify(bpb).getUniqueId(); verify(ice).getBlueprintBundle(); verify(pm).setDeaths(eq(world), eq(uuid), eq(0)); - verify(im).setHomeLocation(eq(user), any()); + verify(im, never()).setHomeLocation(eq(user), any()); } /** @@ -270,7 +270,7 @@ public class NewIslandTest { verify(bpb).getUniqueId(); verify(ice).getBlueprintBundle(); verify(pm).setDeaths(eq(world), eq(uuid), eq(0)); - verify(im).setHomeLocation(eq(user), any()); + verify(im, never()).setHomeLocation(eq(user), any()); verify(island).setProtectionRange(eq(20)); verify(island).setReserved(eq(false)); } @@ -291,7 +291,7 @@ public class NewIslandTest { verify(bpb).getUniqueId(); verify(ice).getBlueprintBundle(); verify(pm).setDeaths(eq(world), eq(uuid), eq(0)); - verify(im).setHomeLocation(eq(user), any()); + verify(im, never()).setHomeLocation(eq(user), any()); verify(island).setProtectionRange(eq(20)); verify(plugin).logError("New island for user tastybento was not reserved!"); } @@ -312,7 +312,7 @@ public class NewIslandTest { verify(bpb).getUniqueId(); verify(ice).getBlueprintBundle(); verify(pm).setDeaths(eq(world), eq(uuid), eq(0)); - verify(im).setHomeLocation(eq(user), any()); + verify(im, never()).setHomeLocation(eq(user), any()); verify(island).setProtectionRange(eq(20)); verify(plugin).logError("New island for user tastybento was not reserved!"); }