package com.intellectualcrafters.plot.object;
import com.google.common.base.Optional;
import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableSet;
import com.intellectualcrafters.jnbt.CompoundTag;
import com.intellectualcrafters.plot.PS;
import com.intellectualcrafters.plot.config.C;
import com.intellectualcrafters.plot.config.Configuration;
import com.intellectualcrafters.plot.config.Settings;
import com.intellectualcrafters.plot.database.DBFunc;
import com.intellectualcrafters.plot.flag.Flag;
import com.intellectualcrafters.plot.flag.FlagManager;
import com.intellectualcrafters.plot.flag.Flags;
import com.intellectualcrafters.plot.generator.SquarePlotWorld;
import com.intellectualcrafters.plot.util.BO3Handler;
import com.intellectualcrafters.plot.util.ChunkManager;
import com.intellectualcrafters.plot.util.EventUtil;
import com.intellectualcrafters.plot.util.MainUtil;
import com.intellectualcrafters.plot.util.MathMan;
import com.intellectualcrafters.plot.util.Permissions;
import com.intellectualcrafters.plot.util.SchematicHandler;
import com.intellectualcrafters.plot.util.StringMan;
import com.intellectualcrafters.plot.util.TaskManager;
import com.intellectualcrafters.plot.util.UUIDHandler;
import com.intellectualcrafters.plot.util.WorldUtil;
import com.intellectualcrafters.plot.util.block.GlobalBlockQueue;
import com.intellectualcrafters.plot.util.block.LocalBlockQueue;
import com.intellectualcrafters.plot.util.expiry.ExpireManager;
import com.intellectualcrafters.plot.util.expiry.PlotAnalysis;
import com.plotsquared.listener.PlotListener;
import java.awt.geom.Area;
import java.awt.geom.PathIterator;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.net.URL;
import java.nio.charset.StandardCharsets;
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.Entry;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* The plot class<br>
* [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 raw access is deprecated
*/
@Deprecated
private static HashSet<Plot> connected_cache;
private static HashSet<RegionWrapper> regions_cache;
/**
* The {@link PlotId}.
*/
private final PlotId id;
/**
* plot owner
* (Merged plots can have multiple owners)
* Direct access is Deprecated: use getOwners()
*/
@Deprecated
public UUID owner;
/**
* Has the plot changed since the last save cycle?
*/
public boolean countsTowardsMax = true;
/**
* Represents whatever the database manager needs it to: <br>
* - A value of -1 usually indicates the plot will not be stored in the DB<br>
* - A value of 0 usually indicates that the DB manager hasn't set a value<br>
* @deprecated magical
*/
@Deprecated
public int temp;
/**
* Plot creation timestamp (not accurate if the plot was created before this was implemented)<br>
* - Milliseconds since the epoch<br>
*/
private long timestamp;
/**
* List of trusted (with plot permissions).
*/
private HashSet<UUID> trusted;
/**
* List of members users (with plot permissions).
*/
private HashSet<UUID> members;
/**
* List of denied players.
*/
private HashSet<UUID> denied;
/**
* External settings class.
* - Please favor the methods over direct access to this class<br>
* - The methods are more likely to be left unchanged from version changes<br>
*/
private PlotSettings settings;
/**
* The {@link PlotArea}.
*/
private PlotArea area;
/**
* Session only plot metadata (session is until the server stops)<br>
* <br>
* For persistent metadata use the flag system
* @see FlagManager
*/
private ConcurrentHashMap<String, Object> 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 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(PlotArea area, PlotId id, UUID owner) {
this.area = area;
this.id = id;
this.owner = owner;
}
/**
* Constructor for an unowned plot.
* (Only changes after plot.create() will be properly set in the database)
*
* @see Plot#getPlot(Location) for existing plots
*
* @param area the PlotArea where the plot is located
* @param id the plot id
*/
public Plot(PlotArea area, PlotId id) {
this.area = area;
this.id = id;
}
/**
* Constructor for a temporary plot (use -1 for temp)<br>
* 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 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
*/
public Plot(PlotArea area, PlotId id, UUID owner, int temp) {
this.area = area;
this.id = id;
this.owner = owner;
this.temp = temp;
}
/**
* Constructor for a saved plots (Used by the database manager when plots are fetched)
*
* @see Plot#getPlot(Location) for existing plots
*
* @param id the plot id
* @param owner the plot owner
* @param trusted
* @param denied
* @param merged
*/
public Plot(PlotId id, UUID owner, HashSet<UUID> trusted, HashSet<UUID> members, HashSet<UUID> denied,
String alias, BlockLoc position,
Collection<Flag> flags, PlotArea area, boolean[] merged, long timestamp, int temp) {
this.id = id;
this.area = area;
this.owner = owner;
this.settings = new PlotSettings();
this.members = members;
this.trusted = trusted;
this.denied = denied;
this.settings.setAlias(alias);
this.settings.setPosition(position);
this.settings.setMerged(merged);
if (flags != null) {
for (Flag flag : flags) {
this.settings.flags.put(flag, flag);
}
}
this.timestamp = timestamp;
this.temp = temp;
}
public String getWorldName() {
return area.worldname;
}
/**
* Get 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 Plot fromString(PlotArea defaultArea, String string) {
String[] split = string.split(";|,");
if (split.length == 2) {
if (defaultArea != null) {
PlotId id = PlotId.fromString(split[0] + ';' + split[1]);
return id != null ? defaultArea.getPlotAbs(id) : null;
}
} else if (split.length == 3) {
PlotArea pa = PS.get().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 = PS.get().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.
*
* @see PlotPlayer#getCurrentPlot() if a player is expected here.
*
* @param location the location of the plot
* @return
*/
public static Plot getPlot(Location location) {
PlotArea pa = PS.get().getPlotAreaAbs(location);
if (pa != null) {
return pa.getPlot(location);
}
return null;
}
/**
* Session only plot metadata (session is until the server stops)<br>
* <br>
* For persistent metadata use the flag system
* @see FlagManager
* @param key
* @param value
*/
public void setMeta(String key, Object value) {
if (this.meta == null) {
this.meta = new ConcurrentHashMap<>();
}
this.meta.put(key, value);
}
/**
* Get the metadata for a key<br>
* <br>
* For persistent metadata use the flag system
* @param key
* @return
*/
public Object getMeta(String key) {
if (this.meta != null) {
return this.meta.get(key);
}
return null;
}
/**
* Delete the metadata for a key<br>
* - metadata is session only
* - deleting other plugin's metadata may cause issues
* @param key
*/
public void deleteMeta(String key) {
if (this.meta != null) {
this.meta.remove(key);
}
}
/**
* Get the cluster this plot is associated with
* @return the PlotCluster object, or null
*/
public PlotCluster getCluster() {
return this.getArea().getCluster(this.id);
}
/**
* Efficiently get the players currently inside this plot<br>
* - Will return an empty list if no players are in the plot<br>
* - Remember, you can cast a PlotPlayer to it's respective implementation (BukkitPlayer, SpongePlayer) to obtain the player object
* @return list of PlotPlayer(s) or an empty list
*/
public List<PlotPlayer> getPlayersInPlot() {
ArrayList<PlotPlayer> players = new ArrayList<>();
for (Entry<String, PlotPlayer> entry : UUIDHandler.getPlayers().entrySet()) {
PlotPlayer pp = entry.getValue();
if (this.equals(pp.getCurrentPlot())) {
players.add(pp);
}
}
return players;
}
/**
* Check if the plot has an owner.
*
* @return false if there is no owner
*/
public boolean hasOwner() {
return this.owner != null;
}
/**
* Check if a UUID is a plot owner (merged plots may have multiple owners)
* @param uuid the player uuid
* @return if the provided uuid is the owner of the plot
*/
public boolean isOwner(UUID uuid) {
if (uuid.equals(this.owner)) {
return true;
}
if (!isMerged()) {
return false;
}
Set<Plot> connected = getConnectedPlots();
for (Plot current : connected) {
if (uuid.equals(current.owner)) {
return true;
}
}
return false;
}
public boolean isOwnerAbs(UUID uuid) {
return uuid.equals(this.owner);
}
/**
* Get a immutable set of owner UUIDs for a plot (supports multi-owner mega-plots).
* @return the plot owners
*/
public Set<UUID> getOwners() {
if (this.owner == null) {
return ImmutableSet.of();
}
if (isMerged()) {
Set<Plot> plots = getConnectedPlots();
Plot[] array = plots.toArray(new Plot[plots.size()]);
ImmutableSet.Builder<UUID> owners = ImmutableSet.builder();
UUID last = this.owner;
owners.add(this.owner);
for (Plot current : array) {
if (last == null || current.owner.getMostSignificantBits() != last.getMostSignificantBits()) {
owners.add(current.owner);
last = current.owner;
}
}
return owners.build();
}
return ImmutableSet.of(this.owner);
}
/**
* Check if the player is either the owner or on the trusted/added list.
*
* @param uuid
*
* @return true if the player is added/trusted or is the owner
*/
public boolean isAdded(UUID uuid) {
if (this.owner == null || 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;
}
/**
* Should the player be denied from entering.
*
* @param uuid
*
* @return boolean false if the player is allowed to enter
*/
public boolean isDenied(UUID uuid) {
return this.denied != null && (this.denied.contains(DBFunc.everyone) && !this.isAdded(uuid)
|| !this.isAdded(uuid) && this.denied.contains(uuid));
}
/**
* Get the {@link PlotId}.
*/
public PlotId getId() {
return this.id;
}
/**
* Get the plot world object for this plot<br>
* - The generic PlotArea object can be casted to its respective class for more control (e.g. HybridPlotWorld)
* @return PlotArea
*/
public PlotArea getArea() {
return this.area;
}
/**
* Assign this plot to a plot area.<br>
* (Mostly used during startup when worlds are being created)<br>
* Note: Using this when it doesn't make sense will result in strange behavior
* @param area
*/
public void setArea(PlotArea area) {
if (this.getArea() == area) {
return;
}
if (this.getArea() != null) {
this.area.removePlot(this.id);
}
this.area = area;
area.addPlot(this);
}
/**
* Get the plot manager object for this plot<br>
* - The generic PlotManager object can be casted to its respective class for more control (e.g. HybridPlotManager)
* @return PlotManager
*/
public PlotManager getManager() {
return this.area.getPlotManager();
}
/**
* Get or create plot settings.
* @return PlotSettings
* @deprecated use equivalent plot method;
*/
@Deprecated
public 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
*/
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:<br>
* - Merged plots need to be treated as a single plot for most purposes<br>
* - Some data such as home location needs to be associated with the group rather than each plot<br>
* - If the plot is not merged it will return itself.<br>
* - The result is cached locally
* @return base Plot
*/
public Plot getBasePlot(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.y < min.y || plot.id.y == min.y && plot.id.x < min.x) {
this.origin = plot;
min = plot.id;
}
}
for (Plot plot : this.getConnectedPlots()) {
plot.origin = this.origin;
}
return this.origin;
}
/**
* Check if the plot is merged in any direction.
* @return is the plot merged or not
*/
public boolean isMerged() {
return getSettings().getMerged(0) || getSettings().getMerged(2) || getSettings().getMerged(1) || getSettings().getMerged(3);
}
/**
* Get the timestamp of when the plot was created (unreliable)<br>
* - not accurate if the plot was created before this was implemented<br>
* - Milliseconds since the epoch<br>
* @return the creation date of the plot
*/
public long getTimestamp() {
if (this.timestamp == 0) {
this.timestamp = System.currentTimeMillis();
}
return this.timestamp;
}
/**
* Get if the plot is merged in a direction<br>
* ------- Actual -------<br>
* 0 = north<br>
* 1 = east<br>
* 2 = south<br>
* 3 = west<br>
* ----- Artificial -----<br>
* 4 = north-east<br>
* 5 = south-east<br>
* 6 = south-west<br>
* 7 = north-west<br>
* ----------<br>
* 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<br>
* @param direction
* @return true if merged in that direction
*/
public boolean getMerged(int direction) {
if (this.settings == null) {
return false;
}
switch (direction) {
case 0:
case 1:
case 2:
case 3:
return this.getSettings().getMerged(direction);
case 7:
int i = direction - 4;
int i2 = 0;
if (this.getSettings().getMerged(i2)) {
if (this.getSettings().getMerged(i)) {
if (this.area.getPlotAbs(this.id.getRelative(i)).getMerged(i2)) {
if (this.area.getPlotAbs(this.id.getRelative(i2)).getMerged(i)) {
return true;
}
}
}
}
return false;
case 4:
case 5:
case 6:
i = direction - 4;
i2 = direction - 3;
return this.getSettings().getMerged(i2)
&& this.getSettings().getMerged(i)
&& this.area.getPlotAbs(this.id.getRelative(i)).getMerged(i2)
&& this.area.getPlotAbs(this.id.getRelative(i2)).getMerged(i);
}
return false;
}
/**
* Get the denied users.
* @return a set of denied users
*/
public HashSet<UUID> getDenied() {
if (this.denied == null) {
this.denied = new HashSet<>();
}
return this.denied;
}
/**
* Set the denied users for this plot.
* @param uuids
*/
public void setDenied(Set<UUID> uuids) {
boolean larger = uuids.size() > getDenied().size();
HashSet<UUID> intersection = new HashSet<>(larger ? getDenied() : uuids);
intersection.retainAll(larger ? uuids : getDenied());
uuids.removeAll(intersection);
HashSet<UUID> toRemove = new HashSet<>(getDenied());
toRemove.removeAll(intersection);
for (UUID uuid : toRemove) {
removeDenied(uuid);
}
for (UUID uuid : uuids) {
addDenied(uuid);
}
}
/**
* Get the trusted users.
* @return a set of trusted users
*/
public HashSet<UUID> getTrusted() {
if (this.trusted == null) {
this.trusted = new HashSet<>();
}
return this.trusted;
}
/**
* Set the trusted users for this plot.
* @param uuids
*/
public void setTrusted(Set<UUID> uuids) {
boolean larger = uuids.size() > getTrusted().size();
HashSet<UUID> intersection = new HashSet<>(larger ? getTrusted() : uuids);
intersection.retainAll(larger ? uuids : getTrusted());
uuids.removeAll(intersection);
HashSet<UUID> toRemove = new HashSet<>(getTrusted());
toRemove.removeAll(intersection);
for (UUID uuid : toRemove) {
removeTrusted(uuid);
}
for (UUID uuid : uuids) {
addTrusted(uuid);
}
}
/**
* Get the members
* @return a set of members
*/
public HashSet<UUID> getMembers() {
if (this.members == null) {
this.members = new HashSet<>();
}
return this.members;
}
/**
* Set the members for this plot
* @param uuids
*/
public void setMembers(Set<UUID> uuids) {
boolean larger = uuids.size() > getMembers().size();
HashSet<UUID> intersection = new HashSet<>(larger ? getMembers() : uuids);
intersection.retainAll(larger ? uuids : getMembers());
uuids.removeAll(intersection);
HashSet<UUID> toRemove = new HashSet<>(getMembers());
toRemove.removeAll(intersection);
for (UUID uuid : toRemove) {
removeMember(uuid);
}
for (UUID uuid : uuids) {
addMember(uuid);
}
}
/**
* Deny someone (updates database as well)
* @param uuid the uuid of the player to deny.
*/
public void addDenied(UUID uuid) {
for (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(UUID uuid) {
for (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(UUID uuid) {
for (Plot current : getConnectedPlots()) {
if (current.getMembers().add(uuid)) {
DBFunc.setMember(current, uuid);
}
}
}
/**
* Set the plot owner (and update the database)
* @param owner
*/
public void setOwner(UUID owner) {
if (!hasOwner()) {
this.owner = owner;
create();
return;
}
if (!isMerged()) {
if (!this.owner.equals(owner)) {
this.owner = owner;
DBFunc.setOwner(this, owner);
}
return;
}
for (Plot current : getConnectedPlots()) {
if (!owner.equals(current.owner)) {
current.owner = owner;
DBFunc.setOwner(current, owner);
}
}
}
/**
* Clear a plot.
* @see this#clear(boolean, boolean, Runnable)
* @see #deletePlot(Runnable) to clear and delete a plot
* @param whenDone A runnable to execute when clearing finishes, or null
*/
public void clear(Runnable whenDone) {
this.clear(false, false, whenDone);
}
public boolean clear(boolean checkRunning, final boolean isDelete, final Runnable whenDone) {
if (checkRunning && this.getRunning() != 0 || !EventUtil.manager.callClear(this)) {
return false;
}
final HashSet<RegionWrapper> regions = this.getRegions();
final Set<Plot> plots = this.getConnectedPlots();
final ArrayDeque<Plot> queue = new ArrayDeque<>(plots);
if (isDelete) {
this.removeSign();
}
this.unlinkPlot(true, !isDelete);
final PlotManager manager = this.area.getPlotManager();
Runnable run = new Runnable() {
@Override
public void run() {
if (queue.isEmpty()) {
Runnable run = new Runnable() {
@Override
public void run() {
for (RegionWrapper region : regions) {
Location[] corners = region.getCorners(getWorldName());
ChunkManager.manager.clearAllEntities(corners[0], corners[1]);
}
TaskManager.runTask(whenDone);
}
};
for (Plot current : plots) {
if (isDelete || current.owner == null) {
manager.unclaimPlot(Plot.this.area, current, null);
} else {
manager.claimPlot(Plot.this.area, current);
}
}
GlobalBlockQueue.IMP.addTask(run);
return;
}
Plot current = queue.poll();
if (Plot.this.area.TERRAIN != 0) {
ChunkManager.manager.regenerateRegion(current.getBottomAbs(), current.getTopAbs(), false, this);
return;
}
manager.clearPlot(Plot.this.area, current, this);
}
};
run.run();
return true;
}
/**
* Set the biome for a plot asynchronously
* @param biome The biome e.g. "forest"
* @param whenDone The task to run when finished, or null
*/
public void setBiome(final String biome, final Runnable whenDone) {
final ArrayDeque<RegionWrapper> regions = new ArrayDeque<>(this.getRegions());
final int extendBiome;
if (area instanceof SquarePlotWorld) {
extendBiome = (((SquarePlotWorld) area).ROAD_WIDTH > 0) ? 1 : 0;
} else {
extendBiome = 0;
}
Runnable run = new Runnable() {
@Override
public void run() {
if (regions.isEmpty()) {
Plot.this.refreshChunks();
TaskManager.runTask(whenDone);
return;
}
RegionWrapper region = regions.poll();
Location pos1 = new Location(getWorldName(), region.minX - extendBiome, region.minY, region.minZ - extendBiome);
Location pos2 = new Location(getWorldName(), region.maxX + extendBiome, region.maxY, region.maxZ + extendBiome);
ChunkManager.chunkTask(pos1, pos2, new RunnableVal<int[]>() {
@Override
public void run(int[] value) {
ChunkLoc loc = new ChunkLoc(value[0], value[1]);
ChunkManager.manager.loadChunk(getWorldName(), loc, false);
MainUtil.setBiome(getWorldName(), value[2], value[3], value[4], value[5], biome);
ChunkManager.manager.unloadChunk(getWorldName(), loc, true, true);
}
}, this, 5);
}
};
run.run();
}
/**
* Unlink the plot and all connected plots.
* @param createSign
* @param createRoad
* @return
*/
public boolean unlinkPlot(boolean createRoad, boolean createSign) {
if (!this.isMerged()) {
return false;
}
final Set<Plot> plots = this.getConnectedPlots();
ArrayList<PlotId> ids = new ArrayList<>(plots.size());
for (Plot current : plots) {
current.setHome(null);
ids.add(current.getId());
}
boolean result = EventUtil.manager.callUnlink(this.area, ids);
if (!result) {
return false;
}
this.clearRatings();
if (createSign) {
this.removeSign();
}
PlotManager manager = this.area.getPlotManager();
if (createRoad) {
manager.startPlotUnlink(this.area, ids);
}
if (this.area.TERRAIN != 3 && createRoad) {
for (Plot current : plots) {
if (current.getMerged(1)) {
manager.createRoadEast(current.area, current);
if (current.getMerged(2)) {
manager.createRoadSouth(current.area, current);
if (current.getMerged(5)) {
manager.createRoadSouthEast(current.area, current);
}
}
} else if (current.getMerged(2)) {
manager.createRoadSouth(current.area, current);
}
}
}
for (Plot current : plots) {
boolean[] merged = new boolean[]{false, false, false, false};
current.setMerged(merged);
}
if (createSign) {
GlobalBlockQueue.IMP.addTask(new Runnable() {
@Override
public void run() {
for (Plot current : plots) {
current.setSign(MainUtil.getName(current.owner));
}
}
});
}
if (createRoad) {
manager.finishPlotUnlink(this.area, ids);
}
return true;
}
/**
* Set the sign for a plot to a specific name
* @param name
*/
public void setSign(final String name) {
if (!isLoaded()) return;
if (!PS.get().isMainThread(Thread.currentThread())) {
TaskManager.runTask(new Runnable() {
@Override
public void run() {
Plot.this.setSign(name);
}
});
return;
}
PlotManager manager = this.area.getPlotManager();
if (this.area.ALLOW_SIGNS) {
Location loc = manager.getSignLoc(this.area, this);
String id = this.id.x + ";" + this.id.y;
String[] lines = new String[]{
C.OWNER_SIGN_LINE_1.formatted().replaceAll("%id%", id),
C.OWNER_SIGN_LINE_2.formatted().replaceAll("%id%", id).replaceAll("%plr%", name),
C.OWNER_SIGN_LINE_3.formatted().replaceAll("%id%", id).replaceAll("%plr%", name),
C.OWNER_SIGN_LINE_4.formatted().replaceAll("%id%", id).replaceAll("%plr%", name)};
WorldUtil.IMP.setSign(this.getWorldName(), loc.getX(), loc.getY(), loc.getZ(), lines);
}
}
protected boolean isLoaded() {
return WorldUtil.IMP.isWorld(getWorldName());
}
/**
* This will return null if the plot hasn't been analyzed
* @return analysis of plot
*/
public PlotAnalysis getComplexity(Settings.Auto_Clear settings) {
return PlotAnalysis.getAnalysis(this, settings);
}
public void analyze(RunnableVal<PlotAnalysis> whenDone) {
PlotAnalysis.analyzePlot(this, whenDone);
}
/**
* Set a flag for this plot
* @param flag
* @param value
*/
public <V> boolean setFlag(Flag<V> flag, Object value) {
if (flag == Flags.KEEP && ExpireManager.IMP != null) {
ExpireManager.IMP.updateExpired(this);
}
return FlagManager.addPlotFlag(this, flag, value);
}
/**
* Remove a flag from this plot
* @param flag the flag to remove
* @return
*/
public boolean removeFlag(Flag<?> flag) {
return FlagManager.removePlotFlag(this, flag);
}
/**
* Get the flag for a given key
* @param key
*/
public <V> Optional<V> getFlag(Flag<V> key) {
return FlagManager.getPlotFlag(this, key);
}
/**
* Get the flag for a given key
* @param key the flag
* @param defaultValue if the key is null, the value to return
*/
public <V> V getFlag(Flag<V> key, V defaultValue) {
V value = FlagManager.getPlotFlagRaw(this, key);
if (value == null) {
return defaultValue;
} else {
return value;
}
}
/**
* Delete a plot (use null for the runnable if you don't need to be notified on completion)
* @see PS#removePlot(Plot, boolean)
* @see #clear(Runnable) to simply clear a plot
*/
public boolean deletePlot(final Runnable whenDone) {
if (!this.hasOwner()) {
return false;
}
final Set<Plot> plots = this.getConnectedPlots();
this.clear(false, true, new Runnable() {
@Override
public void run() {
for (Plot current : plots) {
current.unclaim();
}
TaskManager.runTask(whenDone);
}
});
return true;
}
/**
* Count the entities in a plot
* @see ChunkManager#countEntities(Plot)
* 0 = Entity
* 1 = Animal
* 2 = Monster
* 3 = Mob
* 4 = Boat
* 5 = Misc
* @return
*/
public int[] countEntities() {
int[] count = new int[6];
for (Plot current : this.getConnectedPlots()) {
int[] result = ChunkManager.manager.countEntities(current);
count[0] += result[0];
count[1] += result[1];
count[2] += result[2];
count[3] += result[3];
count[4] += result[4];
count[5] += result[5];
}
return count;
}
/**
* Returns true if a previous task was running
* @return 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<br>
* - 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;
}
/**
* Get the number of tracked running tasks for this plot<br>
* - 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 false if the Plot has no owner, otherwise true.
*/
public boolean unclaim() {
if (this.owner == null) {
return false;
}
for (Plot current : getConnectedPlots()) {
List<PlotPlayer> players = current.getPlayersInPlot();
for (PlotPlayer pp : players) {
PlotListener.plotExit(pp, current);
}
getArea().removePlot(getId());
DBFunc.delete(current);
current.owner = null;
current.settings = null;
for (PlotPlayer pp : players) {
PlotListener.plotEntry(pp, current);
}
}
return true;
}
/**
* Unlink a plot and remove the roads
* @see this#unlinkPlot(boolean, boolean)
* @return true if plot was linked
*/
public boolean unlink() {
return this.unlinkPlot(true, true);
}
public Location getCenter() {
Location[] corners = getCorners();
Location top = corners[0];
Location bot = corners[1];
Location loc = new Location(this.getWorldName(), MathMan.average(bot.getX(), top.getX()), MathMan.average(bot.getY(), top.getY()), MathMan.average(bot.getZ(), top.getZ()));
if (!isLoaded()) return loc;
int y = isLoaded() ? WorldUtil.IMP.getHighestBlock(getWorldName(), loc.getX(), loc.getZ()) : 64;
if (area.ALLOW_SIGNS) {
y = Math.max(y, getManager().getSignLoc(area, this).getY());
}
loc.setY(1 + y);
return loc;
}
public Location getSide() {
RegionWrapper largest = getLargestRegion();
int x = (largest.maxX >> 1) - (largest.minX >> 1) + largest.minX;
int z = largest.minZ - 1;
PlotManager manager = getManager();
int y = isLoaded() ? WorldUtil.IMP.getHighestBlock(getWorldName(), x, z) : 64;
if (area.ALLOW_SIGNS) {
y = Math.max(y, manager.getSignLoc(area, this).getY());
}
return new Location(getWorldName(), x, y + 1, z);
}
/**
* Return the home location for the plot
* @return Home location
*/
public Location getHome() {
BlockLoc home = this.getPosition();
if (home == null || home.x == 0 && home.z == 0) {
return this.getDefaultHome();
} else {
Location bot = this.getBottomAbs();
Location loc = new Location(bot.getWorld(), bot.getX() + home.x, bot.getY() + home.y, bot.getZ() + home.z, home.yaw, home.pitch);
if (!isLoaded()) return loc;
if (WorldUtil.IMP.getBlock(loc).id != 0) {
loc.setY(Math.max(WorldUtil.IMP.getHighestBlock(this.getWorldName(), loc.getX(), loc.getZ()), bot.getY()));
}
return loc;
}
}
/**
* Set the home location
* @param location
*/
public void setHome(BlockLoc location) {
Plot plot = this.getBasePlot(false);
if (location != null && new BlockLoc(0, 0, 0).equals(location)) {
return;
}
plot.getSettings().setPosition(location);
if (location != null) {
DBFunc.setPosition(plot, plot.getSettings().getPosition().toString());
return;
}
DBFunc.setPosition(plot, null);
}
/**
* Get the default home location for a plot<br>
* - Ignores any home location set for that specific plot
* @return
*/
public Location getDefaultHome() {
Plot plot = this.getBasePlot(false);
if (this.area.DEFAULT_HOME != null) {
int x;
int z;
if (this.area.DEFAULT_HOME.x == Integer.MAX_VALUE && this.area.DEFAULT_HOME.z == Integer.MAX_VALUE) {
// center
RegionWrapper largest = plot.getLargestRegion();
x = (largest.maxX >> 1) - (largest.minX >> 1) + largest.minX;
z = (largest.maxZ >> 1) - (largest.minZ >> 1) + largest.minZ;
} else {
// specific
Location bot = plot.getBottomAbs();
x = bot.getX() + this.area.DEFAULT_HOME.x;
z = bot.getZ() + this.area.DEFAULT_HOME.z;
}
int y = isLoaded() ? WorldUtil.IMP.getHighestBlock(plot.getWorldName(), x, z) : 64;
return new Location(plot.getWorldName(), x, y + 1, z);
}
// Side
return plot.getSide();
}
public double getVolume() {
double count = 0;
for (RegionWrapper region : getRegions()) {
count += (region.maxX - (double) region.minX + 1) * (region.maxZ - (double) region.minZ + 1) * 256;
}
return count;
}
/**
* Get the average rating of the plot. This is the value displayed in /plot info
* @return average rating as double
*/
public double getAverageRating() {
double sum = 0;
Collection<Rating> ratings = this.getRatings().values();
for (Rating rating : ratings) {
sum += rating.getAverageRating();
}
return sum / ratings.size();
}
/**
* Set a rating for a user<br>
* - If the user has already rated, the following will return false
* @param uuid
* @param rating
* @return
*/
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 for this plot */
public void clearRatings() {
Plot base = this.getBasePlot(false);
PlotSettings baseSettings = base.getSettings();
if (baseSettings.ratings != null && !baseSettings.getRatings().isEmpty()) {
DBFunc.deleteRatings(base);
baseSettings.ratings = null;
}
}
/**
* Get the ratings associated with a plot<br>
* - The rating object may contain multiple categories
* @return Map of user who rated to the rating
*/
public HashMap<UUID, Rating> getRatings() {
Plot base = this.getBasePlot(false);
HashMap<UUID, Rating> map = new HashMap<>();
if (!base.hasRatings()) {
return map;
}
for (Entry<UUID, Integer> 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.ratings != null;
}
/**
* Resend all chunks inside the plot to nearby players<br>
* This should not need to be called
*/
public void refreshChunks() {
LocalBlockQueue queue = GlobalBlockQueue.IMP.getNewQueue(getWorldName(), false);
HashSet<ChunkLoc> chunks = new HashSet<>();
for (RegionWrapper region : Plot.this.getRegions()) {
for (int x = region.minX >> 4; x <= region.maxX >> 4; x++) {
for (int z = region.minZ >> 4; z <= region.maxZ >> 4; z++) {
if (chunks.add(new ChunkLoc(x, z))) {
queue.refreshChunk(x, z);
}
}
}
}
}
/** Remove the plot sign if it is set. */
public void removeSign() {
PlotManager manager = this.area.getPlotManager();
if (!this.area.ALLOW_SIGNS) {
return;
}
Location loc = manager.getSignLoc(this.area, this);
LocalBlockQueue queue = GlobalBlockQueue.IMP.getNewQueue(getWorldName(), false);
queue.setBlock(loc.getX(), loc.getY(), loc.getZ(), 0);
queue.flush();
}
/** Set the plot sign if plot signs are enabled. */
public void setSign() {
if (this.owner == null) {
this.setSign("unknown");
return;
}
this.setSign(UUIDHandler.getName(this.owner));
}
/**
* Register a plot and create it in the database<br>
* - The plot will not be created if the owner is null<br>
* - Any setting from before plot creation will not be saved until the server is stopped properly. i.e. Set any values/options after plot
* creation.
* @return true if plot was created successfully
*/
public boolean create() {
return this.create(this.owner, true);
}
public boolean claim(final PlotPlayer player, boolean teleport, String schematic) {
if (!canClaim(player)) {
return false;
}
return claim(player, teleport, schematic, true);
}
public boolean claim(final PlotPlayer player, boolean teleport, String schematic, boolean updateDB) {
boolean result = EventUtil.manager.callClaim(player, this, false);
if (updateDB) {
if (!result || (!create(player.getUUID(), true))) {
return false;
}
} else {
area.addPlot(this);
}
setSign(player.getName());
MainUtil.sendMessage(player, C.CLAIMED);
if (teleport) {
teleportPlayer(player);
}
PlotArea plotworld = getArea();
if (plotworld.SCHEMATIC_ON_CLAIM) {
SchematicHandler.Schematic sch;
if (schematic == null || schematic.isEmpty()) {
sch = SchematicHandler.manager.getSchematic(plotworld.SCHEMATIC_FILE);
} else {
sch = SchematicHandler.manager.getSchematic(schematic);
if (sch == null) {
sch = SchematicHandler.manager.getSchematic(plotworld.SCHEMATIC_FILE);
}
}
SchematicHandler.manager.paste(sch, this, 0, 0, 0, true, new RunnableVal<Boolean>() {
@Override
public void run(Boolean value) {
if (value) {
MainUtil.sendMessage(player, C.SCHEMATIC_PASTE_SUCCESS);
} else {
MainUtil.sendMessage(player, C.SCHEMATIC_PASTE_FAILED);
}
}
});
}
plotworld.getPlotManager().claimPlot(plotworld, this);
return true;
}
/**
* Register a plot and create it in the database<br>
* - The plot will not be created if the owner is null<br>
* - Any setting from before plot creation will not be saved until the server is stopped properly. i.e. Set any values/options after plot
* creation.
* @param uuid the uuid of the plot owner
* @param notify
* @return true if plot was created successfully
*/
public boolean create(final UUID uuid, final boolean notify) {
if (uuid == null) {
throw new IllegalArgumentException("UUID cannot be null");
}
this.owner = uuid;
Plot existing = this.area.getOwnedPlotAbs(this.id);
if (existing != null) {
throw new IllegalStateException("Plot already exists!");
}
if (notify) {
Integer meta = (Integer) this.area.getMeta("worldBorder");
if (meta != null) {
this.updateWorldBorder();
}
}
connected_cache = null;
regions_cache = null;
this.getTrusted().clear();
this.getMembers().clear();
this.getDenied().clear();
this.settings = new PlotSettings();
if (this.area.addPlot(this)) {
DBFunc.createPlotAndSettings(this, new Runnable() {
@Override
public void run() {
PlotArea plotworld = Plot.this.area;
if (notify && plotworld.AUTO_MERGE) {
Plot.this.autoMerge(-1, Integer.MAX_VALUE, uuid, true);
}
}
});
return true;
}
return false;
}
/**
* Set components such as border, wall, floor.
* (components are generator specific)
*/
public boolean setComponent(String component, String blocks) {
PlotBlock[] parsed = Configuration.BLOCKLIST.parseString(blocks);
return !(parsed == null || parsed.length == 0) && this.setComponent(component, parsed);
}
/**
* Retrieve the biome of the plot.
* @return the name of the biome
*/
public String getBiome() {
Location loc = this.getCenter();
return WorldUtil.IMP.getBiome(loc.getWorld(), loc.getX(), loc.getZ());
}
/**
* Return the top location for the plot.
* @return
*/
public Location getTopAbs() {
Location top = this.area.getPlotManager().getPlotTopLocAbs(this.area, this.id);
top.setWorld(getWorldName());
return top;
}
/**
* Return the bottom location for the plot.
* @return
*/
public Location getBottomAbs() {
Location loc = this.area.getPlotManager().getPlotBottomLocAbs(this.area, this.id);
loc.setWorld(getWorldName());
return loc;
}
/**
* Swap the settings for two plots.
* @param plot the plot to swap data with
* @param whenDone the task to run at the end of this method.
* @return
*/
public boolean swapData(Plot plot, Runnable whenDone) {
if (this.owner == null) {
if (plot != null && plot.hasOwner()) {
plot.moveData(this, whenDone);
return true;
}
return false;
}
if (plot == null || plot.owner == null) {
this.moveData(plot, whenDone);
return true;
}
// Swap cached
PlotId temp = new PlotId(this.getId().x, this.getId().y);
this.getId().x = plot.getId().x;
this.getId().y = plot.getId().y;
plot.getId().x = temp.x;
plot.getId().y = temp.y;
this.area.removePlot(this.getId());
plot.area.removePlot(plot.getId());
this.getId().recalculateHash();
plot.getId().recalculateHash();
this.area.addPlotAbs(this);
plot.area.addPlotAbs(plot);
// Swap database
DBFunc.swapPlots(plot, this);
TaskManager.runTaskLater(whenDone, 1);
return true;
}
/**
* Move the settings for a plot.
* @param plot the plot to move
* @param whenDone
* @return
*/
public boolean moveData(Plot plot, Runnable whenDone) {
if (this.owner == null) {
PS.debug(plot + " is unowned (single)");
TaskManager.runTask(whenDone);
return false;
}
if (plot.hasOwner()) {
PS.debug(plot + " is unowned (multi)");
TaskManager.runTask(whenDone);
return false;
}
this.area.removePlot(this.id);
this.getId().x = plot.getId().x;
this.getId().y = plot.getId().y;
this.getId().recalculateHash();
this.area.addPlotAbs(this);
DBFunc.movePlot(this, plot);
TaskManager.runTaskLater(whenDone, 1);
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 getPlotTopLocAbs(...)
*
* @return Location top of mega plot
*/
public Location getExtendedTopAbs() {
Location top = this.getTopAbs();
if (!this.isMerged()) {
return top;
}
if (this.getMerged(2)) {
top.setZ(this.getRelative(2).getBottomAbs().getZ() - 1);
}
if (this.getMerged(1)) {
top.setX(this.getRelative(1).getBottomAbs().getX() - 1);
}
return top;
}
/**
* Gets the bottom location for a plot.<br>
* - Does not respect mega plots<br>
* - Merged plots, only the road will be considered part of the plot<br>
*
* @return Location bottom of mega plot
*/
public Location getExtendedBottomAbs() {
Location bot = this.getBottomAbs();
if (!this.isMerged()) {
return bot;
}
if (this.getMerged(0)) {
bot.setZ(this.getRelative(0).getTopAbs().getZ() + 1);
}
if (this.getMerged(3)) {
bot.setX(this.getRelative(3).getTopAbs().getX() + 1);
}
return bot;
}
/**
* Returns the top and bottom location.<br>
* - If the plot is not connected, it will return its own corners<br>
* - the returned locations will not necessarily correspond to claimed plots if the connected plots do not form a rectangular shape
* @deprecated as merged plots no longer need to be rectangular
* @return new Location[] { bottom, top }
*/
@Deprecated
public Location[] getCorners() {
if (!this.isMerged()) {
return new Location[]{this.getBottomAbs(), this.getTopAbs()};
}
return MainUtil.getCorners(this.getWorldName(), this.getRegions());
}
/**
* Remove the east road section of a plot<br>
* - Used when a plot is merged<br>
*/
public void removeRoadEast() {
if (this.area.TYPE != 0 && this.area.TERRAIN > 1) {
if (this.area.TERRAIN == 3) {
return;
}
Plot other = this.getRelative(1);
Location bot = other.getBottomAbs();
Location top = this.getTopAbs();
Location pos1 = new Location(this.getWorldName(), top.getX(), 0, bot.getZ());
Location pos2 = new Location(this.getWorldName(), bot.getX(), 256, top.getZ());
ChunkManager.manager.regenerateRegion(pos1, pos2, true, null);
} else {
this.area.getPlotManager().removeRoadEast(this.area, this);
}
}
/**
* Returns the top and bottom plot id.<br>
* - If the plot is not connected, it will return itself for the top/bottom<br>
* - the returned ids will not necessarily correspond to claimed plots if the connected plots do not form a rectangular shape
* @deprecated as merged plots no longer need to be rectangular
* @return new Plot[] { bottom, top }
*/
@Deprecated
public PlotId[] getCornerIds() {
if (!this.isMerged()) {
return new PlotId[]{this.getId(), this.getId()};
}
PlotId min = new PlotId(this.getId().x, this.getId().y);
PlotId max = new PlotId(this.getId().x, this.getId().y);
for (Plot current : this.getConnectedPlots()) {
if (current.getId().x < min.x) {
min.x = current.getId().x;
} else if (current.getId().x > max.x) {
max.x = current.getId().x;
}
if (current.getId().y < min.y) {
min.y = current.getId().y;
} else if (current.getId().y > max.y) {
max.y = current.getId().y;
}
}
return new PlotId[]{min, max};
}
/**
* @deprecated in favor of getCorners()[0];<br>
* @return
*/
@Deprecated
public Location getBottom() {
return this.getCorners()[0];
}
/**
* @deprecated in favor of getCorners()[1];
* @return the top corner of the plot
*/
@Deprecated
public Location getTop() {
return this.getCorners()[1];
}
/**
* Swap the plot contents and settings with another location<br>
* - The destination must correspond to a valid plot of equal dimensions
* @see ChunkManager#swap(Location, Location, Location, Location, Runnable) to swap terrain
* @see this#swapData(Plot, Runnable) to swap plot settings
* @param destination The other plot to swap with
* @param whenDone A task to run when finished, or null
* @see this#swapData(Plot, Runnable)
* @return boolean if swap was successful
*/
public boolean swap(Plot destination, Runnable whenDone) {
return this.move(destination, whenDone, true);
}
/**
* Move the plot to an empty location<br>
* - The location must be empty
* @param destination Where to move the plot
* @param whenDone A task to run when done, or null
* @return if the move was successful
*/
public boolean move(Plot destination, Runnable whenDone) {
return this.move(destination, whenDone, false);
}
/**
* Get 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.x + ";" + this.id.y;
}
/**
* Remove a denied player (use DBFunc as well)<br>
* Using the * uuid will remove all users
* @param uuid
*/
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)<br>
* Using the * uuid will remove all users
* @param uuid
*/
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)<br>
* Using the * uuid will remove all users
* @param uuid
*/
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;
}
/**
* Export the plot as a schematic to the configured output directory.
* @return
*/
public void export(final RunnableVal<Boolean> whenDone) {
SchematicHandler.manager.getCompoundTag(this, new RunnableVal<CompoundTag>() {
@Override
public void run(final CompoundTag value) {
if (value == null) {
if (whenDone != null) {
whenDone.value = false;
TaskManager.runTask(whenDone);
}
} else {
TaskManager.runTaskAsync(new Runnable() {
@Override
public void run() {
String name = Plot.this.id + "," + Plot.this.area + ',' + MainUtil.getName(Plot.this.owner);
boolean result =
SchematicHandler.manager.save(value, Settings.Paths.SCHEMATICS + File.separator + name + ".schematic");
if (whenDone != null) {
whenDone.value = result;
TaskManager.runTask(whenDone);
}
}
});
}
}
});
}
/**
* Export the plot as a BO3 object<br>
* - bedrock, floor and main block are ignored in their respective sections
* - air is ignored
* - The center is considered to be on top of the plot in the center
* @param whenDone value will be false if exporting fails
*/
public void exportBO3(RunnableVal<Boolean> whenDone) {
boolean result = BO3Handler.saveBO3(this);
if (whenDone != null) {
whenDone.value = result;
}
TaskManager.runTask(whenDone);
}
/**
* Upload the plot as a schematic to the configured web interface.
* @param whenDone value will be null if uploading fails
*/
public void upload(final RunnableVal<URL> whenDone) {
SchematicHandler.manager.getCompoundTag(this, new RunnableVal<CompoundTag>() {
@Override
public void run(CompoundTag value) {
SchematicHandler.manager.upload(value, null, null, whenDone);
}
});
}
/**
* Upload this plot as a world file<br>
* - The mca files are each 512x512, so depending on the plot size it may also download adjacent plots<br>
* - Works best when (plot width + road width) % 512 == 0<br>
* @see WorldUtil
* @param whenDone
*/
public void uploadWorld(RunnableVal<URL> whenDone) {
WorldUtil.IMP.upload(this, null, null, whenDone);
}
/**
* Upload this plot as a BO3<br>
* - May not work on non default generator<br>
* - BO3 includes flags/ignores plot main/floor block<br>
* @see BO3Handler
* @param whenDone
*/
public void uploadBO3(RunnableVal<URL> whenDone) {
BO3Handler.upload(this, null, null, whenDone);
}
@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;
}
/**
* Get the plot hashcode<br>
* Note: The hashcode is unique if:<br>
* - Plots are in the same world<br>
* - The x,z coordinates are between Short.MIN_VALUE and Short.MAX_VALUE<br>
* @return integer.
*/
@Override
public int hashCode() {
return this.id.hashCode();
}
/**
* Get the flags specific to this plot<br>
* - Does not take default flags into account<br>
* @return
*/
public HashMap<Flag<?>, Object> getFlags() {
return this.getSettings().flags;
}
/**
* Set a flag for this plot.
* @param flags
*/
public void setFlags(HashMap<Flag<?>, Object> flags) {
FlagManager.setPlotFlags(this, flags);
}
/**
* Get the plot alias.
* - Returns an empty string if no alias is set
* @return The plot alias
*/
public String getAlias() {
if (this.settings == null) {
return "";
}
return this.settings.getAlias();
}
/**
* Set 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);
}
}
/**
* Set the raw merge data<br>
* - Updates DB<br>
* - Does not modify terrain<br>
* ----------<br>
* 0 = north<br>
* 1 = east<br>
* 2 = south<br>
* 3 = west<br>
* ----------<br>
* @param direction
* @param value
*/
public void setMerged(int 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.y < this.id.y || other.id.y == this.id.y && other.id.x < this.id.x ? 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;
}
}
/**
* Get the merged array.
* @return boolean [ north, east, south, west ]
*/
public boolean[] getMerged() {
return this.getSettings().getMerged();
}
/**
* Set the raw merge data<br>
* - Updates DB<br>
* - Does not modify terrain<br>
* Get if the plot is merged in a direction<br>
* ----------<br>
* 0 = north<br>
* 1 = east<br>
* 2 = south<br>
* 3 = west<br>
* ----------<br>
* Note: Diagonal merging (4-7) must be done by merging the corresponding plots.
* @param merged
*/
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;
}
}
/**
* Get the set home location or 0,0,0 if no location is set<br>
* - Does not take the default home location into account
* @see #getHome()
* @return
*/
public BlockLoc getPosition() {
return this.getSettings().getPosition();
}
/**
* Check if a plot can be claimed by the provided player.
* @param player the claiming player
* @return
*/
public boolean canClaim(PlotPlayer player) {
PlotCluster cluster = this.getCluster();
if (cluster != null && player != null) {
if (!cluster.isAdded(player.getUUID()) && !Permissions.hasPermission(player, "plots.admin.command.claim")) {
return false;
}
}
return this.guessOwner() == null && !isMerged();
}
/**
* Guess the owner of a plot either by the value in memory, or the sign data<br>
* Note: Recovering from sign information is useful if e.g. PlotMe conversion wasn't successful
* @return UUID
*/
public UUID guessOwner() {
if (this.hasOwner()) {
return this.owner;
}
if (!this.area.ALLOW_SIGNS) {
return null;
}
try {
final Location loc = this.getManager().getSignLoc(this.area, this);
String[] lines = TaskManager.IMP.sync(new RunnableVal<String[]>() {
@Override
public void run(String[] value) {
ChunkManager.manager.loadChunk(loc.getWorld(), loc.getChunkLoc(), false);
this.value = WorldUtil.IMP.getSign(loc);
}
});
if (lines == null) {
return null;
}
loop:
for (int i = 4; i > 0; i--) {
String caption = C.valueOf("OWNER_SIGN_LINE_" + i).s();
int index = caption.indexOf("%plr%");
if (index < 0) {
continue;
}
String line = lines[i - 1];
if (line.length() <= index) {
return null;
}
String name = line.substring(index);
if (name.isEmpty()) {
return null;
}
UUID owner = UUIDHandler.getUUID(name, null);
if (owner != null) {
this.owner = owner;
break;
}
if (lines[i - 1].length() == 15) {
BiMap<StringWrapper, UUID> map = UUIDHandler.getUuidMap();
for (Entry<StringWrapper, UUID> entry : map.entrySet()) {
String key = entry.getKey().value;
if (key.length() > name.length() && key.startsWith(name)) {
this.owner = entry.getValue();
break loop;
}
}
}
this.owner = UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(StandardCharsets.UTF_8));
break;
}
if (this.hasOwner()) {
this.create();
}
return this.owner;
} catch (IllegalArgumentException ignored) {
return null;
}
}
/**
* Remove the south road section of a plot<br>
* - Used when a plot is merged<br>
*/
public void removeRoadSouth() {
if (this.area.TYPE != 0 && this.area.TERRAIN > 1) {
if (this.area.TERRAIN == 3) {
return;
}
Plot other = this.getRelative(2);
Location bot = other.getBottomAbs();
Location top = this.getTopAbs();
Location pos1 = new Location(this.getWorldName(), bot.getX(), 0, top.getZ());
Location pos2 = new Location(this.getWorldName(), top.getX(), 256, bot.getZ());
ChunkManager.manager.regenerateRegion(pos1, pos2, true, null);
} else {
this.getManager().removeRoadSouth(this.area, this);
}
}
/**
* Auto merge a plot in a specific direction<br>
* @param dir The direction to merge<br>
* -1 = All directions<br>
* 0 = north<br>
* 1 = east<br>
* 2 = south<br>
* 3 = west<br>
* @param max The max number of merges to do
* @param uuid The UUID it is allowed to merge with
* @param removeRoads Whether to remove roads
* @return true if a merge takes place
*/
public boolean autoMerge(int dir, int max, UUID uuid, boolean removeRoads) {
if (this.owner == null) {
return false;
}
HashSet<Plot> visited = new HashSet<>();
HashSet<PlotId> merged = new HashSet<>();
Set<Plot> connected = this.getConnectedPlots();
for (Plot current : connected) {
merged.add(current.getId());
}
ArrayDeque<Plot> frontier = new ArrayDeque<>(connected);
Plot current;
boolean toReturn = false;
while ((current = frontier.poll()) != null && max >= 0) {
if (visited.contains(current)) {
continue;
}
visited.add(current);
Set<Plot> plots;
if ((dir == -1 || dir == 0) && !current.getMerged(0)) {
Plot other = current.getRelative(0);
if (other != null && other.isOwner(uuid)
&& (other.getBasePlot(false).equals(current.getBasePlot(false))
|| (plots = other.getConnectedPlots()).size() <= max && frontier.addAll(plots) && (max -= plots.size()) != -1)) {
current.mergePlot(other, removeRoads);
merged.add(current.getId());
merged.add(other.getId());
toReturn = true;
}
}
if (max >= 0 && (dir == -1 || dir == 1) && !current.getMerged(1)) {
Plot other = current.getRelative(1);
if (other != null && other.isOwner(uuid)
&& (other.getBasePlot(false).equals(current.getBasePlot(false))
|| (plots = other.getConnectedPlots()).size() <= max && frontier.addAll(plots) && (max -= plots.size()) != -1)) {
current.mergePlot(other, removeRoads);
merged.add(current.getId());
merged.add(other.getId());
toReturn = true;
}
}
if (max >= 0 && (dir == -1 || dir == 2) && !current.getMerged(2)) {
Plot other = current.getRelative(2);
if (other != null && other.isOwner(uuid)
&& (other.getBasePlot(false).equals(current.getBasePlot(false))
|| (plots = other.getConnectedPlots()).size() <= max && frontier.addAll(plots) && (max -= plots.size()) != -1)) {
current.mergePlot(other, removeRoads);
merged.add(current.getId());
merged.add(other.getId());
toReturn = true;
}
}
if (max >= 0 && (dir == -1 || dir == 3) && !current.getMerged(3)) {
Plot other = current.getRelative(3);
if (other != null && other.isOwner(uuid)
&& (other.getBasePlot(false).equals(current.getBasePlot(false))
|| (plots = other.getConnectedPlots()).size() <= max && frontier.addAll(plots) && (max -= plots.size()) != -1)) {
current.mergePlot(other, removeRoads);
merged.add(current.getId());
merged.add(other.getId());
toReturn = true;
}
}
}
if (removeRoads && toReturn) {
ArrayList<PlotId> ids = new ArrayList<>(merged);
this.getManager().finishPlotMerge(this.area, ids);
}
return toReturn;
}
/**
* Merge the plot settings<br>
* - Used when a plot is merged<br>
* @param b
*/
public void mergeData(Plot b) {
HashMap<Flag<?>, Object> flags1 = this.getFlags();
HashMap<Flag<?>, Object> flags2 = b.getFlags();
if ((!flags1.isEmpty() || !flags2.isEmpty()) && !flags1.equals(flags2)) {
boolean greater = flags1.size() > flags2.size();
if (greater) {
flags1.putAll(flags2);
} else {
flags2.putAll(flags1);
}
HashMap<Flag<?>, Object> net = (greater ? flags1 : flags2);
this.setFlags(net);
b.setFlags(net);
}
if (!this.getAlias().isEmpty()) {
b.setAlias(this.getAlias());
} else if (!b.getAlias().isEmpty()) {
this.setAlias(b.getAlias());
}
for (UUID uuid : this.getTrusted()) {
b.addTrusted(uuid);
}
for (UUID uuid : b.getTrusted()) {
this.addTrusted(uuid);
}
for (UUID uuid : this.getMembers()) {
b.addMember(uuid);
}
for (UUID uuid : b.getMembers()) {
this.addMember(uuid);
}
for (UUID uuid : this.getDenied()) {
b.addDenied(uuid);
}
for (UUID uuid : b.getDenied()) {
this.addDenied(uuid);
}
}
/**
* Remove the SE road (only effects terrain)
*/
public void removeRoadSouthEast() {
if (this.area.TYPE != 0 && this.area.TERRAIN > 1) {
if (this.area.TERRAIN == 3) {
return;
}
Plot other = this.getRelative(1, 1);
Location pos1 = this.getTopAbs().add(1, 0, 1);
Location pos2 = other.getBottomAbs().subtract(1, 0, 1);
pos1.setY(0);
pos2.setY(256);
ChunkManager.manager.regenerateRegion(pos1, pos2, true, null);
} else {
this.area.getPlotManager().removeRoadSouthEast(this.area, this);
}
}
/**
* Get the plot in a relative location<br>
* Note: May be null if the partial plot area does not include the relative location
* @param x
* @param y
* @return Plot
*/
public Plot getRelative(int x, int y) {
return this.area.getPlotAbs(this.id.getRelative(x, y));
}
public Plot getRelative(PlotArea area, int x, int y) {
return area.getPlotAbs(this.id.getRelative(x, y));
}
/**
* Get the plot in a relative direction<br>
* 0 = north<br>
* 1 = east<br>
* 2 = south<br>
* 3 = west<br>
* Note: May be null if the partial plot area does not include the relative location
* @param direction
* @return
*/
public Plot getRelative(int direction) {
return this.area.getPlotAbs(this.id.getRelative(direction));
}
/**
* Get a set of plots connected (and including) this plot<br>
* - This result is cached globally
* @return
*/
public Set<Plot> getConnectedPlots() {
if (this.settings == null) {
return Collections.singleton(this);
}
boolean[] merged = this.getMerged();
int hash = MainUtil.hash(merged);
if (hash == 0) {
return Collections.singleton(this);
}
if (connected_cache != null && connected_cache.contains(this)) {
return connected_cache;
}
regions_cache = null;
connected_cache = new HashSet<>();
ArrayDeque<Plot> frontier = new ArrayDeque<>();
HashSet<Object> queuecache = new HashSet<>();
connected_cache.add(this);
Plot tmp;
if (merged[0]) {
tmp = this.area.getPlotAbs(this.id.getRelative(0));
if (!tmp.getMerged(2)) {
// invalid merge
PS.debug("Fixing invalid merge: " + this);
if (tmp.isOwnerAbs(this.owner)) {
tmp.getSettings().setMerged(2, true);
DBFunc.setMerged(tmp, tmp.getSettings().getMerged());
} else {
this.getSettings().setMerged(0, false);
DBFunc.setMerged(this, this.getSettings().getMerged());
}
}
queuecache.add(tmp);
frontier.add(tmp);
}
if (merged[1]) {
tmp = this.area.getPlotAbs(this.id.getRelative(1));
if (!tmp.getMerged(3)) {
// invalid merge
PS.debug("Fixing invalid merge: " + this);
if (tmp.isOwnerAbs(this.owner)) {
tmp.getSettings().setMerged(3, true);
DBFunc.setMerged(tmp, tmp.getSettings().getMerged());
} else {
this.getSettings().setMerged(1, false);
DBFunc.setMerged(this, this.getSettings().getMerged());
}
}
queuecache.add(tmp);
frontier.add(tmp);
}
if (merged[2]) {
tmp = this.area.getPlotAbs(this.id.getRelative(2));
if (!tmp.getMerged(0)) {
// invalid merge
PS.debug("Fixing invalid merge: " + this);
if (tmp.isOwnerAbs(this.owner)) {
tmp.getSettings().setMerged(0, true);
DBFunc.setMerged(tmp, tmp.getSettings().getMerged());
} else {
this.getSettings().setMerged(2, false);
DBFunc.setMerged(this, this.getSettings().getMerged());
}
}
queuecache.add(tmp);
frontier.add(tmp);
}
if (merged[3]) {
tmp = this.area.getPlotAbs(this.id.getRelative(3));
if (!tmp.getMerged(1)) {
// invalid merge
PS.debug("Fixing invalid merge: " + this);
if (tmp.isOwnerAbs(this.owner)) {
tmp.getSettings().setMerged(1, true);
DBFunc.setMerged(tmp, tmp.getSettings().getMerged());
} else {
this.getSettings().setMerged(3, false);
DBFunc.setMerged(this, this.getSettings().getMerged());
}
}
queuecache.add(tmp);
frontier.add(tmp);
}
Plot current;
while ((current = frontier.poll()) != null) {
if (current.owner == null || current.settings == null) {
// Invalid plot
// merged onto unclaimed plot
PS.debug("Ignoring invalid merged plot: " + current + " | " + current.owner);
continue;
}
connected_cache.add(current);
queuecache.remove(current);
merged = current.getMerged();
if (merged[0]) {
tmp = current.area.getPlotAbs(current.id.getRelative(0));
if (tmp != null && !queuecache.contains(tmp) && !connected_cache.contains(tmp)) {
queuecache.add(tmp);
frontier.add(tmp);
}
}
if (merged[1]) {
tmp = current.area.getPlotAbs(current.id.getRelative(1));
if (tmp != null && !queuecache.contains(tmp) && !connected_cache.contains(tmp)) {
queuecache.add(tmp);
frontier.add(tmp);
}
}
if (merged[2]) {
tmp = current.area.getPlotAbs(current.id.getRelative(2));
if (tmp != null && !queuecache.contains(tmp) && !connected_cache.contains(tmp)) {
queuecache.add(tmp);
frontier.add(tmp);
}
}
if (merged[3]) {
tmp = current.area.getPlotAbs(current.id.getRelative(3));
if (tmp != null && !queuecache.contains(tmp) && !connected_cache.contains(tmp)) {
queuecache.add(tmp);
frontier.add(tmp);
}
}
}
return connected_cache;
}
/**
* This will combine each plot into effective rectangular regions<br>
* - This result is cached globally<br>
* - Useful for handling non rectangular shapes
* @return
*/
public HashSet<RegionWrapper> getRegions() {
if (regions_cache != null && connected_cache != null && connected_cache.contains(this)) {
return regions_cache;
}
if (!this.isMerged()) {
Location pos1 = this.getBottomAbs();
Location pos2 = this.getTopAbs();
connected_cache = new HashSet<>(Collections.singletonList(this));
regions_cache = new HashSet<>(1);
regions_cache.add(new RegionWrapper(pos1.getX(), pos2.getX(), pos1.getY(), pos2.getY(), pos1.getZ(), pos2.getZ()));
return regions_cache;
}
Set<Plot> plots = this.getConnectedPlots();
HashSet<RegionWrapper> regions = regions_cache = new HashSet<>();
HashSet<PlotId> visited = new HashSet<>();
for (Plot current : plots) {
if (visited.contains(current.getId())) {
continue;
}
boolean merge = true;
PlotId bot = new PlotId(current.getId().x, current.getId().y);
PlotId top = new PlotId(current.getId().x, current.getId().y);
while (merge) {
merge = false;
ArrayList<PlotId> ids = MainUtil.getPlotSelectionIds(new PlotId(bot.x, bot.y - 1), new PlotId(top.x, bot.y - 1));
boolean tmp = true;
for (PlotId id : ids) {
Plot plot = this.area.getPlotAbs(id);
if (plot == null || !plot.getMerged(2) || visited.contains(plot.getId())) {
tmp = false;
}
}
if (tmp) {
merge = true;
bot.y--;
}
ids = MainUtil.getPlotSelectionIds(new PlotId(top.x + 1, bot.y), new PlotId(top.x + 1, top.y));
tmp = true;
for (PlotId id : ids) {
Plot plot = this.area.getPlotAbs(id);
if (plot == null || !plot.getMerged(3) || visited.contains(plot.getId())) {
tmp = false;
}
}
if (tmp) {
merge = true;
top.x++;
}
ids = MainUtil.getPlotSelectionIds(new PlotId(bot.x, top.y + 1), new PlotId(top.x, top.y + 1));
tmp = true;
for (PlotId id : ids) {
Plot plot = this.area.getPlotAbs(id);
if (plot == null || !plot.getMerged(0) || visited.contains(plot.getId())) {
tmp = false;
}
}
if (tmp) {
merge = true;
top.y++;
}
ids = MainUtil.getPlotSelectionIds(new PlotId(bot.x - 1, bot.y), new PlotId(bot.x - 1, top.y));
tmp = true;
for (PlotId id : ids) {
Plot plot = this.area.getPlotAbs(id);
if (plot == null || !plot.getMerged(1) || visited.contains(plot.getId())) {
tmp = false;
}
}
if (tmp) {
merge = true;
bot.x--;
}
}
Location gtopabs = this.area.getPlotAbs(top).getTopAbs();
Location gbotabs = this.area.getPlotAbs(bot).getBottomAbs();
for (PlotId id : MainUtil.getPlotSelectionIds(bot, top)) {
visited.add(id);
}
for (int x = bot.x; x <= top.x; x++) {
Plot plot = this.area.getPlotAbs(new PlotId(x, top.y));
if (plot.getMerged(2)) {
// south wedge
Location toploc = plot.getExtendedTopAbs();
Location botabs = plot.getBottomAbs();
Location topabs = plot.getTopAbs();
regions.add(new RegionWrapper(botabs.getX(), topabs.getX(), topabs.getZ() + 1, toploc.getZ()));
if (plot.getMerged(5)) {
regions.add(new RegionWrapper(topabs.getX() + 1, toploc.getX(), topabs.getZ() + 1, toploc.getZ()));
// intersection
}
}
}
for (int y = bot.y; y <= top.y; y++) {
Plot plot = this.area.getPlotAbs(new PlotId(top.x, y));
if (plot.getMerged(1)) {
// east wedge
Location toploc = plot.getExtendedTopAbs();
Location botabs = plot.getBottomAbs();
Location topabs = plot.getTopAbs();
regions.add(new RegionWrapper(topabs.getX() + 1, toploc.getX(), botabs.getZ(), topabs.getZ()));
if (plot.getMerged(5)) {
regions.add(new RegionWrapper(topabs.getX() + 1, toploc.getX(), topabs.getZ() + 1, toploc.getZ()));
// intersection
}
}
}
regions.add(new RegionWrapper(gbotabs.getX(), gtopabs.getX(), gbotabs.getZ(), gtopabs.getZ()));
}
return regions;
}
/**
* Attempt to find the largest rectangular region in a plot (as plots can form non rectangular shapes)
* @return
*/
public RegionWrapper getLargestRegion() {
HashSet<RegionWrapper> regions = this.getRegions();
RegionWrapper max = null;
double area = Double.NEGATIVE_INFINITY;
for (RegionWrapper region : regions) {
double current = (region.maxX - (double) region.minX + 1) * (region.maxZ - (double) region.minZ + 1);
if (current > area) {
max = region;
area = current;
}
}
return max;
}
/**
* Do the plot entry tasks for each player in the plot<br>
* - Usually called when the plot state changes (unclaimed/claimed/flag change etc)
*/
public void reEnter() {
TaskManager.runTaskLater(new Runnable() {
@Override
public void run() {
for (PlotPlayer pp : Plot.this.getPlayersInPlot()) {
PlotListener.plotExit(pp, Plot.this);
PlotListener.plotEntry(pp, Plot.this);
}
}
}, 1);
}
/**
* Get all the corners of the plot (supports non-rectangular shapes).
* @return A list of the plot corners
*/
public List<Location> getAllCorners() {
Area area = new Area();
for (RegionWrapper region : this.getRegions()) {
Rectangle2D rect = new Rectangle2D.Double(region.minX - 0.6, region.minZ - 0.6, region.maxX - region.minX + 1.2, region.maxZ - region.minZ + 1.2);
Area rectArea = new Area(rect);
area.add(rectArea);
}
List<Location> locs = new ArrayList<>();
double[] coords = new double[6];
for (PathIterator pi = area.getPathIterator(null); !pi.isDone(); pi.next()) {
int type = pi.currentSegment(coords);
int x = (int) MathMan.inverseRound(coords[0]);
int z = (int) MathMan.inverseRound(coords[1]);
if (type != 4) {
locs.add(new Location(this.getWorldName(), x, 0, z));
}
}
return locs;
}
/**
* Teleport a player to a plot and send them the teleport message.
* @param player the player
* @return if the teleport succeeded
*/
public boolean teleportPlayer(final PlotPlayer player) {
Plot plot = this.getBasePlot(false);
boolean result = EventUtil.manager.callTeleport(player, player.getLocation(), plot);
if (result) {
final Location location;
if (this.area.HOME_ALLOW_NONMEMBER || plot.isAdded(player.getUUID())) {
location = this.getHome();
} else {
location = this.getDefaultHome();
}
if (Settings.Teleport.DELAY == 0 || Permissions.hasPermission(player, "plots.teleport.delay.bypass")) {
MainUtil.sendMessage(player, C.TELEPORTED_TO_PLOT);
player.teleport(location);
return true;
}
MainUtil.sendMessage(player, C.TELEPORT_IN_SECONDS, Settings.Teleport.DELAY + "");
final String name = player.getName();
TaskManager.TELEPORT_QUEUE.add(name);
TaskManager.runTaskLater(new Runnable() {
@Override
public void run() {
if (!TaskManager.TELEPORT_QUEUE.contains(name)) {
MainUtil.sendMessage(player, C.TELEPORT_FAILED);
return;
}
TaskManager.TELEPORT_QUEUE.remove(name);
if (player.isOnline()) {
MainUtil.sendMessage(player, C.TELEPORTED_TO_PLOT);
player.teleport(location);
}
}
}, Settings.Teleport.DELAY * 20);
return true;
}
return false;
}
public boolean isOnline() {
if (this.owner == null) {
return false;
}
if (!isMerged()) {
return UUIDHandler.getPlayer(this.owner) != null;
}
for (Plot current : getConnectedPlots()) {
if (current.hasOwner() && UUIDHandler.getPlayer(current.owner) != null) {
return true;
}
}
return false;
}
/**
* Set a component for a plot to the provided blocks<br>
* - E.g. floor, wall, border etc.<br>
* - The available components depend on the generator being used<br>
* @param component
* @param blocks
* @return
*/
public boolean setComponent(String component, PlotBlock[] blocks) {
if (StringMan.isEqualToAny(component, getManager().getPlotComponents(this.area, this.getId()))) {
EventUtil.manager.callComponentSet(this, component);
}
return this.getManager().setComponent(this.area, this.getId(), component, blocks);
}
public int getDistanceFromOrigin() {
Location bot = getManager().getPlotBottomLocAbs(this.area, id);
Location top = getManager().getPlotTopLocAbs(this.area, id);
return Math.max(Math.max(Math.abs(bot.getX()), Math.abs(bot.getZ())), Math.max(Math.abs(top.getX()), Math.abs(top.getZ())));
}
/**
* Expand the world border to include the provided plot (if applicable).
*/
public void updateWorldBorder() {
if (this.owner == null) {
return;
}
int border = this.area.getBorder();
if (border == Integer.MAX_VALUE) {
return;
}
int max = getDistanceFromOrigin();
if (max > border) {
this.area.setMeta("worldBorder", max);
}
}
/**
* Merges 2 plots Removes the road in-between <br>- Assumes plots are directly next to each other <br> - saves to DB
*
* @param lesserPlot
* @param removeRoads
*/
public void mergePlot(Plot lesserPlot, boolean removeRoads) {
Plot greaterPlot = this;
if (lesserPlot.getId().x == greaterPlot.getId().x) {
if (lesserPlot.getId().y > greaterPlot.getId().y) {
Plot tmp = lesserPlot;
lesserPlot = greaterPlot;
greaterPlot = tmp;
}
if (!lesserPlot.getMerged(2)) {
lesserPlot.clearRatings();
greaterPlot.clearRatings();
lesserPlot.setMerged(2, true);
greaterPlot.setMerged(0, true);
lesserPlot.mergeData(greaterPlot);
if (removeRoads) {
lesserPlot.removeRoadSouth();
Plot diagonal = greaterPlot.getRelative(1);
if (diagonal.getMerged(7)) {
lesserPlot.removeRoadSouthEast();
}
Plot below = greaterPlot.getRelative(3);
if (below.getMerged(4)) {
below.getRelative(0).removeRoadSouthEast();
}
}
}
} else {
if (lesserPlot.getId().x > greaterPlot.getId().x) {
Plot tmp = lesserPlot;
lesserPlot = greaterPlot;
greaterPlot = tmp;
}
if (!lesserPlot.getMerged(1)) {
lesserPlot.clearRatings();
greaterPlot.clearRatings();
lesserPlot.setMerged(1, true);
greaterPlot.setMerged(3, true);
lesserPlot.mergeData(greaterPlot);
if (removeRoads) {
Plot diagonal = greaterPlot.getRelative(2);
if (diagonal.getMerged(7)) {
lesserPlot.removeRoadSouthEast();
}
lesserPlot.removeRoadEast();
}
Plot below = greaterPlot.getRelative(0);
if (below.getMerged(6)) {
below.getRelative(3).removeRoadSouthEast();
}
}
}
}
/**
* Move a plot physically, as well as the corresponding settings.
* @param destination
* @param whenDone
* @param allowSwap
* @return
*/
public boolean move(final Plot destination, final Runnable whenDone, boolean allowSwap) {
final PlotId offset = new PlotId(destination.getId().x - this.getId().x, destination.getId().y - this.getId().y);
Location db = destination.getBottomAbs();
Location ob = this.getBottomAbs();
final int offsetX = db.getX() - ob.getX();
final int offsetZ = db.getZ() - ob.getZ();
if (this.owner == null) {
TaskManager.runTaskLater(whenDone, 1);
return false;
}
boolean occupied = false;
Set<Plot> plots = this.getConnectedPlots();
for (Plot plot : plots) {
Plot other = plot.getRelative(destination.getArea(), offset.x, offset.y);
if (other.hasOwner()) {
if (!allowSwap) {
TaskManager.runTaskLater(whenDone, 1);
return false;
}
occupied = true;
} else {
plot.removeSign();
}
}
// world border
destination.updateWorldBorder();
final ArrayDeque<RegionWrapper> regions = new ArrayDeque<>(this.getRegions());
// move / swap data
final PlotArea originArea = getArea();
for (Plot plot : plots) {
Plot other = plot.getRelative(destination.getArea(), offset.x, offset.y);
plot.swapData(other, null);
}
// copy terrain
Runnable move = new Runnable() {
@Override
public void run() {
if (regions.isEmpty()) {
Plot plot = destination.getRelative(0, 0);
for (Plot current : plot.getConnectedPlots()) {
getManager().claimPlot(current.getArea(), current);
Plot originPlot = originArea.getPlotAbs(new PlotId(current.id.x - offset.x, current.id.y - offset.y));
originPlot.getManager().unclaimPlot(originArea, originPlot, null);
}
plot.setSign();
TaskManager.runTask(whenDone);
return;
}
final Runnable task = this;
RegionWrapper region = regions.poll();
Location[] corners = region.getCorners(getWorldName());
final Location pos1 = corners[0];
final Location pos2 = corners[1];
Location newPos = pos1.clone().add(offsetX, 0, offsetZ);
newPos.setWorld(destination.getWorldName());
ChunkManager.manager.copyRegion(pos1, pos2, newPos, new Runnable() {
@Override
public void run() {
ChunkManager.manager.regenerateRegion(pos1, pos2, false, task);
}
});
}
};
Runnable swap = new Runnable() {
@Override
public void run() {
if (regions.isEmpty()) {
TaskManager.runTask(whenDone);
return;
}
RegionWrapper region = regions.poll();
Location[] corners = region.getCorners(getWorldName());
Location pos1 = corners[0];
Location pos2 = corners[1];
Location pos3 = pos1.clone().add(offsetX, 0, offsetZ);
Location pos4 = pos2.clone().add(offsetX, 0, offsetZ);
pos3.setWorld(destination.getWorldName());
pos4.setWorld(destination.getWorldName());
ChunkManager.manager.swap(pos1, pos2, pos3, pos4, this);
}
};
if (occupied) {
swap.run();
} else {
move.run();
}
return true;
}
/**
* Copy a plot to a location, both physically and the settings
* @param destination
* @param whenDone
* @return
*/
public boolean copy(final Plot destination, final Runnable whenDone) {
PlotId offset = new PlotId(destination.getId().x - this.getId().x, destination.getId().y - this.getId().y);
Location db = destination.getBottomAbs();
Location ob = this.getBottomAbs();
final int offsetX = db.getX() - ob.getX();
final int offsetZ = db.getZ() - ob.getZ();
if (this.owner == null) {
TaskManager.runTaskLater(whenDone, 1);
return false;
}
Set<Plot> plots = this.getConnectedPlots();
for (Plot plot : plots) {
Plot other = plot.getRelative(destination.getArea(), offset.x, offset.y);
if (other.hasOwner()) {
TaskManager.runTaskLater(whenDone, 1);
return false;
}
}
// world border
destination.updateWorldBorder();
// copy data
for (Plot plot : plots) {
Plot other = plot.getRelative(destination.getArea(), offset.x, offset.y);
other.create(plot.owner, false);
if (!plot.getFlags().isEmpty()) {
other.getSettings().flags = plot.getFlags();
DBFunc.setFlags(other, plot.getFlags());
}
if (plot.isMerged()) {
other.setMerged(plot.getMerged());
}
if (plot.members != null && !plot.members.isEmpty()) {
other.members = plot.members;
for (UUID member : plot.members) {
DBFunc.setMember(other, member);
}
}
if (plot.trusted != null && !plot.trusted.isEmpty()) {
other.trusted = plot.trusted;
for (UUID trusted : plot.trusted) {
DBFunc.setTrusted(other, trusted);
}
}
if (plot.denied != null && !plot.denied.isEmpty()) {
other.denied = plot.denied;
for (UUID denied : plot.denied) {
DBFunc.setDenied(other, denied);
}
}
}
// copy terrain
final ArrayDeque<RegionWrapper> regions = new ArrayDeque<>(this.getRegions());
Runnable run = new Runnable() {
@Override
public void run() {
if (regions.isEmpty()) {
for (Plot current : getConnectedPlots()) {
destination.getManager().claimPlot(destination.getArea(), destination);
}
destination.setSign();
TaskManager.runTask(whenDone);
return;
}
RegionWrapper region = regions.poll();
Location[] corners = region.getCorners(getWorldName());
Location pos1 = corners[0];
Location pos2 = corners[1];
Location newPos = pos1.clone().add(offsetX, 0, offsetZ);
newPos.setWorld(destination.getWorldName());
ChunkManager.manager.copyRegion(pos1, pos2, newPos, this);
}
};
run.run();
return true;
}
public boolean hasFlag(Flag<?> flag) {
return getFlags().containsKey(flag);
}
}