package com.intellectualcrafters.plot.object;
import com.intellectualcrafters.plot.PS;
import com.intellectualcrafters.plot.commands.RequiredType;
import com.intellectualcrafters.plot.config.Settings;
import com.intellectualcrafters.plot.database.DBFunc;
import com.intellectualcrafters.plot.flag.Flags;
import com.intellectualcrafters.plot.object.worlds.PlotAreaManager;
import com.intellectualcrafters.plot.object.worlds.SinglePlotArea;
import com.intellectualcrafters.plot.object.worlds.SinglePlotAreaManager;
import com.intellectualcrafters.plot.util.EconHandler;
import com.intellectualcrafters.plot.util.EventUtil;
import com.intellectualcrafters.plot.util.Permissions;
import com.intellectualcrafters.plot.util.PlotGameMode;
import com.intellectualcrafters.plot.util.PlotWeather;
import com.intellectualcrafters.plot.util.TaskManager;
import com.intellectualcrafters.plot.util.UUIDHandler;
import com.intellectualcrafters.plot.util.expiry.ExpireManager;
import com.plotsquared.general.commands.CommandCaller;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* The abstract class supporting {@code BukkitPlayer} and {@code SpongePlayer}.
*/
public abstract class PlotPlayer implements CommandCaller, OfflinePlotPlayer {
private Map<String, byte[]> metaMap = new HashMap<>();
/** The metadata map.*/
private ConcurrentHashMap<String, Object> meta;
/**
* Efficiently wrap a Player, or OfflinePlayer object to get a PlotPlayer (or fetch if it's already cached)<br>
* - Accepts sponge/bukkit Player (online)
* - Accepts player name (online)
* - Accepts UUID
* - Accepts bukkit OfflinePlayer (offline)
* @param player
* @return
*/
public static PlotPlayer wrap(Object player) {
return PS.get().IMP.wrapPlayer(player);
}
/**
* Get the cached PlotPlayer from a username<br>
* - This will return null if the player has not finished logging in or is not online
* @param name
* @return
*/
public static PlotPlayer get(String name) {
return UUIDHandler.getPlayer(name);
}
/**
* Set some session only metadata for this player.
* @param key
* @param value
*/
public void setMeta(String key, Object value) {
if (value == null) {
deleteMeta(key);
} else {
if (this.meta == null) {
this.meta = new ConcurrentHashMap<>();
}
this.meta.put(key, value);
}
}
/**
* Get the session metadata for a key.
* @param key the name of the metadata key
* @param <T> the object type to return
* @return the value assigned to the key or null if it does not exist
*/
public <T> T getMeta(String key) {
if (this.meta != null) {
return (T) this.meta.get(key);
}
return null;
}
public <T> T getMeta(String key, T defaultValue) {
T meta = getMeta(key);
if (meta == null) {
return defaultValue;
}
return meta;
}
/**
* Delete the metadata for a key.
* - metadata is session only
* - deleting other plugin's metadata may cause issues
* @param key
*/
public Object deleteMeta(String key) {
return this.meta == null ? null : this.meta.remove(key);
}
/**
* This player's name.
*
* @return the name of the player
*/
@Override
public String toString() {
return getName();
}
/**
* Get this player's current plot.
* @return the plot the player is standing on or null if standing on a road or not in a {@link PlotArea}
*/
public Plot getCurrentPlot() {
Plot value = getMeta("lastplot");
if (value == null && !Settings.Enabled_Components.EVENTS) {
return getLocation().getPlot();
}
return value;
}
/**
* Get the total number of allowed plots
* Possibly relevant: (To increment the player's allowed plots, see the example script on the wiki)
* @return number of allowed plots within the scope (globally, or in the player's current world as defined in the settings.yml)
*/
public int getAllowedPlots() {
return Permissions.hasPermissionRange(this, "plots.plot", Settings.Limit.MAX_PLOTS);
}
/**
* Get the total number of allowed clusters
* @return number of allowed clusters within the scope (globally, or in the player's current world as defined in the settings.yml)
*/
public int getAllowedClusters() {
return Permissions.hasPermissionRange(this, "plots.cluster", Settings.Limit.MAX_PLOTS);
}
/**
* Get the number of plots this player owns.
*
* @see #getPlotCount(String);
* @see #getPlots()
*
* @return number of plots within the scope (globally, or in the player's current world as defined in the settings.yml)
*/
public int getPlotCount() {
if (!Settings.Limit.GLOBAL) {
return getPlotCount(getLocation().getWorld());
}
final AtomicInteger count = new AtomicInteger(0);
final UUID uuid = getUUID();
PS.get().foreachPlotArea(new RunnableVal<PlotArea>() {
@Override
public void run(PlotArea value) {
if (!Settings.Done.COUNTS_TOWARDS_LIMIT) {
for (Plot plot : value.getPlotsAbs(uuid)) {
if (!plot.hasFlag(Flags.DONE)) {
count.incrementAndGet();
}
}
} else {
count.addAndGet(value.getPlotsAbs(uuid).size());
}
}
});
return count.get();
}
public int getClusterCount() {
if (!Settings.Limit.GLOBAL) {
return getClusterCount(getLocation().getWorld());
}
final AtomicInteger count = new AtomicInteger(0);
final UUID uuid = getUUID();
PS.get().foreachPlotArea(new RunnableVal<PlotArea>() {
@Override
public void run(PlotArea value) {
for (PlotCluster cluster : value.getClusters()) {
if (cluster.isOwner(getUUID())) {
count.incrementAndGet();
}
}
}
});
return count.get();
}
/**
* Get the number of plots this player owns in the world.
* @param world the name of the plotworld to check.
* @return
*/
public int getPlotCount(String world) {
UUID uuid = getUUID();
int count = 0;
for (PlotArea area : PS.get().getPlotAreas(world)) {
if (!Settings.Done.COUNTS_TOWARDS_LIMIT) {
for (Plot plot : area.getPlotsAbs(uuid)) {
if (!plot.getFlag(Flags.DONE).isPresent()) {
count++;
}
}
} else {
count += area.getPlotsAbs(uuid).size();
}
}
return count;
}
public int getClusterCount(String world) {
UUID uuid = getUUID();
int count = 0;
for (PlotArea area : PS.get().getPlotAreas(world)) {
for (PlotCluster cluster : area.getClusters()) {
if (cluster.isOwner(getUUID())) {
count++;
}
}
}
return count;
}
/**
* Get a {@code Set} of plots owned by this player.
* @see PS for more searching functions
* @see #getPlotCount() for the number of plots
* @return a {@code Set} of plots owned by the player
*/
public Set<Plot> getPlots() {
return PS.get().getPlots(this);
}
/**
* Return the PlotArea this player is currently in, or null.
* @return
*/
public PlotArea getPlotAreaAbs() {
return PS.get().getPlotAreaAbs(getLocation());
}
public PlotArea getApplicablePlotArea() {
return PS.get().getApplicablePlotArea(getLocation());
}
@Override
public RequiredType getSuperCaller() {
return RequiredType.PLAYER;
}
/////////////// PLAYER META ///////////////
////////////// PARTIALLY IMPLEMENTED ///////////
/**
* Get this player's last recorded location or null if they don't any plot relevant location.
* @return The location
*/
public Location getLocation() {
Location location = getMeta("location");
if (location != null) {
return location;
}
return getLocationFull();
}
////////////////////////////////////////////////
@Deprecated
public long getPreviousLogin() {
return getLastPlayed();
}
/**
* Get this player's full location (including yaw/pitch)
* @return
*/
public abstract Location getLocationFull();
/**
* Get this player's UUID.
* === !IMPORTANT ===<br>
* The UUID is dependent on the mode chosen in the settings.yml and may not be the same as Bukkit has
* (especially if using an old version of Bukkit that does not support UUIDs)
*
* @return UUID
*/
@Override public abstract UUID getUUID();
public boolean canTeleport(Location loc) {
Location current = getLocationFull();
teleport(loc);
boolean result = true;
if (!getLocation().equals(loc)) {
result = false;
}
teleport(current);
return result;
}
/**
* Teleport this player to a location.
* @param location the target location
*/
public abstract void teleport(Location location);
/**
* Set this compass target.
* @param location the target location
*/
public abstract void setCompassTarget(Location location);
/**
* Set player data that will persist restarts.
* - Please note that this is not intended to store large values
* - For session only data use meta
* @param key
*/
public void setAttribute(String key) {
setPersistentMeta("attrib_" + key, new byte[]{(byte) 1});
}
/**
* Retrieves the attribute of this player.
*
* @param key
* @return the attribute will be either true or false
*/
public boolean getAttribute(String key) {
if (!hasPersistentMeta("attrib_" + key)) {
return false;
}
return getPersistentMeta("attrib_" + key)[0] == 1;
}
/**
* Remove an attribute from a player.
* @param key
*/
public void removeAttribute(String key) {
removePersistentMeta("attrib_" + key);
}
/**
* Sets the local weather for this Player.
* @param weather the weather visible to the player
*/
public abstract void setWeather(PlotWeather weather);
/**
* Get this player's gamemode.
* @return the gamemode of the player.
*/
public abstract PlotGameMode getGameMode();
/**
* Set this player's gameMode.
* @param gameMode the gamemode to set
*/
public abstract void setGameMode(PlotGameMode gameMode);
/**
* Set this player's local time (ticks).
* @param time the time visible to the player
*/
public abstract void setTime(long time);
public abstract boolean getFlight();
/**
* Set this player's fly mode.
* @param fly if the player can fly
*/
public abstract void setFlight(boolean fly);
/**
* Play music at a location for this player.
* @param location where to play the music
* @param id the numerical record item id
*/
public abstract void playMusic(Location location, int id);
/**
* Check if this player is banned.
* @return true if the player is banned, false otherwise.
*/
public abstract boolean isBanned();
/**
* Kick this player from the game.
* @param message the reason for the kick
*/
public abstract void kick(String message);
/**
* Called when this player quits.
*/
public void unregister() {
Plot plot = getCurrentPlot();
if (plot != null) {
if (Settings.Enabled_Components.PERSISTENT_META) {
if (plot.getArea() instanceof SinglePlotArea) {
PlotId id = plot.getId();
int x = id.x;
int z = id.y;
ByteBuffer buffer = ByteBuffer.allocate(13);
buffer.putShort((short) x);
buffer.putShort((short) z);
Location loc = getLocation();
buffer.putInt(loc.getX());
buffer.put((byte) loc.getY());
buffer.putInt(loc.getZ());
setPersistentMeta("quitLoc", buffer.array());
}
}
EventUtil.manager.callLeave(this, plot);
}
if (Settings.Enabled_Components.BAN_DELETER && isBanned()) {
for (Plot owned : getPlots()) {
owned.deletePlot(null);
PS.debug(String.format("&cPlot &6%s &cwas deleted + cleared due to &6%s&c getting banned", plot.getId(), getName()));
}
}
String name = getName();
if (ExpireManager.IMP != null) {
ExpireManager.IMP.storeDate(getUUID(), System.currentTimeMillis());
}
UUIDHandler.getPlayers().remove(name);
PS.get().IMP.unregister(this);
}
/**
* Get the amount of clusters this player owns in the specific world.
* @param world
* @return
*/
public int getPlayerClusterCount(String world) {
UUID uuid = getUUID();
int count = 0;
for (PlotCluster cluster : PS.get().getClusters(world)) {
if (uuid.equals(cluster.owner)) {
count += cluster.getArea();
}
}
return count;
}
/**
* Get the amount of clusters this player owns.
* @return the number of clusters this player owns
*/
public int getPlayerClusterCount() {
final AtomicInteger count = new AtomicInteger();
PS.get().foreachPlotArea(new RunnableVal<PlotArea>() {
@Override
public void run(PlotArea value) {
count.addAndGet(value.getClusters().size());
}
});
return count.get();
}
/**
* Return a {@code Set} of all plots this player owns in a certain world.
* @param world the world to retrieve plots from
* @return a {@code Set} of plots this player owns in the provided world
*/
public Set<Plot> getPlots(String world) {
UUID uuid = getUUID();
HashSet<Plot> plots = new HashSet<>();
for (Plot plot : PS.get().getPlots(world)) {
if (plot.isOwner(uuid)) {
plots.add(plot);
}
}
return plots;
}
public void populatePersistentMetaMap() {
if (Settings.Enabled_Components.PERSISTENT_META) {
DBFunc.getPersistentMeta(getUUID(), new RunnableVal<Map<String, byte[]>>() {
@Override
public void run(Map<String, byte[]> value) {
PlotPlayer.this.metaMap = value;
if (!value.isEmpty()) {
if (Settings.Enabled_Components.PERSISTENT_META) {
PlotAreaManager manager = PS.get().getPlotAreaManager();
if (manager instanceof SinglePlotAreaManager) {
PlotArea area = ((SinglePlotAreaManager) manager).getArea();
byte[] arr = PlotPlayer.this.getPersistentMeta("quitLoc");
if (arr != null && getMeta("teleportOnLogin", true)) {
ByteBuffer quitWorld = ByteBuffer.wrap(arr);
PlotId id = new PlotId(quitWorld.getShort(), quitWorld.getShort());
int x = quitWorld.getInt();
int y = quitWorld.get() & 0xFF;
int z = quitWorld.getInt();
Plot plot = area.getOwnedPlot(id);
if (plot != null && plot.isLoaded()) {
final Location loc = new Location(plot.getWorldName(), x, y, z);
TaskManager.IMP.sync(new RunnableVal<Object>() {
@Override
public void run(Object o) {
if (getMeta("teleportOnLogin", true)) {
teleport(loc);
}
}
});
}
}
}
}
}
}
});
}
}
public byte[] getPersistentMeta(String key) {
return this.metaMap.get(key);
}
public void removePersistentMeta(String key) {
if (this.metaMap.containsKey(key)) {
this.metaMap.remove(key);
}
if (Settings.Enabled_Components.PERSISTENT_META) {
DBFunc.removePersistentMeta(getUUID(), key);
}
}
public void setPersistentMeta(String key, byte[] value) {
boolean delete = hasPersistentMeta(key);
this.metaMap.put(key, value);
if (Settings.Enabled_Components.PERSISTENT_META) {
DBFunc.addPersistentMeta(getUUID(), key, value, delete);
}
}
public boolean hasPersistentMeta(String key) {
return this.metaMap.containsKey(key);
}
public abstract void stopSpectating();
/**
* The amount of money this Player has.
* @return
*/
public double getMoney() {
return EconHandler.manager == null ? 0 : EconHandler.manager.getMoney(this);
}
public void withdraw(double amount) {
if (EconHandler.manager != null) {
EconHandler.manager.withdrawMoney(this, amount);
}
}
public void deposit(double amount) {
if (EconHandler.manager != null) {
EconHandler.manager.depositMoney(this, amount);
}
}
}