/* * _____ _ _ _____ _ * | __ \| | | | / ____| | | * | |__) | | ___ | |_| (___ __ _ _ _ __ _ _ __ ___ __| | * | ___/| |/ _ \| __|\___ \ / _` | | | |/ _` | '__/ _ \/ _` | * | | | | (_) | |_ ____) | (_| | |_| | (_| | | | __/ (_| | * |_| |_|\___/ \__|_____/ \__, |\__,_|\__,_|_| \___|\__,_| * | | * |_| * PlotSquared plot management system for Minecraft * Copyright (C) 2014 - 2022 IntellectualSites * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.plotsquared.core.plot; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.inject.Inject; import com.plotsquared.core.PlotSquared; import com.plotsquared.core.command.Like; import com.plotsquared.core.configuration.Settings; import com.plotsquared.core.configuration.caption.Caption; import com.plotsquared.core.configuration.caption.CaptionUtility; import com.plotsquared.core.configuration.caption.StaticCaption; import com.plotsquared.core.configuration.caption.TranslatableCaption; import com.plotsquared.core.database.DBFunc; import com.plotsquared.core.events.Result; import com.plotsquared.core.events.TeleportCause; import com.plotsquared.core.generator.ClassicPlotWorld; import com.plotsquared.core.listener.PlotListener; import com.plotsquared.core.location.BlockLoc; import com.plotsquared.core.location.Direction; import com.plotsquared.core.location.Location; import com.plotsquared.core.permissions.Permission; import com.plotsquared.core.player.ConsolePlayer; import com.plotsquared.core.player.PlotPlayer; import com.plotsquared.core.plot.expiration.ExpireManager; import com.plotsquared.core.plot.expiration.PlotAnalysis; import com.plotsquared.core.plot.flag.FlagContainer; import com.plotsquared.core.plot.flag.GlobalFlagContainer; import com.plotsquared.core.plot.flag.InternalFlag; import com.plotsquared.core.plot.flag.PlotFlag; import com.plotsquared.core.plot.flag.implementations.DescriptionFlag; import com.plotsquared.core.plot.flag.implementations.KeepFlag; import com.plotsquared.core.plot.flag.implementations.ServerPlotFlag; import com.plotsquared.core.plot.flag.types.DoubleFlag; import com.plotsquared.core.plot.schematic.Schematic; import com.plotsquared.core.plot.world.SinglePlotArea; import com.plotsquared.core.queue.QueueCoordinator; import com.plotsquared.core.util.EventDispatcher; import com.plotsquared.core.util.MathMan; import com.plotsquared.core.util.Permissions; import com.plotsquared.core.util.PlayerManager; import com.plotsquared.core.util.RegionManager; import com.plotsquared.core.util.RegionUtil; import com.plotsquared.core.util.SchematicHandler; import com.plotsquared.core.util.TimeUtil; import com.plotsquared.core.util.WorldUtil; import com.plotsquared.core.util.query.PlotQuery; import com.plotsquared.core.util.task.RunnableVal; import com.plotsquared.core.util.task.TaskManager; import com.plotsquared.core.util.task.TaskTime; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.world.biome.BiomeType; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.Template; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import java.lang.ref.Cleaner; import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Set; import java.util.TimeZone; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; import static com.plotsquared.core.util.entity.EntityCategories.CAP_ANIMAL; import static com.plotsquared.core.util.entity.EntityCategories.CAP_ENTITY; import static com.plotsquared.core.util.entity.EntityCategories.CAP_MISC; import static com.plotsquared.core.util.entity.EntityCategories.CAP_MOB; import static com.plotsquared.core.util.entity.EntityCategories.CAP_MONSTER; import static com.plotsquared.core.util.entity.EntityCategories.CAP_VEHICLE; /** * The plot class
* [IMPORTANT] * - Unclaimed plots will not have persistent information. * - Any information set/modified in an unclaimed object may not be reflected in other instances * - Using the `new` operator will create an unclaimed plot instance * - Use the methods from the PlotArea/PS/Location etc to get existing plots */ public class Plot { @Deprecated(forRemoval = true, since = "6.6.0") public static final int MAX_HEIGHT = 256; private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + Plot.class.getSimpleName()); private static final DecimalFormat FLAG_DECIMAL_FORMAT = new DecimalFormat("0"); private static final MiniMessage MINI_MESSAGE = MiniMessage.builder().build(); private static final Cleaner CLEANER = Cleaner.create(); static Set connected_cache; static Set regions_cache; static { FLAG_DECIMAL_FORMAT.setMaximumFractionDigits(340); } /** * Plot flag container */ private final FlagContainer flagContainer = new FlagContainer(null); /** * Utility used to manage plot comments */ private final PlotCommentContainer plotCommentContainer = new PlotCommentContainer(this); /** * Utility used to modify the plot */ private final PlotModificationManager plotModificationManager = new PlotModificationManager(this); /** * Represents whatever the database manager needs it to:
* - A value of -1 usually indicates the plot will not be stored in the DB
* - A value of 0 usually indicates that the DB manager hasn't set a value
* * @deprecated magical */ @Deprecated public int temp; /** * List of trusted (with plot permissions). */ HashSet trusted; /** * List of members users (with plot permissions). */ HashSet members; /** * List of denied players. */ HashSet denied; /** * External settings class. * - Please favor the methods over direct access to this class
* - The methods are more likely to be left unchanged from version changes
*/ PlotSettings settings; @NonNull private PlotId id; // These will be injected @Inject private EventDispatcher eventDispatcher; @Inject private PlotListener plotListener; @Inject private RegionManager regionManager; @Inject private WorldUtil worldUtil; @Inject private SchematicHandler schematicHandler; /** * plot owner * (Merged plots can have multiple owners) * Direct access is Deprecated: use getOwners() * * @deprecated */ private UUID owner; /** * Plot creation timestamp (not accurate if the plot was created before this was implemented)
* - Milliseconds since the epoch
*/ private long timestamp; private PlotArea area; /** * Session only plot metadata (session is until the server stops)
*
* For persistent metadata use the flag system */ private ConcurrentHashMap meta; /** * The cached origin plot. * - The origin plot is used for plot grouping and relational data */ private Plot origin; /** * Constructor for a new plot. * (Only changes after plot.create() will be properly set in the database) * *

* See {@link Plot#getPlot(Location)} for existing plots *

* * @param area the PlotArea where the plot is located * @param id the plot id * @param owner the plot owner */ public Plot(final PlotArea area, final @NonNull PlotId id, final UUID owner) { this(area, id, owner, 0); } /** * Constructor for an unowned plot. * (Only changes after plot.create() will be properly set in the database) * *

* See {@link Plot#getPlot(Location)} for existing plots *

* * @param area the PlotArea where the plot is located * @param id the plot id */ public Plot(final @NonNull PlotArea area, final @NonNull PlotId id) { this(area, id, null, 0); } /** * Constructor for a temporary plot (use -1 for temp)
* The database will ignore any queries regarding temporary plots. * Please note that some bulk plot management functions may still affect temporary plots (TODO: fix this) * *

* See {@link Plot#getPlot(Location)} for existing plots *

* * @param area the PlotArea where the plot is located * @param id the plot id * @param owner the owner of the plot * @param temp Represents whatever the database manager needs it to */ public Plot(final PlotArea area, final @NonNull PlotId id, final UUID owner, final int temp) { this.area = area; this.id = id; this.owner = owner; this.temp = temp; this.flagContainer.setParentContainer(area.getFlagContainer()); PlotSquared.platform().injector().injectMembers(this); // This is needed, because otherwise the Plot, the FlagContainer and its // `this::handleUnknown` PlotFlagUpdateHandler won't get cleaned up ever CLEANER.register(this, this.flagContainer.createCleanupHook()); } /** * Constructor for a saved plots (Used by the database manager when plots are fetched) * *

* See {@link Plot#getPlot(Location)} for existing plots *

* * @param id the plot id * @param owner the plot owner * @param trusted the plot trusted players * @param members the plot added players * @param denied the plot denied players * @param alias the plot's alias * @param position plot home position * @param flags the plot's flags * @param area the plot's PlotArea * @param merged an array giving merged plots * @param timestamp when the plot was created * @param temp value representing whatever DBManager needs it to. Do not touch tbh. */ public Plot( @NonNull PlotId id, UUID owner, Set trusted, Set members, Set denied, String alias, BlockLoc position, Collection> flags, PlotArea area, boolean[] merged, long timestamp, int temp ) { this.id = id; this.area = area; this.owner = owner; this.settings = new PlotSettings(); this.members = new HashSet<>(members); this.trusted = new HashSet<>(trusted); this.denied = new HashSet<>(denied); this.settings.setAlias(alias); this.settings.setPosition(position); this.settings.setMerged(merged); this.timestamp = timestamp; this.temp = temp; if (area != null) { this.flagContainer.setParentContainer(area.getFlagContainer()); if (flags != null) { flags.forEach(this.flagContainer::addFlag); } } PlotSquared.platform().injector().injectMembers(this); } /** * Get the plot from a string. * * @param player Provides a context for what world to search in. Prefixing the term with 'world_name;' will override this context. * @param arg The search term * @param message If a message should be sent to the player if a plot cannot be found * @return The plot if only 1 result is found, or null */ public static @Nullable Plot getPlotFromString( final @Nullable PlotPlayer player, final @Nullable String arg, final boolean message ) { if (arg == null) { if (player == null) { if (message) { LOGGER.info("No plot area string was supplied"); } return null; } return player.getCurrentPlot(); } PlotArea area; if (player != null) { area = PlotSquared.get().getPlotAreaManager().getPlotAreaByString(arg); if (area == null) { area = player.getApplicablePlotArea(); } } else { area = ConsolePlayer.getConsole().getApplicablePlotArea(); } String[] split = arg.split("[;,]"); PlotId id; if (split.length == 4) { area = PlotSquared.get().getPlotAreaManager().getPlotAreaByString(split[0] + ';' + split[1]); id = PlotId.fromString(split[2] + ';' + split[3]); } else if (split.length == 3) { area = PlotSquared.get().getPlotAreaManager().getPlotAreaByString(split[0]); id = PlotId.fromString(split[1] + ';' + split[2]); } else if (split.length == 2) { id = PlotId.fromString(arg); } else { Collection plots; if (area == null) { plots = PlotQuery.newQuery().allPlots().asList(); } else { plots = area.getPlots(); } for (Plot p : plots) { String name = p.getAlias(); if (!name.isEmpty() && name.equalsIgnoreCase(arg)) { return p.getBasePlot(false); } } if (message && player != null) { player.sendMessage(TranslatableCaption.of("invalid.not_valid_plot_id")); } return null; } if (area == null) { if (message && player != null) { player.sendMessage(TranslatableCaption.of("errors.invalid_plot_world")); } return null; } return area.getPlotAbs(id); } /** * Gets a plot from a string e.g. [area];[id] * * @param defaultArea if no area is specified * @param string plot id/area + id * @return New or existing plot object */ public static @Nullable Plot fromString(final @Nullable PlotArea defaultArea, final @NonNull String string) { final String[] split = string.split("[;,]"); if (split.length == 2) { if (defaultArea != null) { PlotId id = PlotId.fromString(split[0] + ';' + split[1]); return defaultArea.getPlotAbs(id); } } else if (split.length == 3) { PlotArea pa = PlotSquared.get().getPlotAreaManager().getPlotArea(split[0], null); if (pa != null) { PlotId id = PlotId.fromString(split[1] + ';' + split[2]); return pa.getPlotAbs(id); } } else if (split.length == 4) { PlotArea pa = PlotSquared.get().getPlotAreaManager().getPlotArea(split[0], split[1]); if (pa != null) { PlotId id = PlotId.fromString(split[1] + ';' + split[2]); return pa.getPlotAbs(id); } } return null; } /** * Return a new/cached plot object at a given location. * *

* Use {@link PlotPlayer#getCurrentPlot()} if a player is expected here. *

* * @param location the location of the plot * @return plot at location or null */ public static @Nullable Plot getPlot(final @NonNull Location location) { final PlotArea pa = location.getPlotArea(); if (pa != null) { return pa.getPlot(location); } return null; } @NonNull static Location[] getCorners(final @NonNull String world, final @NonNull CuboidRegion region) { final BlockVector3 min = region.getMinimumPoint(); final BlockVector3 max = region.getMaximumPoint(); return new Location[]{Location.at(world, min), Location.at(world, max)}; } /** * Get the owner of this exact plot, as it is * stored in the database. *

* If the plot is a mega-plot, then the method returns * the owner of this particular subplot. *

* Unlike {@link #getOwner()} this method does not * consider factors such as {@link com.plotsquared.core.plot.flag.implementations.ServerPlotFlag} * that could alter the de facto owner of the plot. * * @return The plot owner of this particular (sub-)plot * as stored in the database, if one exists. Else, null. */ public @Nullable UUID getOwnerAbs() { return this.owner; } /** * Set the owner of this exact sub-plot. This does * not update the database. * * @param owner The new owner of this particular sub-plot. */ public void setOwnerAbs(final @Nullable UUID owner) { this.owner = owner; } /** * Get the name of the world that the plot is in * * @return World name */ public @Nullable String getWorldName() { return area.getWorldName(); } /** * Session only plot metadata (session is until the server stops)
*
* For persistent metadata use the flag system * * @param key metadata key * @param value metadata value */ public void setMeta(final @NonNull String key, final @NonNull Object value) { if (this.meta == null) { this.meta = new ConcurrentHashMap<>(); } this.meta.put(key, value); } /** * Gets the metadata for a key
*
* For persistent metadata use the flag system * * @param key metadata key to get value for * @return Object value */ public @Nullable Object getMeta(final @NonNull String key) { if (this.meta != null) { return this.meta.get(key); } return null; } /** * Delete the metadata for a key
* - metadata is session only * - deleting other plugin's metadata may cause issues * * @param key key to delete */ public void deleteMeta(final @NonNull String key) { if (this.meta != null) { this.meta.remove(key); } } /** * Gets the cluster this plot is associated with * * @return the PlotCluster object, or null */ public @Nullable PlotCluster getCluster() { if (this.getArea() == null) { return null; } return this.getArea().getCluster(this.id); } /** * Efficiently get the players currently inside this plot
* - Will return an empty list if no players are in the plot
* - Remember, you can cast a PlotPlayer to its respective implementation (BukkitPlayer, SpongePlayer) to obtain the player object * * @return list of PlotPlayer(s) or an empty list */ public @NonNull List> getPlayersInPlot() { final List> players = new ArrayList<>(); for (final PlotPlayer player : PlotSquared.platform().playerManager().getPlayers()) { if (this.equals(player.getCurrentPlot())) { players.add(player); } } return players; } /** * Checks if the plot has an owner. * * @return {@code true} if there is an owner, else {@code false} */ public boolean hasOwner() { return this.getOwnerAbs() != null; } /** * Checks if a UUID is a plot owner (merged plots may have multiple owners) * * @param uuid Player UUID * @return {@code true} if the provided uuid is the owner of the plot, else {@code false} */ public boolean isOwner(final @NonNull UUID uuid) { if (uuid.equals(this.getOwner())) { return true; } if (!isMerged()) { return false; } final Set connected = getConnectedPlots(); return connected.stream().anyMatch(current -> uuid.equals(current.getOwner())); } /** * Checks if the given UUID is the owner of this specific plot * * @param uuid Player UUID * @return {@code true} if the provided uuid is the owner of the plot, else {@code false} */ public boolean isOwnerAbs(final @Nullable UUID uuid) { if (uuid == null) { return false; } return uuid.equals(this.getOwner()); } /** * Get the plot owner of this particular sub-plot. * (Merged plots can have multiple owners) * Direct access is discouraged: use {@link #getOwners()} * *

* Use {@link #getOwnerAbs()} to get the owner as stored in the database *

* * @return Server if ServerPlot flag set, else {@link #getOwnerAbs()} */ public @Nullable UUID getOwner() { if (this.getFlag(ServerPlotFlag.class)) { return DBFunc.SERVER; } return this.getOwnerAbs(); } /** * Sets the plot owner (and update the database) * * @param owner uuid to set as owner */ public void setOwner(final @NonNull UUID owner) { if (!hasOwner()) { this.setOwnerAbs(owner); this.getPlotModificationManager().create(); return; } if (!isMerged()) { if (!owner.equals(this.getOwnerAbs())) { this.setOwnerAbs(owner); DBFunc.setOwner(this, owner); } return; } for (final Plot current : getConnectedPlots()) { if (!owner.equals(current.getOwnerAbs())) { current.setOwnerAbs(owner); DBFunc.setOwner(current, owner); } } } /** * Gets a immutable set of owner UUIDs for a plot (supports multi-owner mega-plots). *

* This method cannot be used to add or remove owners from a plot. *

* * @return Immutable view of plot owners */ public @NonNull Set getOwners() { if (this.getOwner() == null) { return ImmutableSet.of(); } if (isMerged()) { Set plots = getConnectedPlots(); Plot[] array = plots.toArray(new Plot[0]); ImmutableSet.Builder owners = ImmutableSet.builder(); UUID last = this.getOwner(); owners.add(this.getOwner()); for (final Plot current : array) { if (current.getOwner() == null) { continue; } if (last == null || current.getOwner().getMostSignificantBits() != last.getMostSignificantBits()) { owners.add(current.getOwner()); last = current.getOwner(); } } return owners.build(); } return ImmutableSet.of(this.getOwner()); } /** * Checks if the player is either the owner or on the trusted/added list. * * @param uuid uuid to check * @return {@code true} if the player is added/trusted or is the owner, else {@code false} */ public boolean isAdded(final @NonNull UUID uuid) { if (!this.hasOwner() || getDenied().contains(uuid)) { return false; } if (isOwner(uuid)) { return true; } if (getMembers().contains(uuid)) { return isOnline(); } if (getTrusted().contains(uuid) || getTrusted().contains(DBFunc.EVERYONE)) { return true; } if (getMembers().contains(DBFunc.EVERYONE)) { return isOnline(); } return false; } /** * Checks if the player is not permitted on this plot. * * @param uuid uuid to check * @return {@code false} if the player is allowed to enter the plot, else {@code true} */ public boolean isDenied(final @NonNull UUID uuid) { return this.denied != null && (this.denied.contains(DBFunc.EVERYONE) && !this.isAdded(uuid) || !this.isAdded(uuid) && this.denied .contains(uuid)); } /** * Gets the {@link PlotId} of this plot. * * @return the PlotId for this plot */ public @NonNull PlotId getId() { return this.id; } /** * Change the plot ID * * @param id new plot ID */ public void setId(final @NonNull PlotId id) { this.id = id; } /** * Gets the plot world object for this plot
* - The generic PlotArea object can be casted to its respective class for more control (e.g. HybridPlotWorld) * * @return PlotArea */ public @Nullable PlotArea getArea() { return this.area; } /** * Assigns this plot to a plot area.
* (Mostly used during startup when worlds are being created)
*

* Do not use this unless you absolutely know what you are doing. *

* * @param area area to assign to */ public void setArea(final @NonNull PlotArea area) { if (this.getArea() == area) { return; } if (this.getArea() != null) { this.area.removePlot(this.id); } this.area = area; area.addPlot(this); this.flagContainer.setParentContainer(area.getFlagContainer()); } /** * Gets the plot manager object for this plot
* - The generic PlotManager object can be casted to its respective class for more control (e.g. HybridPlotManager) * * @return PlotManager */ public @NonNull PlotManager getManager() { return this.area.getPlotManager(); } /** * Gets or create plot settings. * * @return PlotSettings */ public @NonNull PlotSettings getSettings() { if (this.settings == null) { this.settings = new PlotSettings(); } return this.settings; } /** * Returns true if the plot is not merged, or it is the base * plot of multiple merged plots. * * @return Boolean */ public boolean isBasePlot() { return !this.isMerged() || this.equals(this.getBasePlot(false)); } /** * The base plot is an arbitrary but specific connected plot. It is useful for the following:
* - Merged plots need to be treated as a single plot for most purposes
* - Some data such as home location needs to be associated with the group rather than each plot
* - If the plot is not merged it will return itself.
* - The result is cached locally * * @param recalculate whether to recalculate the merged plots to find the origin * @return base Plot */ public Plot getBasePlot(final boolean recalculate) { if (this.origin != null && !recalculate) { if (this.equals(this.origin)) { return this; } return this.origin.getBasePlot(false); } if (!this.isMerged()) { this.origin = this; return this.origin; } this.origin = this; PlotId min = this.id; for (Plot plot : this.getConnectedPlots()) { if (plot.id.getY() < min.getY() || plot.id.getY() == min.getY() && plot.id.getX() < min.getX()) { this.origin = plot; min = plot.id; } } for (Plot plot : this.getConnectedPlots()) { plot.origin = this.origin; } return this.origin; } /** * Checks if this plot is merged in any direction. * * @return {@code true} if this plot is merged, otherwise {@code false} */ public boolean isMerged() { return getSettings().getMerged(0) || getSettings().getMerged(2) || getSettings().getMerged(1) || getSettings().getMerged(3); } /** * Gets the timestamp of when the plot was created (unreliable)
* - not accurate if the plot was created before this was implemented
* - Milliseconds since the epoch
* * @return the creation date of the plot */ public long getTimestamp() { if (this.timestamp == 0) { this.timestamp = System.currentTimeMillis(); } return this.timestamp; } /** * Gets if the plot is merged in a direction
* ------- Actual -------
* 0 = north
* 1 = east
* 2 = south
* 3 = west
* ----- Artificial -----
* 4 = north-east
* 5 = south-east
* 6 = south-west
* 7 = north-west
* ----------
*

* Note: A plot that is merged north and east will not be merged northeast if the northeast plot is not part of the same group
* * @param dir direction to check for merged plot * @return {@code true} if merged in that direction, else {@code false} */ public boolean isMerged(final int dir) { if (this.settings == null) { return false; } switch (dir) { case 0: case 1: case 2: case 3: return this.getSettings().getMerged(dir); case 7: int i = dir - 4; int i2 = 0; if (this.getSettings().getMerged(i2)) { if (this.getSettings().getMerged(i)) { if (Objects.requireNonNull( this.area.getPlotAbs(this.id.getRelative(Direction.getFromIndex(i)))).isMerged(i2)) { return Objects.requireNonNull(this.area .getPlotAbs(this.id.getRelative(Direction.getFromIndex(i2)))).isMerged(i); } } } return false; case 4: case 5: case 6: i = dir - 4; i2 = dir - 3; return this.getSettings().getMerged(i2) && this.getSettings().getMerged(i) && Objects .requireNonNull( this.area.getPlotAbs(this.id.getRelative(Direction.getFromIndex(i)))).isMerged(i2) && Objects .requireNonNull( this.area.getPlotAbs(this.id.getRelative(Direction.getFromIndex(i2)))).isMerged(i); } return false; } /** * Gets the denied users. * * @return a set of denied users */ public @NonNull HashSet getDenied() { if (this.denied == null) { this.denied = new HashSet<>(); } return this.denied; } /** * Sets the denied users for this plot. * * @param uuids uuids to deny */ public void setDenied(final @NonNull Set uuids) { boolean larger = uuids.size() > getDenied().size(); HashSet intersection; if (larger) { intersection = new HashSet<>(getDenied()); } else { intersection = new HashSet<>(uuids); } if (larger) { intersection.retainAll(uuids); } else { intersection.retainAll(getDenied()); } uuids.removeAll(intersection); HashSet toRemove = new HashSet<>(getDenied()); toRemove.removeAll(intersection); for (UUID uuid : toRemove) { removeDenied(uuid); } for (UUID uuid : uuids) { addDenied(uuid); } } /** * Gets the trusted users. * * @return a set of trusted users */ public @NonNull HashSet getTrusted() { if (this.trusted == null) { this.trusted = new HashSet<>(); } return this.trusted; } /** * Sets the trusted users for this plot. * * @param uuids uuids to trust */ public void setTrusted(final @NonNull Set uuids) { boolean larger = uuids.size() > getTrusted().size(); HashSet intersection = new HashSet<>(larger ? getTrusted() : uuids); intersection.retainAll(larger ? uuids : getTrusted()); uuids.removeAll(intersection); HashSet toRemove = new HashSet<>(getTrusted()); toRemove.removeAll(intersection); for (UUID uuid : toRemove) { removeTrusted(uuid); } for (UUID uuid : uuids) { addTrusted(uuid); } } /** * Gets the members * * @return a set of members */ public @NonNull HashSet getMembers() { if (this.members == null) { this.members = new HashSet<>(); } return this.members; } /** * Sets the members for this plot. * * @param uuids uuids to set member status for */ public void setMembers(final @NonNull Set uuids) { boolean larger = uuids.size() > getMembers().size(); HashSet intersection = new HashSet<>(larger ? getMembers() : uuids); intersection.retainAll(larger ? uuids : getMembers()); uuids.removeAll(intersection); HashSet toRemove = new HashSet<>(getMembers()); toRemove.removeAll(intersection); for (UUID uuid : toRemove) { removeMember(uuid); } for (UUID uuid : uuids) { addMember(uuid); } } /** * Denies a player from this plot. (updates database as well) * * @param uuid the uuid of the player to deny. */ public void addDenied(final @NonNull UUID uuid) { for (final Plot current : getConnectedPlots()) { if (current.getDenied().add(uuid)) { DBFunc.setDenied(current, uuid); } } } /** * Add someone as a helper (updates database as well) * * @param uuid the uuid of the player to trust */ public void addTrusted(final @NonNull UUID uuid) { for (final Plot current : getConnectedPlots()) { if (current.getTrusted().add(uuid)) { DBFunc.setTrusted(current, uuid); } } } /** * Add someone as a trusted user (updates database as well) * * @param uuid the uuid of the player to add as a member */ public void addMember(final @NonNull UUID uuid) { for (final Plot current : getConnectedPlots()) { if (current.getMembers().add(uuid)) { DBFunc.setMember(current, uuid); } } } /** * Sets the plot owner (and update the database) * * @param owner uuid to set as owner * @param initiator player initiating set owner * @return boolean */ public boolean setOwner(UUID owner, PlotPlayer initiator) { if (!hasOwner()) { this.setOwnerAbs(owner); this.getPlotModificationManager().create(); return true; } if (!isMerged()) { if (!owner.equals(this.getOwnerAbs())) { this.setOwnerAbs(owner); DBFunc.setOwner(this, owner); } return true; } for (final Plot current : getConnectedPlots()) { if (!owner.equals(current.getOwnerAbs())) { current.setOwnerAbs(owner); DBFunc.setOwner(current, owner); } } return true; } public boolean isLoaded() { return this.worldUtil.isWorld(getWorldName()); } /** * This will return null if the plot hasn't been analyzed * * @param settings The set of settings to obtain the analysis of * @return analysis of plot */ public PlotAnalysis getComplexity(Settings.Auto_Clear settings) { return PlotAnalysis.getAnalysis(this, settings); } /** * Get an immutable view of all the flags associated with the plot. * * @return Immutable set containing the flags associated with the plot */ public Set> getFlags() { return ImmutableSet.copyOf(flagContainer.getFlagMap().values()); } /** * Sets a flag for the plot and stores it in the database. * * @param flag Flag to set * @param flag value type * @return A boolean indicating whether or not the operation succeeded */ public boolean setFlag(final @NonNull PlotFlag flag) { if (flag instanceof KeepFlag && ExpireManager.IMP != null) { ExpireManager.IMP.updateExpired(this); } for (final Plot plot : this.getConnectedPlots()) { plot.getFlagContainer().addFlag(flag); plot.reEnter(); DBFunc.setFlag(plot, flag); } return true; } /** * Parse the flag value into a flag instance based on the provided * flag class, and store it in the database. * * @param flag Flag type * @param value Flag value * @return A boolean indicating whether or not the operation succeeded */ public boolean setFlag(final @NonNull Class flag, final @NonNull String value) { try { this.setFlag(GlobalFlagContainer.getInstance().getFlagErased(flag).parse(value)); } catch (final Exception e) { return false; } return true; } /** * Remove a flag from this plot * * @param flag the flag to remove * @return success */ public boolean removeFlag(final @NonNull Class> flag) { return this.removeFlag(getFlagContainer().queryLocal(flag)); } /** * Get flags associated with the plot. * * @param plotOnly Whether or not to only consider the plot. If this parameter is set to * true, the default values of the owning plot area will not be considered * @param ignorePluginFlags Whether or not to ignore {@link InternalFlag internal flags} * @return Collection containing all the flags that matched the given criteria */ public Collection> getApplicableFlags(final boolean plotOnly, final boolean ignorePluginFlags) { if (!hasOwner()) { return Collections.emptyList(); } final Map, PlotFlag> flags = new HashMap<>(); if (!plotOnly && getArea() != null && !getArea().getFlagContainer().getFlagMap().isEmpty()) { final Map, PlotFlag> flagMap = getArea().getFlagContainer().getFlagMap(); flags.putAll(flagMap); } final Map, PlotFlag> flagMap = getFlagContainer().getFlagMap(); if (ignorePluginFlags) { for (final PlotFlag flag : flagMap.values()) { if (flag instanceof InternalFlag) { continue; } flags.put(flag.getClass(), flag); } } else { flags.putAll(flagMap); } return flags.values(); } /** * Get flags associated with the plot and the plot area that contains it. * * @param ignorePluginFlags Whether or not to ignore {@link InternalFlag internal flags} * @return Collection containing all the flags that matched the given criteria */ public Collection> getApplicableFlags(final boolean ignorePluginFlags) { return getApplicableFlags(false, ignorePluginFlags); } /** * Remove a flag from this plot * * @param flag the flag to remove * @return success */ public boolean removeFlag(final @NonNull PlotFlag flag) { if (flag == null || origin == null) { return false; } boolean removed = false; for (final Plot plot : origin.getConnectedPlots()) { final Object value = plot.getFlagContainer().removeFlag(flag); if (value == null) { continue; } plot.reEnter(); DBFunc.removeFlag(plot, flag); removed = true; } return removed; } /** * Count the entities in a plot * * @return array of entity counts * @see RegionManager#countEntities(Plot) */ public int[] countEntities() { int[] count = new int[6]; for (Plot current : this.getConnectedPlots()) { int[] result = this.regionManager.countEntities(current); count[CAP_ENTITY] += result[CAP_ENTITY]; count[CAP_ANIMAL] += result[CAP_ANIMAL]; count[CAP_MONSTER] += result[CAP_MONSTER]; count[CAP_MOB] += result[CAP_MOB]; count[CAP_VEHICLE] += result[CAP_VEHICLE]; count[CAP_MISC] += result[CAP_MISC]; } return count; } /** * Returns true if a previous task was running * * @return {@code true} if a previous task is running */ public int addRunning() { int value = this.getRunning(); for (Plot plot : this.getConnectedPlots()) { plot.setMeta("running", value + 1); } return value; } /** * Decrement the number of tracked tasks this plot is running
* - Used to track/limit the number of things a player can do on the plot at once * * @return previous number of tasks (int) */ public int removeRunning() { int value = this.getRunning(); if (value < 2) { for (Plot plot : this.getConnectedPlots()) { plot.deleteMeta("running"); } } else { for (Plot plot : this.getConnectedPlots()) { plot.setMeta("running", value - 1); } } return value; } /** * Gets the number of tracked running tasks for this plot
* - Used to track/limit the number of things a player can do on the plot at once * * @return number of tasks (int) */ public int getRunning() { Integer value = (Integer) this.getMeta("running"); return value == null ? 0 : value; } /** * Unclaim the plot (does not modify terrain). Changes made to this plot will not be reflected in unclaimed plot objects. * * @return {@code false} if the Plot has no owner, otherwise {@code true}. */ public boolean unclaim() { if (!this.hasOwner()) { return false; } for (Plot current : getConnectedPlots()) { List> players = current.getPlayersInPlot(); for (PlotPlayer pp : players) { this.plotListener.plotExit(pp, current); } if (Settings.Backup.DELETE_ON_UNCLAIM) { // Destroy all backups when the plot is unclaimed Objects.requireNonNull(PlotSquared.platform()).backupManager().getProfile(current).destroy(); } getArea().removePlot(getId()); DBFunc.delete(current); current.setOwnerAbs(null); current.settings = null; for (final PlotPlayer pp : players) { this.plotListener.plotEntry(pp, current); } } return true; } public void getCenter(final Consumer result) { Location[] corners = getCorners(); Location top = corners[0]; Location bot = corners[1]; Location location = Location.at( this.getWorldName(), MathMan.average(bot.getX(), top.getX()), MathMan.average(bot.getY(), top.getY()), MathMan.average(bot.getZ(), top.getZ()) ); this.worldUtil.getHighestBlock(getWorldName(), location.getX(), location.getZ(), y -> { int height = y; if (area.allowSigns()) { height = Math.max(y, getManager().getSignLoc(this).getY()); } result.accept(location.withY(1 + height)); }); } /** * @return Location of center * @deprecated May cause synchronous chunk loads */ @Deprecated public Location getCenterSynchronous() { Location[] corners = getCorners(); Location top = corners[0]; Location bot = corners[1]; if (!isLoaded()) { return Location.at( "", 0, this.getArea() instanceof ClassicPlotWorld ? ((ClassicPlotWorld) this.getArea()).PLOT_HEIGHT + 1 : 4, 0 ); } Location location = Location.at( this.getWorldName(), MathMan.average(bot.getX(), top.getX()), MathMan.average(bot.getY(), top.getY()), MathMan.average(bot.getZ(), top.getZ()) ); int y = this.worldUtil.getHighestBlockSynchronous(getWorldName(), location.getX(), location.getZ()); if (area.allowSigns()) { y = Math.max(y, getManager().getSignLoc(this).getY()); } return location.withY(1 + y); } /** * @return side where players should teleport to * @deprecated May cause synchronous chunk loads */ @Deprecated public Location getSideSynchronous() { CuboidRegion largest = getLargestRegion(); int x = (largest.getMaximumPoint().getX() >> 1) - (largest.getMinimumPoint().getX() >> 1) + largest .getMinimumPoint() .getX(); int z = largest.getMinimumPoint().getZ() - 1; PlotManager manager = getManager(); int y = isLoaded() ? this.worldUtil.getHighestBlockSynchronous(getWorldName(), x, z) : 62; if (area.allowSigns() && (y <= area.getMinGenHeight() || y >= area.getMaxGenHeight())) { y = Math.max(y, manager.getSignLoc(this).getY() - 1); } return Location.at(getWorldName(), x, y + 1, z); } public void getSide(Consumer result) { CuboidRegion largest = getLargestRegion(); int x = (largest.getMaximumPoint().getX() >> 1) - (largest.getMinimumPoint().getX() >> 1) + largest .getMinimumPoint() .getX(); int z = largest.getMinimumPoint().getZ() - 1; PlotManager manager = getManager(); if (isLoaded()) { this.worldUtil.getHighestBlock(getWorldName(), x, z, y -> { int height = y; if (area.allowSigns() && (y <= area.getMinGenHeight() || y >= area.getMaxGenHeight())) { height = Math.max(y, manager.getSignLoc(this).getY() - 1); } result.accept(Location.at(getWorldName(), x, height + 1, z)); }); } else { int y = 62; if (area.allowSigns()) { y = Math.max(y, manager.getSignLoc(this).getY() - 1); } result.accept(Location.at(getWorldName(), x, y + 1, z)); } } /** * @return the plot home location * @deprecated May cause synchronous chunk loading */ @Deprecated public Location getHomeSynchronous() { BlockLoc home = this.getPosition(); if (home == null || home.getX() == 0 && home.getZ() == 0) { return this.getDefaultHomeSynchronous(true); } else { Location bottom = this.getBottomAbs(); if (!isLoaded()) { return Location.at( "", 0, this.getArea() instanceof ClassicPlotWorld ? ((ClassicPlotWorld) this.getArea()).PLOT_HEIGHT + 1 : 4, 0 ); } Location location = Location .at( bottom.getWorldName(), bottom.getX() + home.getX(), bottom.getY() + home.getY(), bottom.getZ() + home.getZ(), home.getYaw(), home.getPitch() ); if (!this.worldUtil.getBlockSynchronous(location).getBlockType().getMaterial().isAir()) { location = location.withY( Math.max(1 + this.worldUtil.getHighestBlockSynchronous( this.getWorldName(), location.getX(), location.getZ() ), bottom.getY())); } return location; } } /** * Return the home location for the plot * * @param result consumer to pass location to when found */ public void getHome(final Consumer result) { BlockLoc home = this.getPosition(); if (home == null || home.getX() == 0 && home.getZ() == 0) { this.getDefaultHome(result); } else { if (!isLoaded()) { result.accept(Location.at( "", 0, this.getArea() instanceof ClassicPlotWorld ? ((ClassicPlotWorld) this.getArea()).PLOT_HEIGHT + 1 : 4, 0 )); return; } Location bottom = this.getBottomAbs(); Location location = Location .at( bottom.getWorldName(), bottom.getX() + home.getX(), home.getY(), // y is absolute bottom.getZ() + home.getZ(), home.getYaw(), home.getPitch() ); this.worldUtil.getBlock(location, block -> { if (!block.getBlockType().getMaterial().isAir()) { this.worldUtil.getHighestBlock(this.getWorldName(), location.getX(), location.getZ(), y -> result.accept(location.withY(Math.max(1 + y, bottom.getY()))) ); } else { result.accept(location); } }); } } /** * Sets the home location * * @param location location to set as home */ public void setHome(BlockLoc location) { Plot plot = this.getBasePlot(false); if (BlockLoc.ZERO.equals(location) || BlockLoc.MINY.equals(location)) { return; } plot.getSettings().setPosition(location); if (location != null) { DBFunc.setPosition(plot, plot.getSettings().getPosition().toString()); return; } DBFunc.setPosition(plot, null); } /** * Gets the default home location for a plot
* - Ignores any home location set for that specific plot * * @param result consumer to pass location to when found */ public void getDefaultHome(Consumer result) { getDefaultHome(false, result); } /** * @param member if to get the home for plot members * @return location of home for members or visitors * @deprecated May cause synchronous chunk loads */ @Deprecated public Location getDefaultHomeSynchronous(final boolean member) { Plot plot = this.getBasePlot(false); BlockLoc loc = member ? area.defaultHome() : area.nonmemberHome(); if (loc != null) { int x; int z; if (loc.getX() == Integer.MAX_VALUE && loc.getZ() == Integer.MAX_VALUE) { // center if (getArea() instanceof SinglePlotArea) { int y = loc.getY() == Integer.MIN_VALUE ? (isLoaded() ? this.worldUtil.getHighestBlockSynchronous(plot.getWorldName(), 0, 0) + 1 : 63) : loc.getY(); return Location.at(plot.getWorldName(), 0, y, 0, 0, 0); } CuboidRegion largest = plot.getLargestRegion(); x = (largest.getMaximumPoint().getX() >> 1) - (largest.getMinimumPoint().getX() >> 1) + largest .getMinimumPoint() .getX(); z = (largest.getMaximumPoint().getZ() >> 1) - (largest.getMinimumPoint().getZ() >> 1) + largest .getMinimumPoint() .getZ(); } else { // specific Location bot = plot.getBottomAbs(); x = bot.getX() + loc.getX(); z = bot.getZ() + loc.getZ(); } int y = loc.getY() == Integer.MIN_VALUE ? (isLoaded() ? this.worldUtil.getHighestBlockSynchronous(plot.getWorldName(), x, z) + 1 : 63) : loc.getY(); return Location.at(plot.getWorldName(), x, y, z, loc.getYaw(), loc.getPitch()); } if (getArea() instanceof SinglePlotArea) { int y = isLoaded() ? this.worldUtil.getHighestBlockSynchronous(plot.getWorldName(), 0, 0) + 1 : 63; return Location.at(plot.getWorldName(), 0, y, 0, 0, 0); } // Side return plot.getSideSynchronous(); } public void getDefaultHome(boolean member, Consumer result) { Plot plot = this.getBasePlot(false); if (!isLoaded()) { result.accept(Location.at( "", 0, this.getArea() instanceof ClassicPlotWorld ? ((ClassicPlotWorld) this.getArea()).PLOT_HEIGHT + 1 : 4, 0 )); return; } BlockLoc loc = member ? area.defaultHome() : area.nonmemberHome(); if (loc != null) { int x; int z; if (loc.getX() == Integer.MAX_VALUE && loc.getZ() == Integer.MAX_VALUE) { // center if (getArea() instanceof SinglePlotArea) { x = 0; z = 0; } else { CuboidRegion largest = plot.getLargestRegion(); x = (largest.getMaximumPoint().getX() >> 1) - (largest.getMinimumPoint().getX() >> 1) + largest .getMinimumPoint() .getX(); z = (largest.getMaximumPoint().getZ() >> 1) - (largest.getMinimumPoint().getZ() >> 1) + largest .getMinimumPoint() .getZ(); } } else { // specific Location bot = plot.getBottomAbs(); x = bot.getX() + loc.getX(); z = bot.getZ() + loc.getZ(); } if (loc.getY() == Integer.MIN_VALUE) { if (isLoaded()) { this.worldUtil.getHighestBlock( plot.getWorldName(), x, z, y -> result.accept(Location.at(plot.getWorldName(), x, y + 1, z)) ); } else { int y = this.getArea() instanceof ClassicPlotWorld ? ((ClassicPlotWorld) this.getArea()).PLOT_HEIGHT + 1 : 63; result.accept(Location.at(plot.getWorldName(), x, y, z, loc.getYaw(), loc.getPitch())); } } else { result.accept(Location.at(plot.getWorldName(), x, loc.getY(), z, loc.getYaw(), loc.getPitch())); } return; } // Side if (getArea() instanceof SinglePlotArea) { int y = isLoaded() ? this.worldUtil.getHighestBlockSynchronous(plot.getWorldName(), 0, 0) + 1 : 63; result.accept(Location.at(plot.getWorldName(), 0, y, 0, 0, 0)); } plot.getSide(result); } public double getVolume() { double count = 0; for (CuboidRegion region : getRegions()) { // CuboidRegion#getArea is deprecated and we want to ensure use of correct height count += region.getLength() * region.getWidth() * (area.getMaxGenHeight() - area.getMinGenHeight() + 1); } return count; } /** * Gets the average rating of the plot. This is the value displayed in /plot info * * @return average rating as double, {@link Double#NaN} of no ratings exist */ public double getAverageRating() { Collection ratings = this.getRatings().values(); double sum = ratings.stream().mapToDouble(Rating::getAverageRating).sum(); return sum / ratings.size(); } /** * Sets a rating for a user
* - If the user has already rated, the following will return false * * @param uuid uuid of rater * @param rating rating * @return success */ public boolean addRating(UUID uuid, Rating rating) { Plot base = this.getBasePlot(false); PlotSettings baseSettings = base.getSettings(); if (baseSettings.getRatings().containsKey(uuid)) { return false; } int aggregate = rating.getAggregate(); baseSettings.getRatings().put(uuid, aggregate); DBFunc.setRating(base, uuid, aggregate); return true; } /** * Clear the ratings/likes for this plot */ public void clearRatings() { Plot base = this.getBasePlot(false); PlotSettings baseSettings = base.getSettings(); if (baseSettings.getRatings() != null && !baseSettings.getRatings().isEmpty()) { DBFunc.deleteRatings(base); baseSettings.setRatings(null); } } public Map getLikes() { final Map map = new HashMap<>(); final Map ratings = this.getRatings(); ratings.forEach((uuid, rating) -> map.put(uuid, rating.getLike())); return map; } /** * Gets the ratings associated with a plot
* - The rating object may contain multiple categories * * @return Map of user who rated to the rating */ public HashMap getRatings() { Plot base = this.getBasePlot(false); HashMap map = new HashMap<>(); if (!base.hasRatings()) { return map; } for (Entry entry : base.getSettings().getRatings().entrySet()) { map.put(entry.getKey(), new Rating(entry.getValue())); } return map; } public boolean hasRatings() { Plot base = this.getBasePlot(false); return base.settings != null && base.settings.getRatings() != null; } @Deprecated(forRemoval = true, since = "6.1.0") public boolean claim(final @NonNull PlotPlayer player, boolean teleport, String schematic) { if (!canClaim(player)) { return false; } return claim(player, teleport, schematic, true); } @Deprecated(forRemoval = true, since = "6.1.0") public boolean claim(final @NonNull PlotPlayer player, boolean teleport, String schematic, boolean updateDB) { return claim(player, teleport, schematic, updateDB, false); } /** * Claim the plot * * @param player The player to set the owner to * @param teleport If the player should be teleported * @param schematic The schematic name to paste on the plot * @param updateDB If the database should be updated * @param auto If the plot is being claimed by a /plot auto * @return success * @since 6.1.0 */ public boolean claim( final @NonNull PlotPlayer player, boolean teleport, String schematic, boolean updateDB, boolean auto ) { this.eventDispatcher.callPlotClaimedNotify(this, auto); if (updateDB) { if (!this.getPlotModificationManager().create(player.getUUID(), true)) { LOGGER.error("Player {} attempted to claim plot {}, but the database failed to update", player.getName(), this.getId().toCommaSeparatedString() ); return false; } } else { area.addPlot(this); updateWorldBorder(); } player.sendMessage(TranslatableCaption.of("working.claimed"), Template.of("plot", this.getId().toString())); if (teleport) { if (!auto && Settings.Teleport.ON_CLAIM) { teleportPlayer(player, TeleportCause.COMMAND_CLAIM, result -> { }); } else if (auto && Settings.Teleport.ON_AUTO) { teleportPlayer(player, TeleportCause.COMMAND_AUTO, result -> { }); } } PlotArea plotworld = getArea(); if (plotworld.isSchematicOnClaim()) { Schematic sch; try { if (schematic == null || schematic.isEmpty()) { sch = schematicHandler.getSchematic(plotworld.getSchematicFile()); } else { sch = schematicHandler.getSchematic(schematic); if (sch == null) { sch = schematicHandler.getSchematic(plotworld.getSchematicFile()); } } } catch (SchematicHandler.UnsupportedFormatException e) { e.printStackTrace(); return true; } schematicHandler.paste( sch, this, 0, getArea().getMinBuildHeight(), 0, Settings.Schematics.PASTE_ON_TOP, player, new RunnableVal<>() { @Override public void run(Boolean value) { if (value) { player.sendMessage(TranslatableCaption.of("schematics.schematic_paste_success")); } else { player.sendMessage(TranslatableCaption.of("schematics.schematic_paste_failed")); } } } ); } plotworld.getPlotManager().claimPlot(this, null); this.getPlotModificationManager().setSign(player.getName()); return true; } /** * Retrieve the biome of the plot. * * @param result consumer to pass biome to when found */ public void getBiome(Consumer result) { this.getCenter(location -> this.worldUtil.getBiome(location.getWorldName(), location.getX(), location.getZ(), result)); } //TODO Better documentation needed. /** * @return biome at center of plot * @deprecated May cause synchronous chunk loads */ @Deprecated public BiomeType getBiomeSynchronous() { final Location location = this.getCenterSynchronous(); return this.worldUtil.getBiomeSynchronous(location.getWorldName(), location.getX(), location.getZ()); } /** * Returns the top location for the plot. * * @return location of Absolute Top */ public Location getTopAbs() { return this.getManager().getPlotTopLocAbs(this.id).withWorld(this.getWorldName()); } /** * Returns the bottom location for the plot. * * @return location of absolute bottom of plot */ public Location getBottomAbs() { return this.getManager().getPlotBottomLocAbs(this.id).withWorld(this.getWorldName()); } /** * Swaps the settings for two plots. * * @param plot the plot to swap data with * @return Future containing the result */ public CompletableFuture swapData(Plot plot) { if (!this.hasOwner()) { if (plot != null && plot.hasOwner()) { plot.moveData(this, null); return CompletableFuture.completedFuture(true); } return CompletableFuture.completedFuture(false); } if (plot == null || plot.getOwner() == null) { this.moveData(plot, null); return CompletableFuture.completedFuture(true); } // Swap cached final PlotId temp = PlotId.of(this.getId().getX(), this.getId().getY()); this.id = plot.getId().copy(); plot.id = temp.copy(); this.area.removePlot(this.getId()); plot.area.removePlot(plot.getId()); this.area.addPlotAbs(this); plot.area.addPlotAbs(plot); // Swap database return DBFunc.swapPlots(plot, this); } /** * Moves the settings for a plot. * * @param plot the plot to move * @param whenDone task to run when settings have been moved * @return success or not */ public boolean moveData(Plot plot, Runnable whenDone) { if (!this.hasOwner()) { TaskManager.runTask(whenDone); return false; } if (plot.hasOwner()) { TaskManager.runTask(whenDone); return false; } this.area.removePlot(this.id); this.id = plot.getId().copy(); this.area.addPlotAbs(this); DBFunc.movePlot(this, plot); TaskManager.runTaskLater(whenDone, TaskTime.ticks(1L)); return true; } /** * Gets the top loc of a plot (if mega, returns top loc of that mega plot) - If you would like each plot treated as * a small plot use {@link #getTopAbs()} * * @return Location top of mega plot */ public Location getExtendedTopAbs() { Location top = this.getTopAbs(); if (!this.isMerged()) { return top; } if (this.isMerged(Direction.SOUTH)) { top = top.withZ(this.getRelative(Direction.SOUTH).getBottomAbs().getZ() - 1); } if (this.isMerged(Direction.EAST)) { top = top.withX(this.getRelative(Direction.EAST).getBottomAbs().getX() - 1); } return top; } /** * Gets the bot loc of a plot (if mega, returns bot loc of that mega plot) - If you would like each plot treated as * a small plot use {@link #getBottomAbs()} * * @return Location bottom of mega plot */ public Location getExtendedBottomAbs() { Location bot = this.getBottomAbs(); if (!this.isMerged()) { return bot; } if (this.isMerged(Direction.NORTH)) { bot = bot.withZ(this.getRelative(Direction.NORTH).getTopAbs().getZ() + 1); } if (this.isMerged(Direction.WEST)) { bot = bot.withX(this.getRelative(Direction.WEST).getTopAbs().getX() + 1); } return bot; } /** * Returns the top and bottom location.
* - If the plot is not connected, it will return its own corners
* - the returned locations will not necessarily correspond to claimed plots if the connected plots do not form a rectangular shape * * @return new Location[] { bottom, top } * @deprecated as merged plots no longer need to be rectangular */ @Deprecated public Location[] getCorners() { if (!this.isMerged()) { return new Location[]{this.getBottomAbs(), this.getTopAbs()}; } return RegionUtil.getCorners(this.getWorldName(), this.getRegions()); } /** * @return bottom corner location * @deprecated in favor of getCorners()[0];
*/ // Won't remove as suggestion also points to deprecated method @Deprecated public Location getBottom() { return this.getCorners()[0]; } /** * @return the top corner of the plot * @deprecated in favor of getCorners()[1]; */ // Won't remove as suggestion also points to deprecated method @Deprecated public Location getTop() { return this.getCorners()[1]; } /** * Gets plot display name. * * @return alias if set, else id */ @Override public String toString() { if (this.settings != null && this.settings.getAlias().length() > 1) { return this.settings.getAlias(); } return this.area + ";" + this.id; } /** * Remove a denied player (use DBFunc as well)
* Using the * uuid will remove all users * * @param uuid uuid of player to remove from denied list * @return success or not */ public boolean removeDenied(UUID uuid) { if (uuid == DBFunc.EVERYONE && !denied.contains(uuid)) { boolean result = false; for (UUID other : new HashSet<>(getDenied())) { result = rmvDenied(other) || result; } return result; } return rmvDenied(uuid); } private boolean rmvDenied(UUID uuid) { for (Plot current : this.getConnectedPlots()) { if (current.getDenied().remove(uuid)) { DBFunc.removeDenied(current, uuid); } else { return false; } } return true; } /** * Remove a helper (use DBFunc as well)
* Using the * uuid will remove all users * * @param uuid uuid of trusted player to remove * @return success or not */ public boolean removeTrusted(UUID uuid) { if (uuid == DBFunc.EVERYONE && !trusted.contains(uuid)) { boolean result = false; for (UUID other : new HashSet<>(getTrusted())) { result = rmvTrusted(other) || result; } return result; } return rmvTrusted(uuid); } private boolean rmvTrusted(UUID uuid) { for (Plot plot : this.getConnectedPlots()) { if (plot.getTrusted().remove(uuid)) { DBFunc.removeTrusted(plot, uuid); } else { return false; } } return true; } /** * Remove a trusted user (use DBFunc as well)
* Using the * uuid will remove all users * * @param uuid uuid of player to remove * @return success or not */ public boolean removeMember(UUID uuid) { if (this.members == null) { return false; } if (uuid == DBFunc.EVERYONE && !members.contains(uuid)) { boolean result = false; for (UUID other : new HashSet<>(this.members)) { result = rmvMember(other) || result; } return result; } return rmvMember(uuid); } private boolean rmvMember(UUID uuid) { for (Plot current : this.getConnectedPlots()) { if (current.getMembers().remove(uuid)) { DBFunc.removeMember(current, uuid); } else { return false; } } return true; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (this.getClass() != obj.getClass()) { return false; } Plot other = (Plot) obj; return this.hashCode() == other.hashCode() && this.id.equals(other.id) && this.area == other.area; } /** * Gets the plot hashcode
* Note: The hashcode is unique if:
* - Plots are in the same world
* - The x,z coordinates are between Short.MIN_VALUE and Short.MAX_VALUE
* * @return integer. */ @Override public int hashCode() { return this.id.hashCode(); } /** * Gets the plot alias. * - Returns an empty string if no alias is set * * @return The plot alias */ public @NonNull String getAlias() { if (this.settings == null) { return ""; } return this.settings.getAlias(); } /** * Sets the plot alias. * * @param alias The alias */ public void setAlias(String alias) { for (Plot current : this.getConnectedPlots()) { String name = this.getSettings().getAlias(); if (alias == null) { alias = ""; } if (name.equals(alias)) { return; } current.getSettings().setAlias(alias); DBFunc.setAlias(current, alias); } } /** * Sets the raw merge data
* - Updates DB
* - Does not modify terrain
* * @param direction direction to merge the plot in * @param value if the plot is merged or not */ public void setMerged(Direction direction, boolean value) { if (this.getSettings().setMerged(direction, value)) { if (value) { Plot other = this.getRelative(direction).getBasePlot(false); if (!other.equals(this.getBasePlot(false))) { Plot base = other.id.getY() < this.id.getY() || other.id.getY() == this.id.getY() && other.id.getX() < this.id .getX() ? other : this.origin; this.origin.origin = base; other.origin = base; this.origin = base; connected_cache = null; } } else { if (this.origin != null) { this.origin.origin = null; this.origin = null; } connected_cache = null; } DBFunc.setMerged(this, this.getSettings().getMerged()); regions_cache = null; } } /** * Gets the merged array. * * @return boolean [ north, east, south, west ] */ public boolean[] getMerged() { return this.getSettings().getMerged(); } /** * Sets the raw merge data
* - Updates DB
* - Does not modify terrain
* Gets if the plot is merged in a direction
* ----------
* 0 = north
* 1 = east
* 2 = south
* 3 = west
* ----------
* Note: Diagonal merging (4-7) must be done by merging the corresponding plots. * * @param merged set the plot's merged plots */ public void setMerged(boolean[] merged) { this.getSettings().setMerged(merged); DBFunc.setMerged(this, merged); clearCache(); } public void clearCache() { connected_cache = null; regions_cache = null; if (this.origin != null) { this.origin.origin = null; this.origin = null; } } /** * Gets the set home location or 0,Integer#MIN_VALUE,0 if no location is set
* - Does not take the default home location into account * - PlotSquared will internally find the correct place to teleport to if y = Integer#MIN_VALUE when teleporting to the plot. * * @return home location */ public BlockLoc getPosition() { return this.getSettings().getPosition(); } /** * Check if a plot can be claimed by the provided player. * * @param player the claiming player * @return if the given player can claim the plot */ public boolean canClaim(@NonNull PlotPlayer player) { PlotCluster cluster = this.getCluster(); if (cluster != null) { if (!cluster.isAdded(player.getUUID()) && !Permissions.hasPermission(player, "plots.admin.command.claim")) { return false; } } final UUID owner = this.getOwnerAbs(); if (owner != null) { return false; } return !isMerged(); } /** * Merge the plot settings
* - Used when a plot is merged
* * @param plot plot to merge the data from */ public void mergeData(Plot plot) { final FlagContainer flagContainer1 = this.getFlagContainer(); final FlagContainer flagContainer2 = plot.getFlagContainer(); if (!flagContainer1.equals(flagContainer2)) { boolean greater = flagContainer1.getFlagMap().size() > flagContainer2.getFlagMap().size(); if (greater) { flagContainer1.addAll(flagContainer2.getFlagMap().values()); } else { flagContainer2.addAll(flagContainer1.getFlagMap().values()); } if (!greater) { this.flagContainer.clearLocal(); this.flagContainer.addAll(flagContainer2.getFlagMap().values()); } plot.flagContainer.clearLocal(); plot.flagContainer.addAll(this.flagContainer.getFlagMap().values()); } if (!this.getAlias().isEmpty()) { plot.setAlias(this.getAlias()); } else if (!plot.getAlias().isEmpty()) { this.setAlias(plot.getAlias()); } for (UUID uuid : this.getTrusted()) { plot.addTrusted(uuid); } for (UUID uuid : plot.getTrusted()) { this.addTrusted(uuid); } for (UUID uuid : this.getMembers()) { plot.addMember(uuid); } for (UUID uuid : plot.getMembers()) { this.addMember(uuid); } for (UUID uuid : this.getDenied()) { plot.addDenied(uuid); } for (UUID uuid : plot.getDenied()) { this.addDenied(uuid); } } /** * Gets the plot in a relative location
* Note: May be null if the partial plot area does not include the relative location * * @param x relative id X * @param y relative id Y * @return Plot */ public Plot getRelative(int x, int y) { return this.area.getPlotAbs(PlotId.of(this.id.getX() + x, this.id.getY() + y)); } public Plot getRelative(PlotArea area, int x, int y) { return area.getPlotAbs(PlotId.of(this.id.getX() + x, this.id.getY() + y)); } /** * Gets the plot in a relative direction * Note: May be null if the partial plot area does not include the relative location * * @param direction Direction * @return the plot relative to this one */ public @Nullable Plot getRelative(@NonNull Direction direction) { return this.area.getPlotAbs(this.id.getRelative(direction)); } /** * Gets a set of plots connected (and including) this plot
* - This result is cached globally * * @return a Set of Plots connected to this Plot */ public Set getConnectedPlots() { if (this.settings == null) { return Collections.singleton(this); } if (!this.isMerged()) { return Collections.singleton(this); } if (connected_cache != null && connected_cache.contains(this)) { return connected_cache; } regions_cache = null; HashSet tmpSet = new HashSet<>(); tmpSet.add(this); Plot tmp; HashSet queuecache = new HashSet<>(); ArrayDeque frontier = new ArrayDeque<>(); if (this.isMerged(Direction.NORTH)) { tmp = this.area.getPlotAbs(this.id.getRelative(Direction.NORTH)); if (!tmp.isMerged(Direction.SOUTH)) { // invalid merge if (tmp.isOwnerAbs(this.getOwnerAbs())) { tmp.getSettings().setMerged(Direction.SOUTH, true); DBFunc.setMerged(tmp, tmp.getSettings().getMerged()); } else { this.getSettings().setMerged(Direction.NORTH, false); DBFunc.setMerged(this, this.getSettings().getMerged()); } } queuecache.add(tmp); frontier.add(tmp); } if (this.isMerged(Direction.EAST)) { tmp = this.area.getPlotAbs(this.id.getRelative(Direction.EAST)); assert tmp != null; if (!tmp.isMerged(Direction.WEST)) { // invalid merge if (tmp.isOwnerAbs(this.getOwnerAbs())) { tmp.getSettings().setMerged(Direction.WEST, true); DBFunc.setMerged(tmp, tmp.getSettings().getMerged()); } else { this.getSettings().setMerged(Direction.EAST, false); DBFunc.setMerged(this, this.getSettings().getMerged()); } } queuecache.add(tmp); frontier.add(tmp); } if (this.isMerged(Direction.SOUTH)) { tmp = this.area.getPlotAbs(this.id.getRelative(Direction.SOUTH)); assert tmp != null; if (!tmp.isMerged(Direction.NORTH)) { // invalid merge if (tmp.isOwnerAbs(this.getOwnerAbs())) { tmp.getSettings().setMerged(Direction.NORTH, true); DBFunc.setMerged(tmp, tmp.getSettings().getMerged()); } else { this.getSettings().setMerged(Direction.SOUTH, false); DBFunc.setMerged(this, this.getSettings().getMerged()); } } queuecache.add(tmp); frontier.add(tmp); } if (this.isMerged(Direction.WEST)) { tmp = this.area.getPlotAbs(this.id.getRelative(Direction.WEST)); if (!tmp.isMerged(Direction.EAST)) { // invalid merge if (tmp.isOwnerAbs(this.getOwnerAbs())) { tmp.getSettings().setMerged(Direction.EAST, true); DBFunc.setMerged(tmp, tmp.getSettings().getMerged()); } else { this.getSettings().setMerged(Direction.WEST, false); DBFunc.setMerged(this, this.getSettings().getMerged()); } } queuecache.add(tmp); frontier.add(tmp); } Plot current; while ((current = frontier.poll()) != null) { if (!current.hasOwner() || current.settings == null) { continue; } tmpSet.add(current); queuecache.remove(current); if (current.isMerged(Direction.NORTH)) { tmp = current.area.getPlotAbs(current.id.getRelative(Direction.NORTH)); if (tmp != null && !queuecache.contains(tmp) && !tmpSet.contains(tmp)) { queuecache.add(tmp); frontier.add(tmp); } } if (current.isMerged(Direction.EAST)) { tmp = current.area.getPlotAbs(current.id.getRelative(Direction.EAST)); if (tmp != null && !queuecache.contains(tmp) && !tmpSet.contains(tmp)) { queuecache.add(tmp); frontier.add(tmp); } } if (current.isMerged(Direction.SOUTH)) { tmp = current.area.getPlotAbs(current.id.getRelative(Direction.SOUTH)); if (tmp != null && !queuecache.contains(tmp) && !tmpSet.contains(tmp)) { queuecache.add(tmp); frontier.add(tmp); } } if (current.isMerged(Direction.WEST)) { tmp = current.area.getPlotAbs(current.id.getRelative(Direction.WEST)); if (tmp != null && !queuecache.contains(tmp) && !tmpSet.contains(tmp)) { queuecache.add(tmp); frontier.add(tmp); } } } connected_cache = tmpSet; return tmpSet; } /** * This will combine each plot into effective rectangular regions
* - This result is cached globally
* - Useful for handling non rectangular shapes * * @return all regions within the plot */ public @NonNull Set getRegions() { if (regions_cache != null && connected_cache != null && connected_cache.contains(this)) { return regions_cache; } if (!this.isMerged()) { Location pos1 = this.getBottomAbs().withY(getArea().getMinBuildHeight()); Location pos2 = this.getTopAbs().withY(getArea().getMaxBuildHeight()); connected_cache = Sets.newHashSet(this); CuboidRegion rg = new CuboidRegion(pos1.getBlockVector3(), pos2.getBlockVector3()); regions_cache = Collections.singleton(rg); return regions_cache; } Set plots = this.getConnectedPlots(); Set regions = regions_cache = new HashSet<>(); Set visited = new HashSet<>(); for (Plot current : plots) { if (visited.contains(current.getId())) { continue; } boolean merge = true; PlotId bot = current.getId(); PlotId top = current.getId(); while (merge) { merge = false; Iterable ids = PlotId.PlotRangeIterator.range( PlotId.of(bot.getX(), bot.getY() - 1), PlotId.of(top.getX(), bot.getY() - 1) ); boolean tmp = true; for (PlotId id : ids) { Plot plot = this.area.getPlotAbs(id); if (plot == null || !plot.isMerged(Direction.SOUTH) || visited.contains(plot.getId())) { tmp = false; } } if (tmp) { merge = true; bot = PlotId.of(bot.getX(), bot.getY() - 1); } ids = PlotId.PlotRangeIterator.range( PlotId.of(top.getX() + 1, bot.getY()), PlotId.of(top.getX() + 1, top.getY()) ); tmp = true; for (PlotId id : ids) { Plot plot = this.area.getPlotAbs(id); if (plot == null || !plot.isMerged(Direction.WEST) || visited.contains(plot.getId())) { tmp = false; } } if (tmp) { merge = true; top = PlotId.of(top.getX() + 1, top.getY()); } ids = PlotId.PlotRangeIterator.range( PlotId.of(bot.getX(), top.getY() + 1), PlotId.of(top.getX(), top.getY() + 1) ); tmp = true; for (PlotId id : ids) { Plot plot = this.area.getPlotAbs(id); if (plot == null || !plot.isMerged(Direction.NORTH) || visited.contains(plot.getId())) { tmp = false; } } if (tmp) { merge = true; top = PlotId.of(top.getX(), top.getY() + 1); } ids = PlotId.PlotRangeIterator.range( PlotId.of(bot.getX() - 1, bot.getY()), PlotId.of(bot.getX() - 1, top.getY()) ); tmp = true; for (PlotId id : ids) { Plot plot = this.area.getPlotAbs(id); if (plot == null || !plot.isMerged(Direction.EAST) || visited.contains(plot.getId())) { tmp = false; } } if (tmp) { merge = true; bot = PlotId.of(bot.getX() - 1, bot.getY()); } } int minHeight = getArea().getMinBuildHeight(); int maxHeight = getArea().getMaxBuildHeight(); Location gtopabs = this.area.getPlotAbs(top).getTopAbs(); Location gbotabs = this.area.getPlotAbs(bot).getBottomAbs(); visited.addAll(Lists.newArrayList((Iterable) PlotId.PlotRangeIterator.range(bot, top))); for (int x = bot.getX(); x <= top.getX(); x++) { Plot plot = this.area.getPlotAbs(PlotId.of(x, top.getY())); if (plot.isMerged(Direction.SOUTH)) { // south wedge Location toploc = plot.getExtendedTopAbs(); Location botabs = plot.getBottomAbs(); Location topabs = plot.getTopAbs(); BlockVector3 pos1 = BlockVector3.at(botabs.getX(), minHeight, topabs.getZ() + 1); BlockVector3 pos2 = BlockVector3.at(topabs.getX(), maxHeight, toploc.getZ()); regions.add(new CuboidRegion(pos1, pos2)); if (plot.isMerged(Direction.SOUTHEAST)) { pos1 = BlockVector3.at(topabs.getX() + 1, minHeight, topabs.getZ() + 1); pos2 = BlockVector3.at(toploc.getX(), maxHeight, toploc.getZ()); regions.add(new CuboidRegion(pos1, pos2)); // intersection } } } for (int y = bot.getY(); y <= top.getY(); y++) { Plot plot = this.area.getPlotAbs(PlotId.of(top.getX(), y)); if (plot.isMerged(Direction.EAST)) { // east wedge Location toploc = plot.getExtendedTopAbs(); Location botabs = plot.getBottomAbs(); Location topabs = plot.getTopAbs(); BlockVector3 pos1 = BlockVector3.at(topabs.getX() + 1, minHeight, botabs.getZ()); BlockVector3 pos2 = BlockVector3.at(toploc.getX(), maxHeight, topabs.getZ()); regions.add(new CuboidRegion(pos1, pos2)); if (plot.isMerged(Direction.SOUTHEAST)) { pos1 = BlockVector3.at(topabs.getX() + 1, minHeight, topabs.getZ() + 1); pos2 = BlockVector3.at(toploc.getX(), maxHeight, toploc.getZ()); regions.add(new CuboidRegion(pos1, pos2)); // intersection } } } BlockVector3 pos1 = BlockVector3.at(gbotabs.getX(), minHeight, gbotabs.getZ()); BlockVector3 pos2 = BlockVector3.at(gtopabs.getX(), maxHeight, gtopabs.getZ()); regions.add(new CuboidRegion(pos1, pos2)); } return regions; } /** * Attempt to find the largest rectangular region in a plot (as plots can form non rectangular shapes) * * @return the plot's largest CuboidRegion */ public CuboidRegion getLargestRegion() { Set regions = this.getRegions(); CuboidRegion max = null; double area = Double.NEGATIVE_INFINITY; for (CuboidRegion region : regions) { double current = (region.getMaximumPoint().getX() - (double) region.getMinimumPoint().getX() + 1) * ( region.getMaximumPoint().getZ() - (double) region.getMinimumPoint().getZ() + 1); if (current > area) { max = region; area = current; } } return max; } /** * Do the plot entry tasks for each player in the plot
* - Usually called when the plot state changes (unclaimed/claimed/flag change etc) */ public void reEnter() { TaskManager.runTaskLater(() -> { for (PlotPlayer pp : Plot.this.getPlayersInPlot()) { this.plotListener.plotExit(pp, Plot.this); this.plotListener.plotEntry(pp, Plot.this); } }, TaskTime.ticks(1L)); } public void debug(final @NonNull String message) { try { final Collection> players = PlotPlayer.getDebugModePlayersInPlot(this); if (players.isEmpty()) { return; } Caption caption = TranslatableCaption.of("debug.plot_debug"); Template plotTemplate = Template.of("plot", this.toString()); Template messageTemplate = Template.of("message", message); for (final PlotPlayer player : players) { if (isOwner(player.getUUID()) || Permissions.hasPermission(player, Permission.PERMISSION_ADMIN_DEBUG_OTHER)) { player.sendMessage(caption, plotTemplate, messageTemplate); } } } catch (final Exception ignored) { } } /** * Teleport a player to a plot and send them the teleport message. * * @param player the player * @param result Called with the result of the teleportation */ public void teleportPlayer(final PlotPlayer player, Consumer result) { teleportPlayer(player, TeleportCause.PLUGIN, result); } /** * Teleport a player to a plot and send them the teleport message. * * @param player the player * @param cause the cause of the teleport * @param resultConsumer Called with the result of the teleportation */ public void teleportPlayer(final PlotPlayer player, TeleportCause cause, Consumer resultConsumer) { Plot plot = this.getBasePlot(false); Result result = this.eventDispatcher.callTeleport(player, player.getLocation(), plot, cause).getEventResult(); if (result == Result.DENY) { player.sendMessage( TranslatableCaption.of("events.event_denied"), Template.of("value", "Teleport") ); resultConsumer.accept(false); return; } final Consumer locationConsumer = location -> { if (Settings.Teleport.DELAY == 0 || Permissions .hasPermission(player, "plots.teleport.delay.bypass")) { player.sendMessage(TranslatableCaption.of("teleport.teleported_to_plot")); player.teleport(location, cause); resultConsumer.accept(true); return; } player.sendMessage( TranslatableCaption.of("teleport.teleport_in_seconds"), Template.of("amount", String.valueOf(Settings.Teleport.DELAY)) ); final String name = player.getName(); TaskManager.addToTeleportQueue(name); TaskManager.runTaskLater(() -> { if (!TaskManager.removeFromTeleportQueue(name)) { return; } try { player.sendMessage(TranslatableCaption.of("teleport.teleported_to_plot")); player.teleport(location, cause); } catch (final Exception ignored) { } }, TaskTime.seconds(Settings.Teleport.DELAY)); resultConsumer.accept(true); }; if (this.area.isHomeAllowNonmember() || plot.isAdded(player.getUUID())) { this.getHome(locationConsumer); } else { this.getDefaultHome(false, locationConsumer); } } /** * Checks if the owner of this Plot is online. * * @return {@code true} if the owner of the Plot is online */ public boolean isOnline() { if (!this.hasOwner()) { return false; } if (!isMerged()) { return PlotSquared.platform().playerManager().getPlayerIfExists(Objects.requireNonNull(this.getOwnerAbs())) != null; } for (final Plot current : getConnectedPlots()) { if (current.hasOwner() && PlotSquared .platform() .playerManager() .getPlayerIfExists(Objects.requireNonNull(current.getOwnerAbs())) != null) { return true; } } return false; } public int getDistanceFromOrigin() { Location bot = getManager().getPlotBottomLocAbs(id); Location top = getManager().getPlotTopLocAbs(id); return Math.max( Math.max(Math.abs(bot.getX()), Math.abs(bot.getZ())), Math.max(Math.abs(top.getX()), Math.abs(top.getZ())) ); } /** * Expands the world border to include this plot if it is beyond the current border. */ public void updateWorldBorder() { int border = this.area.getBorder(); if (border == Integer.MAX_VALUE) { return; } int max = getDistanceFromOrigin(); if (max > border) { this.area.setMeta("worldBorder", max); } } /** * Merges two plots.
- Assumes plots are directly next to each other
- saves to DB * * @param lesserPlot the plot to merge into this plot instance * @param removeRoads if roads should be removed during the merge * @param queue Nullable {@link QueueCoordinator}. If null, creates own queue and enqueues, * otherwise writes to the queue but does not enqueue. */ public void mergePlot(Plot lesserPlot, boolean removeRoads, @Nullable QueueCoordinator queue) { Plot greaterPlot = this; lesserPlot.getPlotModificationManager().removeSign(); if (lesserPlot.getId().getX() == greaterPlot.getId().getX()) { if (lesserPlot.getId().getY() > greaterPlot.getId().getY()) { Plot tmp = lesserPlot; lesserPlot = greaterPlot; greaterPlot = tmp; } if (!lesserPlot.isMerged(Direction.SOUTH)) { lesserPlot.clearRatings(); greaterPlot.clearRatings(); lesserPlot.setMerged(Direction.SOUTH, true); greaterPlot.setMerged(Direction.NORTH, true); lesserPlot.mergeData(greaterPlot); if (removeRoads) { //lesserPlot.removeSign(); lesserPlot.getPlotModificationManager().removeRoadSouth(queue); Plot diagonal = greaterPlot.getRelative(Direction.EAST); if (diagonal.isMerged(Direction.NORTHWEST)) { lesserPlot.plotModificationManager.removeRoadSouthEast(queue); } Plot below = greaterPlot.getRelative(Direction.WEST); if (below.isMerged(Direction.NORTHEAST)) { below.getRelative(Direction.NORTH).plotModificationManager.removeRoadSouthEast(queue); } } } } else { if (lesserPlot.getId().getX() > greaterPlot.getId().getX()) { Plot tmp = lesserPlot; lesserPlot = greaterPlot; greaterPlot = tmp; } if (!lesserPlot.isMerged(Direction.EAST)) { lesserPlot.clearRatings(); greaterPlot.clearRatings(); lesserPlot.setMerged(Direction.EAST, true); greaterPlot.setMerged(Direction.WEST, true); lesserPlot.mergeData(greaterPlot); if (removeRoads) { //lesserPlot.removeSign(); Plot diagonal = greaterPlot.getRelative(Direction.SOUTH); if (diagonal.isMerged(Direction.NORTHWEST)) { lesserPlot.plotModificationManager.removeRoadSouthEast(queue); } lesserPlot.plotModificationManager.removeRoadEast(queue); } Plot below = greaterPlot.getRelative(Direction.NORTH); if (below.isMerged(Direction.SOUTHWEST)) { below.getRelative(Direction.WEST).getPlotModificationManager().removeRoadSouthEast(queue); } } } } /** * Check if the plot is merged in a given direction * * @param direction Direction * @return {@code true} if the plot is merged in the given direction */ public boolean isMerged(final @NonNull Direction direction) { return isMerged(direction.getIndex()); } /** * Get the value associated with the specified flag. This will first look at plot * specific flag values, then at the containing plot area and its default values * and at last, it will look at the default values stored in {@link GlobalFlagContainer}. * * @param flagClass The flag type (Class) * @param the flag value type * @return The flag value */ public @NonNull T getFlag(final @NonNull Class> flagClass) { return this.flagContainer.getFlag(flagClass).getValue(); } /** * Get the value associated with the specified flag. This will first look at plot * specific flag values, then at the containing plot area and its default values * and at last, it will look at the default values stored in {@link GlobalFlagContainer}. * * @param flag The flag type (Any instance of the flag) * @param the flag type (Any instance of the flag) * @param the flag's value type * @return The flag value */ public @NonNull > T getFlag(final @NonNull V flag) { final Class flagClass = flag.getClass(); final PlotFlag flagInstance = this.flagContainer.getFlagErased(flagClass); return FlagContainer.castUnsafe(flagInstance).getValue(); } public CompletableFuture format(final Caption iInfo, PlotPlayer player, final boolean full) { final CompletableFuture future = new CompletableFuture<>(); int num = this.getConnectedPlots().size(); String alias = !this.getAlias().isEmpty() ? this.getAlias() : TranslatableCaption.of("info.none").getComponent(player); Location bot = this.getCorners()[0]; PlotSquared.platform().worldUtil().getBiome( Objects.requireNonNull(this.getWorldName()), bot.getX(), bot.getZ(), biome -> { Component trusted = PlayerManager.getPlayerList(this.getTrusted(), player); Component members = PlayerManager.getPlayerList(this.getMembers(), player); Component denied = PlayerManager.getPlayerList(this.getDenied(), player); String seen; if (Settings.Enabled_Components.PLOT_EXPIRY && ExpireManager.IMP != null) { if (this.isOnline()) { seen = TranslatableCaption.of("info.now").getComponent(player); } else { int time = (int) (ExpireManager.IMP.getAge(this, false) / 1000); if (time != 0) { seen = TimeUtil.secToTime(time); } else { seen = TranslatableCaption.of("info.unknown").getComponent(player); } } } else { seen = TranslatableCaption.of("info.never").getComponent(player); } String description = this.getFlag(DescriptionFlag.class); if (description.isEmpty()) { description = TranslatableCaption.of("info.plot_no_description").getComponent(player); } Component flags; Collection> flagCollection = this.getApplicableFlags(true); if (flagCollection.isEmpty()) { flags = MINI_MESSAGE.parse(TranslatableCaption.of("info.none").getComponent(player)); } else { TextComponent.Builder flagBuilder = Component.text(); String prefix = ""; for (final PlotFlag flag : flagCollection) { Object value; if (flag instanceof DoubleFlag && !Settings.General.SCIENTIFIC) { value = FLAG_DECIMAL_FORMAT.format(flag.getValue()); } else { value = flag.toString(); } Component snip = MINI_MESSAGE.parse( prefix + CaptionUtility.format( player, TranslatableCaption.of("info.plot_flag_list").getComponent(player) ), Template.of("flag", flag.getName()), Template.of("value", CaptionUtility.formatRaw(player, value.toString())) ); flagBuilder.append(snip); prefix = ", "; } flags = flagBuilder.build(); } boolean build = this.isAdded(player.getUUID()); Component owner; if (this.getOwner() == null) { owner = Component.text("unowned"); } else if (this.getOwner().equals(DBFunc.SERVER)) { owner = Component.text(MINI_MESSAGE.stripTokens(TranslatableCaption .of("info.server") .getComponent(player))); } else { owner = PlayerManager.getPlayerList(this.getOwners(), player); } Template headerTemplate = Template.of( "header", TranslatableCaption.of("info.plot_info_header").getComponent(player) ); Template footerTemplate = Template.of( "footer", TranslatableCaption.of("info.plot_info_footer").getComponent(player) ); Template areaTemplate; if (this.getArea() != null) { areaTemplate = Template.of( "area", this.getArea().getWorldName() + (this.getArea().getId() == null ? "" : "(" + this.getArea().getId() + ")") ); } else { areaTemplate = Template.of("area", TranslatableCaption.of("info.none").getComponent(player)); } long creationDate = Long.parseLong(String.valueOf(timestamp)); SimpleDateFormat sdf = new SimpleDateFormat(Settings.Timeformat.DATE_FORMAT); sdf.setTimeZone(TimeZone.getTimeZone(Settings.Timeformat.TIME_ZONE)); String newDate = sdf.format(creationDate); Template idTemplate = Template.of("id", this.getId().toString()); Template aliasTemplate = Template.of("alias", alias); Template numTemplate = Template.of("num", String.valueOf(num)); Template descTemplate = Template.of("desc", description); Template biomeTemplate = Template.of("biome", biome.toString().toLowerCase()); Template ownerTemplate = Template.of("owner", owner); Template membersTemplate = Template.of("members", members); Template playerTemplate = Template.of("player", player.getName()); Template trustedTemplate = Template.of("trusted", trusted); Template helpersTemplate = Template.of("helpers", members); Template deniedTemplate = Template.of("denied", denied); Template seenTemplate = Template.of("seen", seen); Template flagsTemplate = Template.of("flags", flags); Template creationTemplate = Template.of("creationdate", newDate); Template buildTemplate = Template.of("build", String.valueOf(build)); Template sizeTemplate = Template.of("size", String.valueOf(getConnectedPlots().size())); String component = iInfo.getComponent(player); if (component.contains("") || component.contains("")) { TaskManager.runTaskAsync(() -> { Template ratingTemplate; Template likesTemplate; if (Settings.Ratings.USE_LIKES) { ratingTemplate = Template.of( "rating", String.format("%.0f%%", Like.getLikesPercentage(this) * 100D) ); likesTemplate = Template.of( "likes", String.format("%.0f%%", Like.getLikesPercentage(this) * 100D) ); } else { int max = 10; if (Settings.Ratings.CATEGORIES != null && !Settings.Ratings.CATEGORIES.isEmpty()) { max = 8; } if (full && Settings.Ratings.CATEGORIES != null && Settings.Ratings.CATEGORIES.size() > 1) { double[] ratings = this.getAverageRatings(); StringBuilder rating = new StringBuilder(); String prefix = ""; for (int i = 0; i < ratings.length; i++) { rating.append(prefix).append(Settings.Ratings.CATEGORIES.get(i)).append('=') .append(String.format("%.1f", ratings[i])); prefix = ","; } ratingTemplate = Template.of("rating", rating.toString()); } else { double rating = this.getAverageRating(); if (Double.isFinite(rating)) { ratingTemplate = Template.of("rating", String.format("%.1f", rating) + '/' + max); } else { ratingTemplate = Template.of( "rating", TranslatableCaption.of("info.none").getComponent(player) ); } } likesTemplate = Template.of("likes", "N/A"); } future.complete(StaticCaption.of(MINI_MESSAGE.serialize(MINI_MESSAGE .parse( iInfo.getComponent(player), headerTemplate, areaTemplate, idTemplate, aliasTemplate, numTemplate, descTemplate, biomeTemplate, ownerTemplate, membersTemplate, playerTemplate, trustedTemplate, helpersTemplate, deniedTemplate, seenTemplate, flagsTemplate, buildTemplate, ratingTemplate, creationTemplate, sizeTemplate, likesTemplate, footerTemplate )))); }); return; } future.complete(StaticCaption.of(MINI_MESSAGE.serialize(MINI_MESSAGE .parse( iInfo.getComponent(player), headerTemplate, areaTemplate, idTemplate, aliasTemplate, numTemplate, descTemplate, biomeTemplate, ownerTemplate, membersTemplate, playerTemplate, trustedTemplate, helpersTemplate, deniedTemplate, seenTemplate, flagsTemplate, buildTemplate, creationTemplate, sizeTemplate, footerTemplate )))); } ); return future; } /** * If rating categories are enabled, get the average rating by category.
* - The index corresponds to the index of the category in the config * *

* See {@link Settings.Ratings#CATEGORIES} for rating categories *

* * @return Average ratings in each category */ public @NonNull double[] getAverageRatings() { Map rating; if (this.getSettings().getRatings() != null) { rating = this.getSettings().getRatings(); } else if (Settings.Enabled_Components.RATING_CACHE) { rating = new HashMap<>(); } else { rating = DBFunc.getRatings(this); } int size = 1; if (!Settings.Ratings.CATEGORIES.isEmpty()) { size = Math.max(1, Settings.Ratings.CATEGORIES.size()); } double[] ratings = new double[size]; if (rating == null || rating.isEmpty()) { return ratings; } for (Entry entry : rating.entrySet()) { int current = entry.getValue(); if (Settings.Ratings.CATEGORIES.isEmpty()) { ratings[0] += current; } else { for (int i = 0; i < Settings.Ratings.CATEGORIES.size(); i++) { ratings[i] += current % 10 - 1; current /= 10; } } } for (int i = 0; i < size; i++) { ratings[i] /= rating.size(); } return ratings; } /** * Get the plot flag container * * @return Flag container */ public @NonNull FlagContainer getFlagContainer() { return this.flagContainer; } /** * Get the plot comment container. This can be used to manage * and access plot comments * * @return Plot comment container */ public @NonNull PlotCommentContainer getPlotCommentContainer() { return this.plotCommentContainer; } /** * Get the plot modification manager * * @return Plot modification manager */ public @NonNull PlotModificationManager getPlotModificationManager() { return this.plotModificationManager; } }