/* * PlotSquared, a land and world management plugin for Minecraft. * Copyright (C) IntellectualSites * Copyright (C) IntellectualSites team and contributors * * 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.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.plotsquared.core.PlotSquared; import com.plotsquared.core.collection.QuadMap; import com.plotsquared.core.configuration.ConfigurationNode; import com.plotsquared.core.configuration.ConfigurationSection; import com.plotsquared.core.configuration.ConfigurationUtil; import com.plotsquared.core.configuration.Settings; import com.plotsquared.core.configuration.caption.TranslatableCaption; import com.plotsquared.core.configuration.file.YamlConfiguration; import com.plotsquared.core.generator.GridPlotWorld; import com.plotsquared.core.generator.IndependentPlotGenerator; import com.plotsquared.core.inject.annotations.WorldConfig; 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.MetaDataAccess; import com.plotsquared.core.player.PlayerMetaDataKeys; import com.plotsquared.core.player.PlotPlayer; import com.plotsquared.core.plot.flag.FlagContainer; import com.plotsquared.core.plot.flag.FlagParseException; import com.plotsquared.core.plot.flag.GlobalFlagContainer; import com.plotsquared.core.plot.flag.PlotFlag; import com.plotsquared.core.plot.flag.implementations.DoneFlag; import com.plotsquared.core.queue.GlobalBlockQueue; import com.plotsquared.core.queue.QueueCoordinator; import com.plotsquared.core.util.MathMan; import com.plotsquared.core.util.PlotExpression; import com.plotsquared.core.util.RegionUtil; import com.plotsquared.core.util.StringMan; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.biome.BiomeTypes; import com.sk89q.worldedit.world.gamemode.GameMode; import com.sk89q.worldedit.world.gamemode.GameModes; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.ComponentLike; import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.tag.Tag; import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; 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 org.jetbrains.annotations.NotNull; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; /** * @author Jesse Boyd, Alexander Söderberg */ public abstract class PlotArea implements ComponentLike { private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + PlotArea.class.getSimpleName()); private static final MiniMessage MINI_MESSAGE = MiniMessage.builder().build(); private static final DecimalFormat FLAG_DECIMAL_FORMAT = new DecimalFormat("0"); static { FLAG_DECIMAL_FORMAT.setMaximumFractionDigits(340); } protected final ConcurrentHashMap plots = new ConcurrentHashMap<>(); @NonNull private final String worldName; private final String id; @NonNull private final PlotManager plotManager; private final int worldHash; private final PlotId min; private final PlotId max; @NonNull private final IndependentPlotGenerator generator; /** * Area flag container */ private final FlagContainer flagContainer = new FlagContainer(GlobalFlagContainer.getInstance()); private final FlagContainer roadFlagContainer = new FlagContainer(GlobalFlagContainer.getInstance()); private final YamlConfiguration worldConfiguration; private final GlobalBlockQueue globalBlockQueue; private boolean roadFlags = false; private boolean autoMerge = false; private boolean allowSigns = true; private boolean miscSpawnUnowned = false; private boolean mobSpawning = false; private boolean mobSpawnerSpawning = false; private BiomeType plotBiome = BiomeTypes.FOREST; private boolean plotChat = true; private boolean forcingPlotChat = false; private boolean schematicClaimSpecify = false; private boolean schematicOnClaim = false; private String schematicFile = "null"; private boolean spawnEggs = false; private boolean spawnCustom = true; private boolean spawnBreeding = false; private PlotAreaType type = PlotAreaType.NORMAL; private PlotAreaTerrainType terrain = PlotAreaTerrainType.NONE; private boolean homeAllowNonmember = false; private BlockLoc nonmemberHome; private BlockLoc defaultHome; private int maxBuildHeight = PlotSquared.platform().versionMaxHeight() + 1; // Exclusive private int minBuildHeight = PlotSquared.platform().versionMinHeight() + 1; // Inclusive private int maxGenHeight = PlotSquared.platform().versionMaxHeight(); // Inclusive private int minGenHeight = PlotSquared.platform().versionMinHeight(); // Inclusive private GameMode gameMode = GameModes.CREATIVE; private Map prices = new HashMap<>(); private List schematics = new ArrayList<>(); private boolean worldBorder = false; private boolean useEconomy = false; private int hash; private CuboidRegion region; private ConcurrentHashMap meta; private QuadMap clusters; private String signMaterial = "OAK_WALL_SIGN"; private String legacySignMaterial = "WALL_SIGN"; public PlotArea( final @NonNull String worldName, final @Nullable String id, @NonNull IndependentPlotGenerator generator, final @Nullable PlotId min, final @Nullable PlotId max, @WorldConfig final @Nullable YamlConfiguration worldConfiguration, final @NonNull GlobalBlockQueue blockQueue ) { this.worldName = worldName; this.id = id; this.plotManager = createManager(); this.generator = generator; this.globalBlockQueue = blockQueue; if (min == null || max == null) { if (min != max) { throw new IllegalArgumentException( "None of the ids can be null for this constructor"); } this.min = null; this.max = null; } else { this.min = min; this.max = max; } this.worldHash = worldName.hashCode(); this.worldConfiguration = worldConfiguration; } private static Collection> parseFlags(List flagStrings) { final Collection> flags = new ArrayList<>(); for (final String key : flagStrings) { final String[] split; if (key.contains(";")) { split = key.split(";"); } else { split = key.split(":"); } final PlotFlag flagInstance = GlobalFlagContainer.getInstance().getFlagFromString(split[0]); if (flagInstance != null) { try { flags.add(flagInstance.parse(split[1])); } catch (final FlagParseException e) { LOGGER.warn( "Failed to parse default flag with key '{}' and value '{}'. " + "Reason: {}. This flag will not be added as a default flag.", e.getFlag().getName(), e.getValue(), e.getErrorMessage() ); e.printStackTrace(); } } } return flags; } @NonNull protected abstract PlotManager createManager(); public QueueCoordinator getQueue() { return this.globalBlockQueue.getNewQueue(PlotSquared.platform().worldUtil().getWeWorld(worldName)); } /** * Returns the region for this PlotArea, or a CuboidRegion encompassing * the whole world if none exists. * * @return CuboidRegion */ public CuboidRegion getRegion() { this.region = getRegionAbs(); if (this.region == null) { return new CuboidRegion( BlockVector3.at(Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE), BlockVector3.at(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE) ); } return this.region; } /** * Returns the region for this PlotArea. * * @return CuboidRegion or null if no applicable region */ private CuboidRegion getRegionAbs() { if (this.region == null) { if (this.min != null) { Location bot = getPlotManager().getPlotBottomLocAbs(this.min); Location top = getPlotManager().getPlotTopLocAbs(this.max); BlockVector3 pos1 = bot.getBlockVector3().subtract(BlockVector3.ONE); BlockVector3 pos2 = top.getBlockVector3().add(BlockVector3.ONE); this.region = new CuboidRegion(pos1, pos2); } } return this.region; } /** * Returns the minimum value of a {@link PlotId}. * * @return the minimum value for a {@link PlotId} */ public @NonNull PlotId getMin() { return this.min == null ? PlotId.of(Integer.MIN_VALUE, Integer.MIN_VALUE) : this.min; } /** * Returns the max PlotId. * * @return the maximum value for a {@link PlotId} */ public @NonNull PlotId getMax() { return this.max == null ? PlotId.of(Integer.MAX_VALUE, Integer.MAX_VALUE) : this.max; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } PlotArea plotarea = (PlotArea) obj; return this.getWorldHash() == plotarea.getWorldHash() && this.getWorldName() .equals(plotarea.getWorldName()) && StringMan.isEqual(this.getId(), plotarea.getId()); } public Set getClusters() { return this.clusters == null ? new HashSet<>() : this.clusters.getAll(); } /** * Check if a PlotArea is compatible (move/copy etc.). * * @param plotArea the {@link PlotArea} to compare * @return {@code true} if both areas are compatible */ public boolean isCompatible(final @NonNull PlotArea plotArea) { final ConfigurationSection section = this.worldConfiguration.getConfigurationSection("worlds"); for (ConfigurationNode setting : plotArea.getSettingNodes()) { Object constant = section.get(plotArea.worldName + '.' + setting.getConstant()); if (constant == null || !constant .equals(section.get(this.worldName + '.' + setting.getConstant()))) { return false; } } return true; } /** * When a world is created, the following method will be called for each. * * @param config Configuration Section */ public void loadDefaultConfiguration(ConfigurationSection config) { if ((this.min != null || this.max != null) && !(this instanceof GridPlotWorld)) { throw new IllegalArgumentException("Must extend GridPlotWorld to provide"); } if (config.contains("generator.terrain")) { this.terrain = ConfigurationUtil.getTerrain(config); this.type = ConfigurationUtil.getType(config); } this.mobSpawning = config.getBoolean("natural_mob_spawning"); this.miscSpawnUnowned = config.getBoolean("misc_spawn_unowned"); this.mobSpawnerSpawning = config.getBoolean("mob_spawner_spawning"); this.autoMerge = config.getBoolean("plot.auto_merge"); this.allowSigns = config.getBoolean("plot.create_signs"); if (PlotSquared.platform().serverVersion()[1] == 13) { this.legacySignMaterial = config.getString("plot.legacy_sign_material"); } else { this.signMaterial = config.getString("plot.sign_material"); } String biomeString = config.getString("plot.biome"); if (!biomeString.startsWith("minecraft:")) { biomeString = "minecraft:" + biomeString; config.set("plot.biome", biomeString.toLowerCase()); } this.plotBiome = ConfigurationUtil.BIOME.parseString(biomeString.toLowerCase()); this.schematicOnClaim = config.getBoolean("schematic.on_claim"); this.schematicFile = config.getString("schematic.file"); this.schematicClaimSpecify = config.getBoolean("schematic.specify_on_claim"); this.schematics = new ArrayList<>(config.getStringList("schematic.schematics")); this.schematics.replaceAll(String::toLowerCase); this.useEconomy = config.getBoolean("economy.use"); ConfigurationSection priceSection = config.getConfigurationSection("economy.prices"); if (this.useEconomy) { this.prices = new HashMap<>(); for (String key : priceSection.getKeys(false)) { String raw = priceSection.getString(key); if (raw.contains("{arg}")) { raw = raw.replace("{arg}", "plots"); priceSection.set(key, raw); // update if replaced } this.prices.put(key, PlotExpression.compile(raw, "plots")); } } this.plotChat = config.getBoolean("chat.enabled"); this.forcingPlotChat = config.getBoolean("chat.forced"); this.worldBorder = config.getBoolean("world.border"); this.maxBuildHeight = config.getInt("world.max_height"); this.minBuildHeight = config.getInt("world.min_height"); this.minGenHeight = config.getInt("world.min_gen_height"); this.maxGenHeight = config.getInt("world.max_gen_height"); switch (config.getString("world.gamemode").toLowerCase()) { case "creative", "c", "1" -> this.gameMode = GameModes.CREATIVE; case "adventure", "a", "2" -> this.gameMode = GameModes.ADVENTURE; case "spectator", "3" -> this.gameMode = GameModes.SPECTATOR; default -> this.gameMode = GameModes.SURVIVAL; } String homeNonMembers = config.getString("home.nonmembers"); String homeDefault = config.getString("home.default"); this.defaultHome = BlockLoc.fromString(homeDefault); this.homeAllowNonmember = homeNonMembers.equalsIgnoreCase(homeDefault); if (this.homeAllowNonmember) { this.nonmemberHome = defaultHome; } else { this.nonmemberHome = BlockLoc.fromString(homeNonMembers); } if ("side".equalsIgnoreCase(homeDefault)) { this.defaultHome = null; } else if (StringMan.isEqualIgnoreCaseToAny(homeDefault, "center", "middle", "centre")) { this.defaultHome = new BlockLoc(Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE); } else { try { /*String[] split = homeDefault.split(","); this.DEFAULT_HOME = new PlotLoc(Integer.parseInt(split[0]), Integer.parseInt(split[1]));*/ this.defaultHome = BlockLoc.fromString(homeDefault); } catch (NumberFormatException ignored) { this.defaultHome = null; } } List flags = config.getStringList("flags.default"); if (flags.isEmpty()) { flags = config.getStringList("flags"); if (flags.isEmpty()) { flags = new ArrayList<>(); ConfigurationSection section = config.getConfigurationSection("flags"); Set keys = section.getKeys(false); for (String key : keys) { if (!"default".equals(key)) { flags.add(key + ';' + section.get(key)); } } } } this.getFlagContainer().addAll(parseFlags(flags)); ConsolePlayer.getConsole().sendMessage( TranslatableCaption.of("flags.area_flags"), TagResolver.resolver("flags", Tag.inserting(Component.text(flags.toString()))) ); this.spawnEggs = config.getBoolean("event.spawn.egg"); this.spawnCustom = config.getBoolean("event.spawn.custom"); this.spawnBreeding = config.getBoolean("event.spawn.breeding"); List roadflags = config.getStringList("road.flags"); if (roadflags.isEmpty()) { roadflags = new ArrayList<>(); ConfigurationSection section = config.getConfigurationSection("road.flags"); Set keys = section.getKeys(false); for (String key : keys) { if (!"default".equals(key)) { roadflags.add(key + ';' + section.get(key)); } } } this.roadFlags = roadflags.size() > 0; this.getRoadFlagContainer().addAll(parseFlags(roadflags)); ConsolePlayer.getConsole().sendMessage( TranslatableCaption.of("flags.road_flags"), TagResolver.resolver("flags", Tag.inserting(Component.text(roadflags.toString()))) ); loadConfiguration(config); } public abstract void loadConfiguration(ConfigurationSection config); /** * Saving core PlotArea settings. * * @param config Configuration Section */ public void saveConfiguration(ConfigurationSection config) { HashMap options = new HashMap<>(); options.put("natural_mob_spawning", this.isMobSpawning()); options.put("misc_spawn_unowned", this.isMiscSpawnUnowned()); options.put("mob_spawner_spawning", this.isMobSpawnerSpawning()); options.put("plot.auto_merge", this.isAutoMerge()); options.put("plot.create_signs", this.allowSigns()); if (PlotSquared.platform().serverVersion()[1] == 13) { options.put("plot.legacy_sign_material", this.legacySignMaterial); } else { options.put("plot.sign_material", this.signMaterial()); } options.put("plot.biome", "minecraft:forest"); options.put("schematic.on_claim", this.isSchematicOnClaim()); options.put("schematic.file", this.getSchematicFile()); options.put("schematic.specify_on_claim", this.isSchematicClaimSpecify()); options.put("schematic.schematics", this.getSchematics()); options.put("economy.use", this.useEconomy()); options.put("economy.prices.claim", 100); options.put("economy.prices.merge", 100); options.put("economy.prices.sell", 100); options.put("chat.enabled", this.isPlotChat()); options.put("chat.forced", this.isForcingPlotChat()); options.put("flags.default", null); options.put("event.spawn.egg", this.isSpawnEggs()); options.put("event.spawn.custom", this.isSpawnCustom()); options.put("event.spawn.breeding", this.isSpawnBreeding()); options.put("world.border", this.hasWorldBorder()); options.put("home.default", "side"); String position = config.getString( "home.nonmembers", config.getBoolean("home.allow-nonmembers", false) ? config.getString("home.default", "side") : "side" ); options.put("home.nonmembers", position); options.put("world.max_height", this.getMaxBuildHeight()); options.put("world.min_height", this.getMinBuildHeight()); options.put("world.min_gen_height", this.getMinGenHeight()); options.put("world.max_gen_height", this.getMaxGenHeight()); options.put("world.gamemode", this.getGameMode().getName().toLowerCase()); options.put("road.flags.default", null); if (this.getType() != PlotAreaType.NORMAL) { options.put("generator.terrain", this.getTerrain()); options.put("generator.type", this.getType().toString()); } ConfigurationNode[] settings = getSettingNodes(); /* * Saving generator specific settings */ for (ConfigurationNode setting : settings) { options.put(setting.getConstant(), setting.getValue()); } for (Entry stringObjectEntry : options.entrySet()) { if (!config.contains(stringObjectEntry.getKey())) { config.set(stringObjectEntry.getKey(), stringObjectEntry.getValue()); } } if (!config.contains("flags")) { config.set( "flags.use", "63,64,68,69,71,77,96,143,167,193,194,195,196,197,77,143,69,70,72,147,148,107,183,184,185,186,187,132" ); } if (!config.contains("road.flags")) { config.set("road.flags.liquid-flow", false); } } @NonNull @Override public String toString() { if (this.getId() == null) { return this.getWorldName(); } else { return this.getWorldName() + ";" + this.getId(); } } @Override public @NotNull Component asComponent() { return Component.text(toString()); } @Override public int hashCode() { if (this.hash != 0) { return this.hash; } return this.hash = toString().hashCode(); } /** * Used for the /plot setup command Return null if you do not want to support this feature * * @return ConfigurationNode[] */ public abstract ConfigurationNode[] getSettingNodes(); /** * Gets the {@link Plot} at a location. * * @param location the location * @return the {@link Plot} or null if none exists */ public @Nullable Plot getPlotAbs(final @NonNull Location location) { final PlotId pid = this.getPlotManager().getPlotId(location.getX(), location.getY(), location.getZ()); if (pid == null) { return null; } return getPlotAbs(pid); } /** * Gets the base plot at a location. * * @param location the location * @return base Plot */ public @Nullable Plot getPlot(final @NonNull Location location) { final PlotId pid = this.getPlotManager().getPlotId(location.getX(), location.getY(), location.getZ()); if (pid == null) { return null; } return getPlot(pid); } /** * Get the owned base plot at a location. * * @param location the location * @return the base plot or null */ public @Nullable Plot getOwnedPlot(final @NonNull Location location) { final PlotId pid = this.getPlotManager().getPlotId(location.getX(), location.getY(), location.getZ()); if (pid == null) { return null; } Plot plot = this.plots.get(pid); return plot == null ? null : plot.getBasePlot(false); } /** * Get the owned plot at a location. * * @param location the location * @return Plot or null */ public @Nullable Plot getOwnedPlotAbs(final @NonNull Location location) { final PlotId pid = this.getPlotManager().getPlotId(location.getX(), location.getY(), location.getZ()); if (pid == null) { return null; } return this.plots.get(pid); } /** * Get the owned Plot at a PlotId. * * @param id the {@link PlotId} * @return the plot or null */ public @Nullable Plot getOwnedPlotAbs(final @NonNull PlotId id) { return this.plots.get(id); } public @Nullable Plot getOwnedPlot(final @NonNull PlotId id) { Plot plot = this.plots.get(id); return plot == null ? null : plot.getBasePlot(false); } public boolean contains(final int x, final int z) { return this.getType() != PlotAreaType.PARTIAL || RegionUtil.contains(getRegionAbs(), x, z); } public boolean contains(final @NonNull PlotId id) { return this.min == null || (id.getX() >= this.min.getX() && id.getX() <= this.max.getX() && id.getY() >= this.min.getY() && id.getY() <= this.max.getY()); } public boolean contains(final @NonNull Location location) { return StringMan.isEqual(location.getWorldName(), this.getWorldName()) && ( getRegionAbs() == null || this.region.contains(location.getBlockVector3())); } /** * Get if the {@code PlotArea}'s build range (min build height -> max build height) contains the given y value * * @param y y height * @return if build height contains y */ public boolean buildRangeContainsY(int y) { return y >= minBuildHeight && y < maxBuildHeight; } /** * Utility method to check if the player is attempting to place blocks outside the build area, and notify of this if the * player does not have permissions. * * @param player Player to check * @param y y height to check * @return true if outside build area with no permissions * @since 6.9.1 */ public boolean notifyIfOutsideBuildArea(PlotPlayer player, int y) { if (!buildRangeContainsY(y) && !player.hasPermission(Permission.PERMISSION_ADMIN_BUILD_HEIGHT_LIMIT)) { player.sendMessage( TranslatableCaption.of("height.height_limit"), TagResolver.builder() .tag("minHeight", Tag.inserting(Component.text(minBuildHeight))) .tag( "maxHeight", Tag.inserting(Component.text(maxBuildHeight)) ).build() ); // Return true if "failed" as the method will always be inverted otherwise return true; } return false; } public @NonNull Set getPlotsAbs(final UUID uuid) { if (uuid == null) { return Collections.emptySet(); } final HashSet myPlots = new HashSet<>(); forEachPlotAbs(value -> { if (uuid.equals(value.getOwnerAbs())) { myPlots.add(value); } }); return myPlots; } public @NonNull Set getPlots(final @NonNull UUID uuid) { return getPlots().stream().filter(plot -> plot.isBasePlot() && plot.isOwner(uuid)) .collect(ImmutableSet.toImmutableSet()); } /** * A collection of the claimed plots in this {@link PlotArea}. * * @return a collection of claimed plots */ public Collection getPlots() { return this.plots.values(); } public int getPlotCount(final @NonNull UUID uuid) { if (!Settings.Done.COUNTS_TOWARDS_LIMIT) { return (int) getPlotsAbs(uuid).stream().filter(plot -> !DoneFlag.isDone(plot)).count(); } return getPlotsAbs(uuid).size(); } /** * Retrieves the plots for the player in this PlotArea. * * @param player player to get plots of * @return set of player's plots * @deprecated Use {@link #getPlots(UUID)} */ @Deprecated public Set getPlots(final @NonNull PlotPlayer player) { return getPlots(player.getUUID()); } //todo check if this method is needed in this class public boolean hasPlot(final @NonNull UUID uuid) { return this.plots.entrySet().stream().anyMatch(entry -> entry.getValue().isOwner(uuid)); } public int getPlotCount(final @Nullable PlotPlayer player) { return player != null ? getPlotCount(player.getUUID()) : 0; } public @Nullable Plot getPlotAbs(final @NonNull PlotId id) { Plot plot = getOwnedPlotAbs(id); if (plot == null) { if (this.min != null && (id.getX() < this.min.getX() || id.getX() > this.max.getX() || id.getY() < this.min.getY() || id.getY() > this.max.getY())) { return null; } return new Plot(this, id); } return plot; } public @Nullable Plot getPlot(final @NonNull PlotId id) { final Plot plot = getOwnedPlotAbs(id); if (plot == null) { if (this.min != null && (id.getX() < this.min.getX() || id.getX() > this.max.getX() || id.getY() < this.min.getY() || id.getY() > this.max.getY())) { return null; } return new Plot(this, id); } return plot.getBasePlot(false); } /** * Retrieves the number of claimed plot in the {@link PlotArea}. * * @return the number of claimed plots */ public int getPlotCount() { return this.plots.size(); } public @Nullable PlotCluster getCluster(final @NonNull Location location) { final Plot plot = getPlot(location); if (plot == null) { return null; } return this.clusters != null ? this.clusters.get(plot.getId().getX(), plot.getId().getY()) : null; } public @Nullable PlotCluster getFirstIntersectingCluster( final @NonNull PlotId pos1, final @NonNull PlotId pos2 ) { if (this.clusters == null) { return null; } for (PlotCluster cluster : this.clusters.getAll()) { if (cluster.intersects(pos1, pos2)) { return cluster; } } return null; } @Nullable PlotCluster getCluster(final @NonNull PlotId id) { return this.clusters != null ? this.clusters.get(id.getX(), id.getY()) : null; } /** * 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 @Nullable Object value) { if (this.meta == null) { this.meta = new ConcurrentHashMap<>(); } this.meta.put(key, value); } public @NonNull T getMeta(final @NonNull String key, final @NonNull T def) { final Object v = getMeta(key); return v == null ? def : (T) v; } /** * Get the metadata for a key
*
* For persistent metadata use the flag system * * @param key metadata key to get value for * @return metadata value */ public @Nullable Object getMeta(final @NonNull String key) { if (this.meta != null) { return this.meta.get(key); } return null; } @SuppressWarnings("unused") public @NonNull Set getBasePlots() { final HashSet myPlots = new HashSet<>(getPlots()); myPlots.removeIf(plot -> !plot.isBasePlot()); return myPlots; } private void forEachPlotAbs(Consumer run) { for (final Entry entry : this.plots.entrySet()) { run.accept(entry.getValue()); } } public void forEachBasePlot(Consumer run) { for (final Plot plot : getPlots()) { if (plot.isBasePlot()) { run.accept(plot); } } } /** * Returns an ImmutableMap of PlotId's and Plots in this PlotArea. * * @return map of PlotId against Plot for all plots in this area * @deprecated Poorly implemented. May be removed in future. */ //todo eventually remove @Deprecated public @NonNull Map getPlotsRaw() { return ImmutableMap.copyOf(plots); } public @NonNull Set> getPlotEntries() { return this.plots.entrySet(); } public boolean addPlot(final @NonNull Plot plot) { for (final PlotPlayer pp : plot.getPlayersInPlot()) { try (final MetaDataAccess metaDataAccess = pp.accessTemporaryMetaData( PlayerMetaDataKeys.TEMPORARY_LAST_PLOT)) { metaDataAccess.set(plot); } } return this.plots.put(plot.getId(), plot) == null; } public Plot getNextFreePlot(final PlotPlayer player, @Nullable PlotId start) { int plots; PlotId center; PlotId min = getMin(); PlotId max = getMax(); if (getType() == PlotAreaType.PARTIAL) { center = PlotId.of(MathMan.average(min.getX(), max.getX()), MathMan.average(min.getY(), max.getY())); plots = Math.max(max.getX() - min.getX() + 1, max.getY() - min.getY() + 1) + 1; if (start != null) { start = PlotId.of(start.getX() - center.getX(), start.getY() - center.getY()); } } else { center = PlotId.of(0, 0); plots = Integer.MAX_VALUE; } for (int i = 0; i < plots; i++) { if (start == null) { start = getMeta("lastPlot", PlotId.of(0, 0)); } else { start = start.getNextId(); } PlotId currentId = PlotId.of(center.getX() + start.getX(), center.getY() + start.getY()); Plot plot = getPlotAbs(currentId); if (plot != null && plot.canClaim(player)) { setMeta("lastPlot", start); return plot; } } return null; } public boolean addPlotIfAbsent(final @NonNull Plot plot) { if (this.plots.putIfAbsent(plot.getId(), plot) == null) { for (PlotPlayer pp : plot.getPlayersInPlot()) { try (final MetaDataAccess metaDataAccess = pp.accessTemporaryMetaData( PlayerMetaDataKeys.TEMPORARY_LAST_PLOT)) { metaDataAccess.set(plot); } } return true; } return false; } public boolean addPlotAbs(final @NonNull Plot plot) { return this.plots.put(plot.getId(), plot) == null; } /** * Get the plot border distance for a world
* * @return The border distance or Integer.MAX_VALUE if no border is set */ public int getBorder() { final Integer meta = (Integer) getMeta("worldBorder"); if (meta != null) { int border = meta + 1; if (border == 0) { return Integer.MAX_VALUE; } else { return border; } } return Integer.MAX_VALUE; } /** * Setup the plot border for a world (usually done when the world is created). */ public void setupBorder() { if (!this.hasWorldBorder()) { return; } final Integer meta = (Integer) getMeta("worldBorder"); if (meta == null) { setMeta("worldBorder", 1); } for (final Plot plot : getPlots()) { plot.updateWorldBorder(); } } /** * Delete the metadata for a key. * - metadata is session only * - deleting other plugin's metadata may cause issues * * @param key Meta data key */ public void deleteMeta(final @NonNull String key) { if (this.meta != null) { this.meta.remove(key); } } public @Nullable List canClaim( final @Nullable PlotPlayer player, final @NonNull PlotId pos1, final @NonNull PlotId pos2 ) { if (pos1.getX() == pos2.getX() && pos1.getY() == pos2.getY()) { if (getOwnedPlot(pos1) != null) { return null; } final Plot plot = getPlotAbs(pos1); if (plot == null) { return null; } if (plot.canClaim(player)) { return Collections.singletonList(plot); } else { return null; } } final List plots = new LinkedList<>(); for (int x = pos1.getX(); x <= pos2.getX(); x++) { for (int y = pos1.getY(); y <= pos2.getY(); y++) { final PlotId id = PlotId.of(x, y); final Plot plot = getPlotAbs(id); if (plot == null) { return null; } if (!plot.canClaim(player)) { return null; } else { plots.add(plot); } } } return plots; } public boolean removePlot(final @NonNull PlotId id) { return this.plots.remove(id) != null; } /** * Merge a list of plots together. This is non-blocking for the world-changes that will be made. To run a task when the * world changes are complete, use {@link PlotArea#mergePlots(List, boolean, Runnable)}; * * @param plotIds List of plot IDs to merge * @param removeRoads If the roads between plots should be removed * @return if merges were completed successfully. */ public boolean mergePlots(final @NonNull List plotIds, final boolean removeRoads) { return mergePlots(plotIds, removeRoads, null); } /** * Merge a list of plots together. This is non-blocking for the world-changes that will be made. * * @param plotIds List of plot IDs to merge * @param removeRoads If the roads between plots should be removed * @param whenDone Task to run when any merge world changes are complete. Also runs if no changes were made. Does not * run if there was an error or if too few plots IDs were supplied. * @return if merges were completed successfully. * @since 6.9.0 */ public boolean mergePlots( final @NonNull List plotIds, final boolean removeRoads, final @Nullable Runnable whenDone ) { if (plotIds.size() < 2) { return false; } final PlotId pos1 = plotIds.get(0); final PlotId pos2 = plotIds.get(plotIds.size() - 1); final PlotManager manager = getPlotManager(); QueueCoordinator queue = getQueue(); manager.startPlotMerge(plotIds, queue); final Set trusted = new HashSet<>(); final Set members = new HashSet<>(); final Set denied = new HashSet<>(); for (int x = pos1.getX(); x <= pos2.getX(); x++) { for (int y = pos1.getY(); y <= pos2.getY(); y++) { PlotId id = PlotId.of(x, y); Plot plot = getPlotAbs(id); trusted.addAll(plot.getTrusted()); members.addAll(plot.getMembers()); denied.addAll(plot.getDenied()); if (removeRoads) { plot.getPlotModificationManager().removeSign(); } } } members.removeAll(trusted); denied.removeAll(trusted); denied.removeAll(members); for (int x = pos1.getX(); x <= pos2.getX(); x++) { for (int y = pos1.getY(); y <= pos2.getY(); y++) { final boolean lx = x < pos2.getX(); final boolean ly = y < pos2.getY(); final PlotId id = PlotId.of(x, y); final Plot plot = getPlotAbs(id); plot.setTrusted(trusted); plot.setMembers(members); plot.setDenied(denied); Plot plot2; if (lx) { if (ly) { if (!plot.isMerged(Direction.EAST) || !plot.isMerged(Direction.SOUTH)) { if (removeRoads) { plot.getPlotModificationManager().removeRoadSouthEast(queue); } } } if (!plot.isMerged(Direction.EAST)) { plot2 = plot.getRelative(1, 0); plot.mergePlot(plot2, removeRoads, queue); } } if (ly) { if (!plot.isMerged(Direction.SOUTH)) { plot2 = plot.getRelative(0, 1); plot.mergePlot(plot2, removeRoads, queue); } } } } manager.finishPlotMerge(plotIds, queue); if (whenDone != null) { queue.setCompleteTask(whenDone); } queue.enqueue(); return true; } /** * Get a set of owned plots within a selection (chooses the best algorithm based on selection size. * i.e. A selection of billions of plots will work fine * * @param pos1 first corner of selection * @param pos2 second corner of selection * @return the plots in the selection which are owned */ public Set getPlotSelectionOwned(final @NonNull PlotId pos1, final @NonNull PlotId pos2) { final int size = (1 + pos2.getX() - pos1.getX()) * (1 + pos2.getY() - pos1.getY()); final Set result = new HashSet<>(); if (size < 16 || size < getPlotCount()) { for (final PlotId pid : Lists.newArrayList((Iterable) PlotId.PlotRangeIterator.range(pos1, pos2))) { final Plot plot = getPlotAbs(pid); if (plot.hasOwner()) { if (plot.getId().getX() > pos1.getX() || plot.getId().getY() > pos1.getY() || plot.getId().getX() < pos2.getX() || plot.getId().getY() < pos2.getY()) { result.add(plot); } } } } else { for (final Plot plot : getPlots()) { if (plot.getId().getX() > pos1.getX() || plot.getId().getY() > pos1.getY() || plot.getId().getX() < pos2.getX() || plot.getId().getY() < pos2.getY()) { result.add(plot); } } } return result; } @SuppressWarnings("WeakerAccess") public void removeCluster(final @Nullable PlotCluster plotCluster) { if (this.clusters == null) { throw new IllegalAccessError("Clusters not enabled!"); } this.clusters.remove(plotCluster); } public void addCluster(final @Nullable PlotCluster plotCluster) { if (this.clusters == null) { this.clusters = new QuadMap<>(Integer.MAX_VALUE, 0, 0, 62) { @Override public CuboidRegion getRegion(PlotCluster value) { BlockVector2 pos1 = BlockVector2.at(value.getP1().getX(), value.getP1().getY()); BlockVector2 pos2 = BlockVector2.at(value.getP2().getX(), value.getP2().getY()); return new CuboidRegion( pos1.toBlockVector3(getMinGenHeight()), pos2.toBlockVector3(getMaxGenHeight()) ); } }; } this.clusters.add(plotCluster); } public @Nullable PlotCluster getCluster(final String string) { for (PlotCluster cluster : getClusters()) { if (cluster.getName().equalsIgnoreCase(string)) { return cluster; } } return null; } /** * Get whether a schematic with that name is available or not. * If a schematic is available, it can be used for plot claiming. * * @param schematic the schematic to look for. * @return {@code true} if the schematic exists, {@code false} otherwise. */ public boolean hasSchematic(@NonNull String schematic) { return getSchematics().contains(schematic.toLowerCase()); } /** * Get whether economy is enabled and used on this plot area or not. * * @return {@code true} if this plot area uses economy, {@code false} otherwise. */ public boolean useEconomy() { return useEconomy; } /** * Get whether the plot area is limited by a world border or not. * * @return {@code true} if the plot area has a world border, {@code false} otherwise. */ public boolean hasWorldBorder() { return worldBorder; } /** * Get whether plot signs are allowed or not. * * @return {@code true} if plot signs are allowed, {@code false} otherwise. */ public boolean allowSigns() { return allowSigns; } /** * Get the plot sign material. * * @return the sign material. */ public String signMaterial() { return signMaterial; } public String legacySignMaterial() { return legacySignMaterial; } /** * Get the value associated with the specified flag. This 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 T getFlag(final Class> flagClass) { return this.flagContainer.getFlag(flagClass).getValue(); } /** * Get the value associated with the specified flag. This 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 flag value type * @return The flag value */ public > T getFlag(final V flag) { final Class flagClass = flag.getClass(); final PlotFlag flagInstance = this.flagContainer.getFlagErased(flagClass); return FlagContainer.castUnsafe(flagInstance).getValue(); } /** * Get the value associated with the specified road flag. This 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 T getRoadFlag(final Class> flagClass) { return this.roadFlagContainer.getFlag(flagClass).getValue(); } /** * Get the value associated with the specified road flag. This 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 flag value type * @return The flag value */ public > T getRoadFlag(final V flag) { final Class flagClass = flag.getClass(); final PlotFlag flagInstance = this.roadFlagContainer.getFlagErased(flagClass); return FlagContainer.castUnsafe(flagInstance).getValue(); } public @NonNull String getWorldName() { return this.worldName; } public String getId() { return this.id; } public @NonNull PlotManager getPlotManager() { return this.plotManager; } public int getWorldHash() { return this.worldHash; } public @NonNull IndependentPlotGenerator getGenerator() { return this.generator; } public boolean isAutoMerge() { return this.autoMerge; } public boolean isMiscSpawnUnowned() { return this.miscSpawnUnowned; } public boolean isMobSpawning() { return this.mobSpawning; } public boolean isMobSpawnerSpawning() { return this.mobSpawnerSpawning; } public BiomeType getPlotBiome() { return this.plotBiome; } public boolean isPlotChat() { return this.plotChat; } public boolean isForcingPlotChat() { return this.forcingPlotChat; } public boolean isSchematicClaimSpecify() { return this.schematicClaimSpecify; } public boolean isSchematicOnClaim() { return this.schematicOnClaim; } public String getSchematicFile() { return this.schematicFile; } public boolean isSpawnEggs() { return this.spawnEggs; } public String getSignMaterial() { return this.signMaterial; } public boolean isSpawnCustom() { return this.spawnCustom; } public boolean isSpawnBreeding() { return this.spawnBreeding; } public PlotAreaType getType() { return this.type; } /** * Set the type of this plot area. * * @param type the type of the plot area. */ public void setType(PlotAreaType type) { // TODO this should probably work only if type == null this.type = type; } public PlotAreaTerrainType getTerrain() { return this.terrain; } /** * Set the terrain generation type of this plot area. * * @param terrain the terrain type of the plot area. */ public void setTerrain(PlotAreaTerrainType terrain) { this.terrain = terrain; } public boolean isHomeAllowNonmember() { return this.homeAllowNonmember; } /** * Get the location for non-members to be teleported to. * * @since 6.1.4 */ public BlockLoc nonmemberHome() { return this.nonmemberHome; } /** * Get the default location for players to be teleported to. May be overridden by {@link #nonmemberHome} if the player is * not a member of the plot. * * @since 6.1.4 */ public BlockLoc defaultHome() { return this.defaultHome; } protected void setDefaultHome(BlockLoc defaultHome) { this.defaultHome = defaultHome; } /** * Get the maximum height players may build in. Exclusive. */ public int getMaxBuildHeight() { return this.maxBuildHeight; } /** * Get the minimum height players may build in. Inclusive. */ public int getMinBuildHeight() { return this.minBuildHeight; } /** * Get the min height from which PlotSquared will generate blocks. Inclusive. * * @since 6.6.0 */ public int getMinGenHeight() { return this.minGenHeight; } /** * Get the max height to which PlotSquared will generate blocks. Inclusive. * * @since 6.6.0 */ public int getMaxGenHeight() { return this.maxGenHeight; } public GameMode getGameMode() { return this.gameMode; } public Map getPrices() { return this.prices; } protected List getSchematics() { return this.schematics; } public boolean isRoadFlags() { return this.roadFlags; } public FlagContainer getFlagContainer() { return this.flagContainer; } public FlagContainer getRoadFlagContainer() { return this.roadFlagContainer; } public void setAllowSigns(boolean allowSigns) { this.allowSigns = allowSigns; } }