package world.bentobox.bentobox.database.objects; import java.io.IOException; import java.util.ArrayList; import java.util.EnumMap; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; import org.bukkit.Bukkit; import org.bukkit.GameMode; import org.bukkit.Location; 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; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet.Builder; import com.google.gson.annotations.Expose; import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.commands.CompositeCommand; import world.bentobox.bentobox.api.configuration.WorldSettings; import world.bentobox.bentobox.api.flags.Flag; import world.bentobox.bentobox.api.logs.LogEntry; import world.bentobox.bentobox.api.metadata.MetaDataAble; import world.bentobox.bentobox.api.metadata.MetaDataValue; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.database.objects.adapters.Adapter; import world.bentobox.bentobox.database.objects.adapters.LogEntryListAdapter; import world.bentobox.bentobox.lists.Flags; import world.bentobox.bentobox.managers.RanksManager; import world.bentobox.bentobox.util.Pair; import world.bentobox.bentobox.util.Util; /** * Stores all the info about an island Managed by IslandsManager Responsible for * team information as well. * * @author tastybento * @author Poslovitch */ @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) { 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 nameToLookFor 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(final String nameToLookFor) { return getHomes().entrySet().stream().filter(en -> en.getKey().equalsIgnoreCase(nameToLookFor)) .map(Entry::getValue) .findFirst().orElse(getProtectionCenter().clone().add(new Vector(0.5D, 0D, 0.5D))); } /** * @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(); } /** * Check if a player is in this island's team * @param playerUUID player's UUID * @return true if in team * @since 2.3.0 */ public boolean inTeam(UUID playerUUID) { return this.getMemberSet().contains(playerUUID); } /** * Check if this island has a team * @return true if this island has a team * @since 2.3.0 */ public boolean hasTeam() { return this.getMemberSet().size() > 1; } /* * (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 + "]"; } }