001/*
002 * PlotSquared, a land and world management plugin for Minecraft.
003 * Copyright (C) IntellectualSites <https://intellectualsites.com>
004 * Copyright (C) IntellectualSites team and contributors
005 *
006 * This program is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 *
011 * This program is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
014 * GNU General Public License for more details.
015 *
016 * You should have received a copy of the GNU General Public License
017 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
018 */
019package com.plotsquared.core.plot;
020
021import com.google.common.collect.ImmutableMap;
022import com.google.common.collect.ImmutableSet;
023import com.google.common.collect.Lists;
024import com.plotsquared.core.PlotSquared;
025import com.plotsquared.core.collection.QuadMap;
026import com.plotsquared.core.configuration.ConfigurationNode;
027import com.plotsquared.core.configuration.ConfigurationSection;
028import com.plotsquared.core.configuration.ConfigurationUtil;
029import com.plotsquared.core.configuration.Settings;
030import com.plotsquared.core.configuration.caption.TranslatableCaption;
031import com.plotsquared.core.configuration.file.YamlConfiguration;
032import com.plotsquared.core.generator.GridPlotWorld;
033import com.plotsquared.core.generator.IndependentPlotGenerator;
034import com.plotsquared.core.inject.annotations.WorldConfig;
035import com.plotsquared.core.location.BlockLoc;
036import com.plotsquared.core.location.Direction;
037import com.plotsquared.core.location.Location;
038import com.plotsquared.core.permissions.Permission;
039import com.plotsquared.core.player.ConsolePlayer;
040import com.plotsquared.core.player.MetaDataAccess;
041import com.plotsquared.core.player.PlayerMetaDataKeys;
042import com.plotsquared.core.player.PlotPlayer;
043import com.plotsquared.core.plot.flag.FlagContainer;
044import com.plotsquared.core.plot.flag.FlagParseException;
045import com.plotsquared.core.plot.flag.GlobalFlagContainer;
046import com.plotsquared.core.plot.flag.PlotFlag;
047import com.plotsquared.core.plot.flag.implementations.DoneFlag;
048import com.plotsquared.core.queue.GlobalBlockQueue;
049import com.plotsquared.core.queue.QueueCoordinator;
050import com.plotsquared.core.util.MathMan;
051import com.plotsquared.core.util.PlotExpression;
052import com.plotsquared.core.util.RegionUtil;
053import com.plotsquared.core.util.StringMan;
054import com.sk89q.worldedit.math.BlockVector2;
055import com.sk89q.worldedit.math.BlockVector3;
056import com.sk89q.worldedit.regions.CuboidRegion;
057import com.sk89q.worldedit.world.biome.BiomeType;
058import com.sk89q.worldedit.world.biome.BiomeTypes;
059import com.sk89q.worldedit.world.gamemode.GameMode;
060import com.sk89q.worldedit.world.gamemode.GameModes;
061import net.kyori.adventure.text.Component;
062import net.kyori.adventure.text.ComponentLike;
063import net.kyori.adventure.text.minimessage.MiniMessage;
064import net.kyori.adventure.text.minimessage.tag.Tag;
065import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
066import org.apache.logging.log4j.LogManager;
067import org.apache.logging.log4j.Logger;
068import org.checkerframework.checker.nullness.qual.NonNull;
069import org.checkerframework.checker.nullness.qual.Nullable;
070import org.jetbrains.annotations.NotNull;
071
072import java.text.DecimalFormat;
073import java.util.ArrayList;
074import java.util.Collection;
075import java.util.Collections;
076import java.util.HashMap;
077import java.util.HashSet;
078import java.util.LinkedList;
079import java.util.List;
080import java.util.Map;
081import java.util.Map.Entry;
082import java.util.Set;
083import java.util.UUID;
084import java.util.concurrent.ConcurrentHashMap;
085import java.util.function.Consumer;
086
087/**
088 * @author Jesse Boyd, Alexander Söderberg
089 */
090public abstract class PlotArea implements ComponentLike {
091
092    private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + PlotArea.class.getSimpleName());
093    private static final MiniMessage MINI_MESSAGE = MiniMessage.builder().build();
094    private static final DecimalFormat FLAG_DECIMAL_FORMAT = new DecimalFormat("0");
095
096    static {
097        FLAG_DECIMAL_FORMAT.setMaximumFractionDigits(340);
098    }
099
100    protected final ConcurrentHashMap<PlotId, Plot> plots = new ConcurrentHashMap<>();
101    @NonNull
102    private final String worldName;
103    private final String id;
104    @NonNull
105    private final PlotManager plotManager;
106    private final int worldHash;
107    private final PlotId min;
108    private final PlotId max;
109    @NonNull
110    private final IndependentPlotGenerator generator;
111    /**
112     * Area flag container
113     */
114    private final FlagContainer flagContainer =
115            new FlagContainer(GlobalFlagContainer.getInstance());
116    private final FlagContainer roadFlagContainer =
117            new FlagContainer(GlobalFlagContainer.getInstance());
118    private final YamlConfiguration worldConfiguration;
119    private final GlobalBlockQueue globalBlockQueue;
120    private boolean roadFlags = false;
121    private boolean autoMerge = false;
122    private boolean allowSigns = true;
123    private boolean miscSpawnUnowned = false;
124    private boolean mobSpawning = false;
125    private boolean mobSpawnerSpawning = false;
126    private BiomeType plotBiome = BiomeTypes.FOREST;
127    private boolean plotChat = true;
128    private boolean forcingPlotChat = false;
129    private boolean schematicClaimSpecify = false;
130    private boolean schematicOnClaim = false;
131    private String schematicFile = "null";
132    private boolean spawnEggs = false;
133    private boolean spawnCustom = true;
134    private boolean spawnBreeding = false;
135    private PlotAreaType type = PlotAreaType.NORMAL;
136    private PlotAreaTerrainType terrain = PlotAreaTerrainType.NONE;
137    private boolean homeAllowNonmember = false;
138    private BlockLoc nonmemberHome;
139    private BlockLoc defaultHome;
140    private int maxBuildHeight = PlotSquared.platform().versionMaxHeight() + 1; // Exclusive
141    private int minBuildHeight = PlotSquared.platform().versionMinHeight() + 1; // Inclusive
142    private int maxGenHeight = PlotSquared.platform().versionMaxHeight(); // Inclusive
143    private int minGenHeight = PlotSquared.platform().versionMinHeight(); // Inclusive
144    private GameMode gameMode = GameModes.CREATIVE;
145    private Map<String, PlotExpression> prices = new HashMap<>();
146    private List<String> schematics = new ArrayList<>();
147    private boolean worldBorder = false;
148    private boolean useEconomy = false;
149    private int hash;
150    private CuboidRegion region;
151    private ConcurrentHashMap<String, Object> meta;
152    private QuadMap<PlotCluster> clusters;
153    private String signMaterial = "OAK_WALL_SIGN";
154    private String legacySignMaterial = "WALL_SIGN";
155
156    public PlotArea(
157            final @NonNull String worldName, final @Nullable String id,
158            @NonNull IndependentPlotGenerator generator, final @Nullable PlotId min,
159            final @Nullable PlotId max,
160            @WorldConfig final @Nullable YamlConfiguration worldConfiguration,
161            final @NonNull GlobalBlockQueue blockQueue
162    ) {
163        this.worldName = worldName;
164        this.id = id;
165        this.plotManager = createManager();
166        this.generator = generator;
167        this.globalBlockQueue = blockQueue;
168        if (min == null || max == null) {
169            if (min != max) {
170                throw new IllegalArgumentException(
171                        "None of the ids can be null for this constructor");
172            }
173            this.min = null;
174            this.max = null;
175        } else {
176            this.min = min;
177            this.max = max;
178        }
179        this.worldHash = worldName.hashCode();
180        this.worldConfiguration = worldConfiguration;
181    }
182
183    private static void parseFlags(FlagContainer flagContainer, List<String> flagStrings) {
184        for (final String key : flagStrings) {
185            final String[] split;
186            if (key.contains(";")) {
187                split = key.split(";");
188            } else {
189                split = key.split(":");
190            }
191            final PlotFlag<?, ?> flagInstance =
192                    GlobalFlagContainer.getInstance().getFlagFromString(split[0]);
193            if (flagInstance != null) {
194                try {
195                    flagContainer.addFlag(flagInstance.parse(split[1]));
196                } catch (final FlagParseException e) {
197                    LOGGER.warn(
198                            "Failed to parse default flag with key '{}' and value '{}'. "
199                                    + "Reason: {}. This flag will not be added as a default flag.",
200                            e.getFlag().getName(),
201                            e.getValue(),
202                            e.getErrorMessage()
203                    );
204                    e.printStackTrace();
205                }
206            } else {
207                flagContainer.addUnknownFlag(split[0], split[1]);
208            }
209        }
210    }
211
212    @NonNull
213    protected abstract PlotManager createManager();
214
215    public QueueCoordinator getQueue() {
216        return this.globalBlockQueue.getNewQueue(PlotSquared.platform().worldUtil().getWeWorld(worldName));
217    }
218
219    /**
220     * Returns the region for this PlotArea, or a CuboidRegion encompassing
221     * the whole world if none exists.
222     *
223     * @return CuboidRegion
224     */
225    public CuboidRegion getRegion() {
226        this.region = getRegionAbs();
227        if (this.region == null) {
228            return new CuboidRegion(
229                    BlockVector3.at(Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE),
230                    BlockVector3.at(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE)
231            );
232        }
233        return this.region;
234    }
235
236    /**
237     * Returns the region for this PlotArea.
238     *
239     * @return CuboidRegion or null if no applicable region
240     */
241    private CuboidRegion getRegionAbs() {
242        if (this.region == null) {
243            if (this.min != null) {
244                Location bot = getPlotManager().getPlotBottomLocAbs(this.min);
245                Location top = getPlotManager().getPlotTopLocAbs(this.max);
246                BlockVector3 pos1 = bot.getBlockVector3().subtract(BlockVector3.ONE);
247                BlockVector3 pos2 = top.getBlockVector3().add(BlockVector3.ONE);
248                this.region = new CuboidRegion(pos1, pos2);
249            }
250        }
251        return this.region;
252    }
253
254    /**
255     * Returns the minimum value of a {@link PlotId}.
256     *
257     * @return the minimum value for a {@link PlotId}
258     */
259    public @NonNull PlotId getMin() {
260        return this.min == null ? PlotId.of(Integer.MIN_VALUE, Integer.MIN_VALUE) : this.min;
261    }
262
263    /**
264     * Returns the max PlotId.
265     *
266     * @return the maximum value for a {@link PlotId}
267     */
268    public @NonNull PlotId getMax() {
269        return this.max == null ? PlotId.of(Integer.MAX_VALUE, Integer.MAX_VALUE) : this.max;
270    }
271
272    @Override
273    public boolean equals(Object obj) {
274        if (this == obj) {
275            return true;
276        }
277        if (obj == null || getClass() != obj.getClass()) {
278            return false;
279        }
280        PlotArea plotarea = (PlotArea) obj;
281        return this.getWorldHash() == plotarea.getWorldHash() && this.getWorldName()
282                .equals(plotarea.getWorldName()) && StringMan.isEqual(this.getId(), plotarea.getId());
283    }
284
285    public Set<PlotCluster> getClusters() {
286        return this.clusters == null ? new HashSet<>() : this.clusters.getAll();
287    }
288
289    /**
290     * Check if a PlotArea is compatible (move/copy etc.).
291     *
292     * @param plotArea the {@link PlotArea} to compare
293     * @return {@code true} if both areas are compatible
294     */
295    public boolean isCompatible(final @NonNull PlotArea plotArea) {
296        final ConfigurationSection section = this.worldConfiguration.getConfigurationSection("worlds");
297        for (ConfigurationNode setting : plotArea.getSettingNodes()) {
298            Object constant = section.get(plotArea.worldName + '.' + setting.getConstant());
299            if (constant == null || !constant
300                    .equals(section.get(this.worldName + '.' + setting.getConstant()))) {
301                return false;
302            }
303        }
304        return true;
305    }
306
307    /**
308     * When a world is created, the following method will be called for each.
309     *
310     * @param config Configuration Section
311     */
312    public void loadDefaultConfiguration(ConfigurationSection config) {
313        if ((this.min != null || this.max != null) && !(this instanceof GridPlotWorld)) {
314            throw new IllegalArgumentException("Must extend GridPlotWorld to provide");
315        }
316        if (config.contains("generator.terrain")) {
317            this.terrain = ConfigurationUtil.getTerrain(config);
318            this.type = ConfigurationUtil.getType(config);
319        }
320        this.mobSpawning = config.getBoolean("natural_mob_spawning");
321        this.miscSpawnUnowned = config.getBoolean("misc_spawn_unowned");
322        this.mobSpawnerSpawning = config.getBoolean("mob_spawner_spawning");
323        this.autoMerge = config.getBoolean("plot.auto_merge");
324        this.allowSigns = config.getBoolean("plot.create_signs");
325        if (PlotSquared.platform().serverVersion()[1] == 13) {
326            this.legacySignMaterial = config.getString("plot.legacy_sign_material");
327        } else {
328            this.signMaterial = config.getString("plot.sign_material");
329        }
330        String biomeString = config.getString("plot.biome");
331        if (!biomeString.startsWith("minecraft:")) {
332            biomeString = "minecraft:" + biomeString;
333            config.set("plot.biome", biomeString.toLowerCase());
334        }
335        this.plotBiome = ConfigurationUtil.BIOME.parseString(biomeString.toLowerCase());
336        this.schematicOnClaim = config.getBoolean("schematic.on_claim");
337        this.schematicFile = config.getString("schematic.file");
338        this.schematicClaimSpecify = config.getBoolean("schematic.specify_on_claim");
339        this.schematics = new ArrayList<>(config.getStringList("schematic.schematics"));
340        this.schematics.replaceAll(String::toLowerCase);
341        this.useEconomy = config.getBoolean("economy.use");
342        ConfigurationSection priceSection = config.getConfigurationSection("economy.prices");
343        if (this.useEconomy) {
344            this.prices = new HashMap<>();
345            for (String key : priceSection.getKeys(false)) {
346                String raw = priceSection.getString(key);
347                if (raw.contains("{arg}")) {
348                    raw = raw.replace("{arg}", "plots");
349                    priceSection.set(key, raw); // update if replaced
350                }
351                this.prices.put(key, PlotExpression.compile(raw, "plots"));
352            }
353        }
354        this.plotChat = config.getBoolean("chat.enabled");
355        this.forcingPlotChat = config.getBoolean("chat.forced");
356        this.worldBorder = config.getBoolean("world.border");
357        this.maxBuildHeight = config.getInt("world.max_height");
358        this.minBuildHeight = config.getInt("world.min_height");
359        this.minGenHeight = config.getInt("world.min_gen_height");
360        this.maxGenHeight = config.getInt("world.max_gen_height");
361
362        switch (config.getString("world.gamemode").toLowerCase()) {
363            case "creative", "c", "1" -> this.gameMode = GameModes.CREATIVE;
364            case "adventure", "a", "2" -> this.gameMode = GameModes.ADVENTURE;
365            case "spectator", "3" -> this.gameMode = GameModes.SPECTATOR;
366            default -> this.gameMode = GameModes.SURVIVAL;
367        }
368
369        String homeNonMembers = config.getString("home.nonmembers");
370        String homeDefault = config.getString("home.default");
371        this.defaultHome = BlockLoc.fromString(homeDefault);
372        this.homeAllowNonmember = homeNonMembers.equalsIgnoreCase(homeDefault);
373        if (this.homeAllowNonmember) {
374            this.nonmemberHome = defaultHome;
375        } else {
376            this.nonmemberHome = BlockLoc.fromString(homeNonMembers);
377        }
378
379        if ("side".equalsIgnoreCase(homeDefault)) {
380            this.defaultHome = null;
381        } else if (StringMan.isEqualIgnoreCaseToAny(homeDefault, "center", "middle", "centre")) {
382            this.defaultHome = new BlockLoc(Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
383        } else {
384            try {
385                /*String[] split = homeDefault.split(",");
386                this.DEFAULT_HOME =
387                    new PlotLoc(Integer.parseInt(split[0]), Integer.parseInt(split[1]));*/
388                this.defaultHome = BlockLoc.fromString(homeDefault);
389            } catch (NumberFormatException ignored) {
390                this.defaultHome = null;
391            }
392        }
393
394        List<String> flags = config.getStringList("flags.default");
395        if (flags.isEmpty()) {
396            flags = config.getStringList("flags");
397            if (flags.isEmpty()) {
398                flags = new ArrayList<>();
399                ConfigurationSection section = config.getConfigurationSection("flags");
400                Set<String> keys = section.getKeys(false);
401                for (String key : keys) {
402                    if (!"default".equals(key)) {
403                        flags.add(key + ';' + section.get(key));
404                    }
405                }
406            }
407        }
408        parseFlags(this.getFlagContainer(), flags);
409        ConsolePlayer.getConsole().sendMessage(
410                TranslatableCaption.of("flags.area_flags"),
411                TagResolver.resolver("flags", Tag.inserting(Component.text(flags.toString())))
412        );
413
414        this.spawnEggs = config.getBoolean("event.spawn.egg");
415        this.spawnCustom = config.getBoolean("event.spawn.custom");
416        this.spawnBreeding = config.getBoolean("event.spawn.breeding");
417
418        List<String> roadflags = config.getStringList("road.flags");
419        if (roadflags.isEmpty()) {
420            roadflags = new ArrayList<>();
421            ConfigurationSection section = config.getConfigurationSection("road.flags");
422            Set<String> keys = section.getKeys(false);
423            for (String key : keys) {
424                if (!"default".equals(key)) {
425                    roadflags.add(key + ';' + section.get(key));
426                }
427            }
428        }
429        this.roadFlags = roadflags.size() > 0;
430        parseFlags(this.getRoadFlagContainer(), roadflags);
431        ConsolePlayer.getConsole().sendMessage(
432                TranslatableCaption.of("flags.road_flags"),
433                TagResolver.resolver("flags", Tag.inserting(Component.text(roadflags.toString())))
434        );
435
436        loadConfiguration(config);
437    }
438
439    public abstract void loadConfiguration(ConfigurationSection config);
440
441    /**
442     * Saving core PlotArea settings.
443     *
444     * @param config Configuration Section
445     */
446    public void saveConfiguration(ConfigurationSection config) {
447        HashMap<String, Object> options = new HashMap<>();
448        options.put("natural_mob_spawning", this.isMobSpawning());
449        options.put("misc_spawn_unowned", this.isMiscSpawnUnowned());
450        options.put("mob_spawner_spawning", this.isMobSpawnerSpawning());
451        options.put("plot.auto_merge", this.isAutoMerge());
452        options.put("plot.create_signs", this.allowSigns());
453        if (PlotSquared.platform().serverVersion()[1] == 13) {
454            options.put("plot.legacy_sign_material", this.legacySignMaterial);
455        } else {
456            options.put("plot.sign_material", this.signMaterial());
457        }
458        options.put("plot.biome", "minecraft:forest");
459        options.put("schematic.on_claim", this.isSchematicOnClaim());
460        options.put("schematic.file", this.getSchematicFile());
461        options.put("schematic.specify_on_claim", this.isSchematicClaimSpecify());
462        options.put("schematic.schematics", this.getSchematics());
463        options.put("economy.use", this.useEconomy());
464        options.put("economy.prices.claim", 100);
465        options.put("economy.prices.merge", 100);
466        options.put("economy.prices.sell", 100);
467        options.put("chat.enabled", this.isPlotChat());
468        options.put("chat.forced", this.isForcingPlotChat());
469        options.put("flags.default", null);
470        options.put("event.spawn.egg", this.isSpawnEggs());
471        options.put("event.spawn.custom", this.isSpawnCustom());
472        options.put("event.spawn.breeding", this.isSpawnBreeding());
473        options.put("world.border", this.hasWorldBorder());
474        options.put("home.default", "side");
475        String position = config.getString(
476                "home.nonmembers",
477                config.getBoolean("home.allow-nonmembers", false) ?
478                        config.getString("home.default", "side") :
479                        "side"
480        );
481        options.put("home.nonmembers", position);
482        options.put("world.max_height", this.getMaxBuildHeight());
483        options.put("world.min_height", this.getMinBuildHeight());
484        options.put("world.min_gen_height", this.getMinGenHeight());
485        options.put("world.max_gen_height", this.getMaxGenHeight());
486        options.put("world.gamemode", this.getGameMode().getName().toLowerCase());
487        options.put("road.flags.default", null);
488
489        if (this.getType() != PlotAreaType.NORMAL) {
490            options.put("generator.terrain", this.getTerrain());
491            options.put("generator.type", this.getType().toString());
492        }
493        ConfigurationNode[] settings = getSettingNodes();
494        /*
495         * Saving generator specific settings
496         */
497        for (ConfigurationNode setting : settings) {
498            options.put(setting.getConstant(), setting.getValue());
499        }
500        for (Entry<String, Object> stringObjectEntry : options.entrySet()) {
501            if (!config.contains(stringObjectEntry.getKey())) {
502                config.set(stringObjectEntry.getKey(), stringObjectEntry.getValue());
503            }
504        }
505        if (!config.contains("flags")) {
506            config.set(
507                    "flags.use",
508                    "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"
509            );
510        }
511        if (!config.contains("road.flags")) {
512            config.set("road.flags.liquid-flow", false);
513        }
514    }
515
516    @NonNull
517    @Override
518    public String toString() {
519        if (this.getId() == null) {
520            return this.getWorldName();
521        } else {
522            return this.getWorldName() + ";" + this.getId();
523        }
524    }
525
526    @Override
527    public @NotNull Component asComponent() {
528        return Component.text(toString());
529    }
530
531    @Override
532    public int hashCode() {
533        if (this.hash != 0) {
534            return this.hash;
535        }
536        return this.hash = toString().hashCode();
537    }
538
539    /**
540     * Used for the <b>/plot setup</b> command Return null if you do not want to support this feature
541     *
542     * @return ConfigurationNode[]
543     */
544    public abstract ConfigurationNode[] getSettingNodes();
545
546    /**
547     * Gets the {@link Plot} at a location.
548     *
549     * @param location the location
550     * @return the {@link Plot} or null if none exists
551     */
552    public @Nullable Plot getPlotAbs(final @NonNull Location location) {
553        final PlotId pid =
554                this.getPlotManager().getPlotId(location.getX(), location.getY(), location.getZ());
555        if (pid == null) {
556            return null;
557        }
558        return getPlotAbs(pid);
559    }
560
561    /**
562     * Gets the base plot at a location.
563     *
564     * @param location the location
565     * @return base Plot
566     */
567    public @Nullable Plot getPlot(final @NonNull Location location) {
568        final PlotId pid =
569                this.getPlotManager().getPlotId(location.getX(), location.getY(), location.getZ());
570        if (pid == null) {
571            return null;
572        }
573        return getPlot(pid);
574    }
575
576    /**
577     * Get the owned base plot at a location.
578     *
579     * @param location the location
580     * @return the base plot or null
581     */
582    public @Nullable Plot getOwnedPlot(final @NonNull Location location) {
583        final PlotId pid =
584                this.getPlotManager().getPlotId(location.getX(), location.getY(), location.getZ());
585        if (pid == null) {
586            return null;
587        }
588        Plot plot = this.plots.get(pid);
589        return plot == null ? null : plot.getBasePlot(false);
590    }
591
592    /**
593     * Get the owned plot at a location.
594     *
595     * @param location the location
596     * @return Plot or null
597     */
598    public @Nullable Plot getOwnedPlotAbs(final @NonNull Location location) {
599        final PlotId pid =
600                this.getPlotManager().getPlotId(location.getX(), location.getY(), location.getZ());
601        if (pid == null) {
602            return null;
603        }
604        return this.plots.get(pid);
605    }
606
607    /**
608     * Get the owned Plot at a PlotId.
609     *
610     * @param id the {@link PlotId}
611     * @return the plot or null
612     */
613    public @Nullable Plot getOwnedPlotAbs(final @NonNull PlotId id) {
614        return this.plots.get(id);
615    }
616
617    public @Nullable Plot getOwnedPlot(final @NonNull PlotId id) {
618        Plot plot = this.plots.get(id);
619        return plot == null ? null : plot.getBasePlot(false);
620    }
621
622    public boolean contains(final int x, final int z) {
623        return this.getType() != PlotAreaType.PARTIAL || RegionUtil.contains(getRegionAbs(), x, z);
624    }
625
626    public boolean contains(final @NonNull PlotId id) {
627        return this.min == null || (id.getX() >= this.min.getX() && id.getX() <= this.max.getX() &&
628                id.getY() >= this.min.getY() && id.getY() <= this.max.getY());
629    }
630
631    public boolean contains(final @NonNull Location location) {
632        return StringMan.isEqual(location.getWorldName(), this.getWorldName()) && (
633                getRegionAbs() == null || this.region.contains(location.getBlockVector3()));
634    }
635
636    /**
637     * Get if the {@code PlotArea}'s build range (min build height -> max build height) contains the given y value
638     *
639     * @param y y height
640     * @return if build height contains y
641     */
642    public boolean buildRangeContainsY(int y) {
643        return y >= minBuildHeight && y < maxBuildHeight;
644    }
645
646    /**
647     * Utility method to check if the player is attempting to place blocks outside the build area, and notify of this if the
648     * player does not have permissions.
649     *
650     * @param player Player to check
651     * @param y      y height to check
652     * @return true if outside build area with no permissions
653     * @since 6.9.1
654     */
655    public boolean notifyIfOutsideBuildArea(PlotPlayer<?> player, int y) {
656        if (!buildRangeContainsY(y) && !player.hasPermission(Permission.PERMISSION_ADMIN_BUILD_HEIGHT_LIMIT)) {
657            player.sendMessage(
658                    TranslatableCaption.of("height.height_limit"),
659                    TagResolver.builder()
660                            .tag("minheight", Tag.inserting(Component.text(minBuildHeight)))
661                            .tag(
662                                    "maxheight",
663                                    Tag.inserting(Component.text(maxBuildHeight))
664                            ).build()
665            );
666            // Return true if "failed" as the method will always be inverted otherwise
667            return true;
668        }
669        return false;
670    }
671
672    public @NonNull Set<Plot> getPlotsAbs(final UUID uuid) {
673        if (uuid == null) {
674            return Collections.emptySet();
675        }
676        final HashSet<Plot> myPlots = new HashSet<>();
677        forEachPlotAbs(value -> {
678            if (uuid.equals(value.getOwnerAbs())) {
679                myPlots.add(value);
680            }
681        });
682        return myPlots;
683    }
684
685    public @NonNull Set<Plot> getPlots(final @NonNull UUID uuid) {
686        return getPlots().stream().filter(plot -> plot.isBasePlot() && plot.isOwner(uuid))
687                .collect(ImmutableSet.toImmutableSet());
688    }
689
690    /**
691     * A collection of the claimed plots in this {@link PlotArea}.
692     *
693     * @return a collection of claimed plots
694     */
695    public Collection<Plot> getPlots() {
696        return this.plots.values();
697    }
698
699    public int getPlotCount(final @NonNull UUID uuid) {
700        if (!Settings.Done.COUNTS_TOWARDS_LIMIT) {
701            return (int) getPlotsAbs(uuid).stream().filter(plot -> !DoneFlag.isDone(plot)).count();
702        }
703        return getPlotsAbs(uuid).size();
704    }
705
706    /**
707     * Retrieves the plots for the player in this PlotArea.
708     *
709     * @param player player to get plots of
710     * @return set of player's plots
711     * @deprecated Use {@link #getPlots(UUID)}
712     */
713    @Deprecated
714    public Set<Plot> getPlots(final @NonNull PlotPlayer<?> player) {
715        return getPlots(player.getUUID());
716    }
717
718    //todo check if this method is needed in this class
719
720    public boolean hasPlot(final @NonNull UUID uuid) {
721        return this.plots.entrySet().stream().anyMatch(entry -> entry.getValue().isOwner(uuid));
722    }
723
724    public int getPlotCount(final @Nullable PlotPlayer<?> player) {
725        return player != null ? getPlotCount(player.getUUID()) : 0;
726    }
727
728    public @Nullable Plot getPlotAbs(final @NonNull PlotId id) {
729        Plot plot = getOwnedPlotAbs(id);
730        if (plot == null) {
731            if (this.min != null && (id.getX() < this.min.getX() || id.getX() > this.max.getX() || id.getY() < this.min.getY()
732                    || id.getY() > this.max.getY())) {
733                return null;
734            }
735            return new Plot(this, id);
736        }
737        return plot;
738    }
739
740    public @Nullable Plot getPlot(final @NonNull PlotId id) {
741        final Plot plot = getOwnedPlotAbs(id);
742        if (plot == null) {
743            if (this.min != null && (id.getX() < this.min.getX() || id.getX() > this.max.getX() || id.getY() < this.min.getY()
744                    || id.getY() > this.max.getY())) {
745                return null;
746            }
747            return new Plot(this, id);
748        }
749        return plot.getBasePlot(false);
750    }
751
752    /**
753     * Retrieves the number of claimed plot in the {@link PlotArea}.
754     *
755     * @return the number of claimed plots
756     */
757    public int getPlotCount() {
758        return this.plots.size();
759    }
760
761    public @Nullable PlotCluster getCluster(final @NonNull Location location) {
762        final Plot plot = getPlot(location);
763        if (plot == null) {
764            return null;
765        }
766        return this.clusters != null ? this.clusters.get(plot.getId().getX(), plot.getId().getY()) : null;
767    }
768
769    public @Nullable PlotCluster getFirstIntersectingCluster(
770            final @NonNull PlotId pos1,
771            final @NonNull PlotId pos2
772    ) {
773        if (this.clusters == null) {
774            return null;
775        }
776        for (PlotCluster cluster : this.clusters.getAll()) {
777            if (cluster.intersects(pos1, pos2)) {
778                return cluster;
779            }
780        }
781        return null;
782    }
783
784    @Nullable PlotCluster getCluster(final @NonNull PlotId id) {
785        return this.clusters != null ? this.clusters.get(id.getX(), id.getY()) : null;
786    }
787
788    /**
789     * Session only plot metadata (session is until the server stops).
790     * <br>
791     * For persistent metadata use the flag system
792     *
793     * @param key   metadata key
794     * @param value metadata value
795     */
796    public void setMeta(final @NonNull String key, final @Nullable Object value) {
797        if (this.meta == null) {
798            this.meta = new ConcurrentHashMap<>();
799        }
800        this.meta.put(key, value);
801    }
802
803    public @NonNull <T> T getMeta(final @NonNull String key, final @NonNull T def) {
804        final Object v = getMeta(key);
805        return v == null ? def : (T) v;
806    }
807
808    /**
809     * Get the metadata for a key<br>
810     * <br>
811     * For persistent metadata use the flag system
812     *
813     * @param key metadata key to get value for
814     * @return metadata value
815     */
816    public @Nullable Object getMeta(final @NonNull String key) {
817        if (this.meta != null) {
818            return this.meta.get(key);
819        }
820        return null;
821    }
822
823    @SuppressWarnings("unused")
824    public @NonNull Set<Plot> getBasePlots() {
825        final HashSet<Plot> myPlots = new HashSet<>(getPlots());
826        myPlots.removeIf(plot -> !plot.isBasePlot());
827        return myPlots;
828    }
829
830    private void forEachPlotAbs(Consumer<Plot> run) {
831        for (final Entry<PlotId, Plot> entry : this.plots.entrySet()) {
832            run.accept(entry.getValue());
833        }
834    }
835
836    public void forEachBasePlot(Consumer<Plot> run) {
837        for (final Plot plot : getPlots()) {
838            if (plot.isBasePlot()) {
839                run.accept(plot);
840            }
841        }
842    }
843
844    /**
845     * Returns an ImmutableMap of PlotId's and Plots in this PlotArea.
846     *
847     * @return map of PlotId against Plot for all plots in this area
848     * @deprecated Poorly implemented. May be removed in future.
849     */
850    //todo eventually remove
851    @Deprecated
852    public @NonNull Map<PlotId, Plot> getPlotsRaw() {
853        return ImmutableMap.copyOf(plots);
854    }
855
856    public @NonNull Set<Entry<PlotId, Plot>> getPlotEntries() {
857        return this.plots.entrySet();
858    }
859
860    public boolean addPlot(final @NonNull Plot plot) {
861        for (final PlotPlayer<?> pp : plot.getPlayersInPlot()) {
862            try (final MetaDataAccess<Plot> metaDataAccess = pp.accessTemporaryMetaData(
863                    PlayerMetaDataKeys.TEMPORARY_LAST_PLOT)) {
864                metaDataAccess.set(plot);
865            }
866        }
867        return this.plots.put(plot.getId(), plot) == null;
868    }
869
870    public Plot getNextFreePlot(final PlotPlayer<?> player, @Nullable PlotId start) {
871        int plots;
872        PlotId center;
873        PlotId min = getMin();
874        PlotId max = getMax();
875        if (getType() == PlotAreaType.PARTIAL) {
876            center = PlotId.of(MathMan.average(min.getX(), max.getX()), MathMan.average(min.getY(), max.getY()));
877            plots = Math.max(max.getX() - min.getX() + 1, max.getY() - min.getY() + 1) + 1;
878            if (start != null) {
879                start = PlotId.of(start.getX() - center.getX(), start.getY() - center.getY());
880            }
881        } else {
882            center = PlotId.of(0, 0);
883            plots = Integer.MAX_VALUE;
884        }
885        for (int i = 0; i < plots; i++) {
886            if (start == null) {
887                start = getMeta("lastPlot", PlotId.of(0, 0));
888            } else {
889                start = start.getNextId();
890            }
891            PlotId currentId = PlotId.of(center.getX() + start.getX(), center.getY() + start.getY());
892            Plot plot = getPlotAbs(currentId);
893            if (plot != null && plot.canClaim(player)) {
894                setMeta("lastPlot", start);
895                return plot;
896            }
897        }
898        return null;
899    }
900
901    public boolean addPlotIfAbsent(final @NonNull Plot plot) {
902        if (this.plots.putIfAbsent(plot.getId(), plot) == null) {
903            for (PlotPlayer<?> pp : plot.getPlayersInPlot()) {
904                try (final MetaDataAccess<Plot> metaDataAccess = pp.accessTemporaryMetaData(
905                        PlayerMetaDataKeys.TEMPORARY_LAST_PLOT)) {
906                    metaDataAccess.set(plot);
907                }
908            }
909            return true;
910        }
911        return false;
912    }
913
914    public boolean addPlotAbs(final @NonNull Plot plot) {
915        return this.plots.put(plot.getId(), plot) == null;
916    }
917
918    /**
919     * Get the plot border distance for a world<br>
920     *
921     * @return The border distance or Integer.MAX_VALUE if no border is set
922     */
923    public int getBorder() {
924        final Integer meta = (Integer) getMeta("worldBorder");
925        if (meta != null) {
926            int border = meta + 1;
927            if (border == 0) {
928                return Integer.MAX_VALUE;
929            } else {
930                return border;
931            }
932        }
933        return Integer.MAX_VALUE;
934    }
935
936    /**
937     * Setup the plot border for a world (usually done when the world is created).
938     */
939    public void setupBorder() {
940        if (!this.hasWorldBorder()) {
941            return;
942        }
943        final Integer meta = (Integer) getMeta("worldBorder");
944        if (meta == null) {
945            setMeta("worldBorder", 1);
946        }
947        for (final Plot plot : getPlots()) {
948            plot.updateWorldBorder();
949        }
950    }
951
952    /**
953     * Delete the metadata for a key.
954     * - metadata is session only
955     * - deleting other plugin's metadata may cause issues
956     *
957     * @param key Meta data key
958     */
959    public void deleteMeta(final @NonNull String key) {
960        if (this.meta != null) {
961            this.meta.remove(key);
962        }
963    }
964
965    public @Nullable List<Plot> canClaim(
966            final @Nullable PlotPlayer<?> player, final @NonNull PlotId pos1,
967            final @NonNull PlotId pos2
968    ) {
969        if (pos1.getX() == pos2.getX() && pos1.getY() == pos2.getY()) {
970            if (getOwnedPlot(pos1) != null) {
971                return null;
972            }
973            final Plot plot = getPlotAbs(pos1);
974            if (plot == null) {
975                return null;
976            }
977            if (plot.canClaim(player)) {
978                return Collections.singletonList(plot);
979            } else {
980                return null;
981            }
982        }
983        final List<Plot> plots = new LinkedList<>();
984        for (int x = pos1.getX(); x <= pos2.getX(); x++) {
985            for (int y = pos1.getY(); y <= pos2.getY(); y++) {
986                final PlotId id = PlotId.of(x, y);
987                final Plot plot = getPlotAbs(id);
988                if (plot == null) {
989                    return null;
990                }
991                if (!plot.canClaim(player)) {
992                    return null;
993                } else {
994                    plots.add(plot);
995                }
996            }
997        }
998        return plots;
999    }
1000
1001    public boolean removePlot(final @NonNull PlotId id) {
1002        return this.plots.remove(id) != null;
1003    }
1004
1005    /**
1006     * Merge a list of plots together. This is non-blocking for the world-changes that will be made. To run a task when the
1007     * world changes are complete, use {@link PlotArea#mergePlots(List, boolean, Runnable)};
1008     *
1009     * @param plotIds     List of plot IDs to merge
1010     * @param removeRoads If the roads between plots should be removed
1011     * @return if merges were completed successfully.
1012     */
1013    public boolean mergePlots(final @NonNull List<PlotId> plotIds, final boolean removeRoads) {
1014        return mergePlots(plotIds, removeRoads, null);
1015    }
1016
1017    /**
1018     * Merge a list of plots together. This is non-blocking for the world-changes that will be made.
1019     *
1020     * @param plotIds     List of plot IDs to merge
1021     * @param removeRoads If the roads between plots should be removed
1022     * @param whenDone    Task to run when any merge world changes are complete. Also runs if no changes were made. Does not
1023     *                    run if there was an error or if too few plots IDs were supplied.
1024     * @return if merges were completed successfully.
1025     * @since 6.9.0
1026     */
1027    public boolean mergePlots(
1028            final @NonNull List<PlotId> plotIds, final boolean removeRoads, final @Nullable Runnable whenDone
1029    ) {
1030        if (plotIds.size() < 2) {
1031            return false;
1032        }
1033
1034        final PlotId pos1 = plotIds.get(0);
1035        final PlotId pos2 = plotIds.get(plotIds.size() - 1);
1036        final PlotManager manager = getPlotManager();
1037
1038        QueueCoordinator queue = getQueue();
1039        manager.startPlotMerge(plotIds, queue);
1040        final Set<UUID> trusted = new HashSet<>();
1041        final Set<UUID> members = new HashSet<>();
1042        final Set<UUID> denied = new HashSet<>();
1043        for (int x = pos1.getX(); x <= pos2.getX(); x++) {
1044            for (int y = pos1.getY(); y <= pos2.getY(); y++) {
1045                PlotId id = PlotId.of(x, y);
1046                Plot plot = getPlotAbs(id);
1047                trusted.addAll(plot.getTrusted());
1048                members.addAll(plot.getMembers());
1049                denied.addAll(plot.getDenied());
1050                if (removeRoads) {
1051                    plot.getPlotModificationManager().removeSign();
1052                }
1053            }
1054        }
1055        members.removeAll(trusted);
1056        denied.removeAll(trusted);
1057        denied.removeAll(members);
1058        for (int x = pos1.getX(); x <= pos2.getX(); x++) {
1059            for (int y = pos1.getY(); y <= pos2.getY(); y++) {
1060                final boolean lx = x < pos2.getX();
1061                final boolean ly = y < pos2.getY();
1062                final PlotId id = PlotId.of(x, y);
1063                final Plot plot = getPlotAbs(id);
1064
1065                plot.setTrusted(trusted);
1066                plot.setMembers(members);
1067                plot.setDenied(denied);
1068
1069                Plot plot2;
1070                if (lx) {
1071                    if (ly) {
1072                        if (!plot.isMerged(Direction.EAST) || !plot.isMerged(Direction.SOUTH)) {
1073                            if (removeRoads) {
1074                                plot.getPlotModificationManager().removeRoadSouthEast(queue);
1075                            }
1076                        }
1077                    }
1078                    if (!plot.isMerged(Direction.EAST)) {
1079                        plot2 = plot.getRelative(1, 0);
1080                        plot.mergePlot(plot2, removeRoads, queue);
1081                    }
1082                }
1083                if (ly) {
1084                    if (!plot.isMerged(Direction.SOUTH)) {
1085                        plot2 = plot.getRelative(0, 1);
1086                        plot.mergePlot(plot2, removeRoads, queue);
1087                    }
1088                }
1089            }
1090        }
1091        manager.finishPlotMerge(plotIds, queue);
1092        if (whenDone != null) {
1093            queue.setCompleteTask(whenDone);
1094        }
1095        queue.enqueue();
1096        return true;
1097    }
1098
1099    /**
1100     * Get a set of owned plots within a selection (chooses the best algorithm based on selection size.
1101     * i.e. A selection of billions of plots will work fine
1102     *
1103     * @param pos1 first corner of selection
1104     * @param pos2 second corner of selection
1105     * @return the plots in the selection which are owned
1106     */
1107    public Set<Plot> getPlotSelectionOwned(final @NonNull PlotId pos1, final @NonNull PlotId pos2) {
1108        final int size = (1 + pos2.getX() - pos1.getX()) * (1 + pos2.getY() - pos1.getY());
1109        final Set<Plot> result = new HashSet<>();
1110        if (size < 16 || size < getPlotCount()) {
1111            for (final PlotId pid : Lists.newArrayList((Iterable<? extends PlotId>)
1112                    PlotId.PlotRangeIterator.range(pos1, pos2))) {
1113                final Plot plot = getPlotAbs(pid);
1114                if (plot.hasOwner()) {
1115                    if (plot.getId().getX() > pos1.getX() || plot.getId().getY() > pos1.getY()
1116                            || plot.getId().getX() < pos2.getX() || plot.getId().getY() < pos2.getY()) {
1117                        result.add(plot);
1118                    }
1119                }
1120            }
1121        } else {
1122            for (final Plot plot : getPlots()) {
1123                if (plot.getId().getX() > pos1.getX() || plot.getId().getY() > pos1.getY() || plot.getId().getX() < pos2.getX()
1124                        || plot.getId().getY() < pos2.getY()) {
1125                    result.add(plot);
1126                }
1127            }
1128        }
1129        return result;
1130    }
1131
1132    @SuppressWarnings("WeakerAccess")
1133    public void removeCluster(final @Nullable PlotCluster plotCluster) {
1134        if (this.clusters == null) {
1135            throw new IllegalAccessError("Clusters not enabled!");
1136        }
1137        this.clusters.remove(plotCluster);
1138    }
1139
1140    public void addCluster(final @Nullable PlotCluster plotCluster) {
1141        if (this.clusters == null) {
1142            this.clusters = new QuadMap<>(Integer.MAX_VALUE, 0, 0, 62) {
1143                @Override
1144                public CuboidRegion getRegion(PlotCluster value) {
1145                    BlockVector2 pos1 = BlockVector2.at(value.getP1().getX(), value.getP1().getY());
1146                    BlockVector2 pos2 = BlockVector2.at(value.getP2().getX(), value.getP2().getY());
1147                    return new CuboidRegion(
1148                            pos1.toBlockVector3(getMinGenHeight()),
1149                            pos2.toBlockVector3(getMaxGenHeight())
1150                    );
1151                }
1152            };
1153        }
1154        this.clusters.add(plotCluster);
1155    }
1156
1157    public @Nullable PlotCluster getCluster(final String string) {
1158        for (PlotCluster cluster : getClusters()) {
1159            if (cluster.getName().equalsIgnoreCase(string)) {
1160                return cluster;
1161            }
1162        }
1163        return null;
1164    }
1165
1166    /**
1167     * Get whether a schematic with that name is available or not.
1168     * If a schematic is available, it can be used for plot claiming.
1169     *
1170     * @param schematic the schematic to look for.
1171     * @return {@code true} if the schematic exists, {@code false} otherwise.
1172     */
1173    public boolean hasSchematic(@NonNull String schematic) {
1174        return getSchematics().contains(schematic.toLowerCase());
1175    }
1176
1177    /**
1178     * Get whether economy is enabled and used on this plot area or not.
1179     *
1180     * @return {@code true} if this plot area uses economy, {@code false} otherwise.
1181     */
1182    public boolean useEconomy() {
1183        return useEconomy;
1184    }
1185
1186    /**
1187     * Get whether the plot area is limited by a world border or not.
1188     *
1189     * @return {@code true} if the plot area has a world border, {@code false} otherwise.
1190     */
1191    public boolean hasWorldBorder() {
1192        return worldBorder;
1193    }
1194
1195    /**
1196     * Get whether plot signs are allowed or not.
1197     *
1198     * @return {@code true} if plot signs are allowed, {@code false} otherwise.
1199     */
1200    public boolean allowSigns() {
1201        return allowSigns;
1202    }
1203
1204    /**
1205     * Get the plot sign material.
1206     *
1207     * @return the sign material.
1208     */
1209    public String signMaterial() {
1210        return signMaterial;
1211    }
1212
1213    public String legacySignMaterial() {
1214        return legacySignMaterial;
1215    }
1216
1217    /**
1218     * Get the value associated with the specified flag. This will look at
1219     * the default values stored in {@link GlobalFlagContainer}.
1220     *
1221     * @param flagClass The flag type (Class)
1222     * @param <T>       The flag value type
1223     * @return The flag value
1224     */
1225    public <T> T getFlag(final Class<? extends PlotFlag<T, ?>> flagClass) {
1226        return this.flagContainer.getFlag(flagClass).getValue();
1227    }
1228
1229    /**
1230     * Get the value associated with the specified flag. This will look at
1231     * the default values stored in {@link GlobalFlagContainer}.
1232     *
1233     * @param flag The flag type (Any instance of the flag)
1234     * @param <V>  The flag type (Any instance of the flag)
1235     * @param <T>  flag value type
1236     * @return The flag value
1237     */
1238    public <T, V extends PlotFlag<T, ?>> T getFlag(final V flag) {
1239        final Class<?> flagClass = flag.getClass();
1240        final PlotFlag<?, ?> flagInstance = this.flagContainer.getFlagErased(flagClass);
1241        return FlagContainer.<T, V>castUnsafe(flagInstance).getValue();
1242    }
1243
1244    /**
1245     * Get the value associated with the specified road flag. This will look at
1246     * the default values stored in {@link GlobalFlagContainer}.
1247     *
1248     * @param flagClass The flag type (Class)
1249     * @param <T>       the flag value type
1250     * @return The flag value
1251     */
1252    public <T> T getRoadFlag(final Class<? extends PlotFlag<T, ?>> flagClass) {
1253        return this.roadFlagContainer.getFlag(flagClass).getValue();
1254    }
1255
1256    /**
1257     * Get the value associated with the specified road flag. This will look at
1258     * the default values stored in {@link GlobalFlagContainer}.
1259     *
1260     * @param flag The flag type (Any instance of the flag)
1261     * @param <V>  The flag type (Any instance of the flag)
1262     * @param <T>  flag value type
1263     * @return The flag value
1264     */
1265    public <T, V extends PlotFlag<T, ?>> T getRoadFlag(final V flag) {
1266        final Class<?> flagClass = flag.getClass();
1267        final PlotFlag<?, ?> flagInstance = this.roadFlagContainer.getFlagErased(flagClass);
1268        return FlagContainer.<T, V>castUnsafe(flagInstance).getValue();
1269    }
1270
1271    public @NonNull String getWorldName() {
1272        return this.worldName;
1273    }
1274
1275    public String getId() {
1276        return this.id;
1277    }
1278
1279    public @NonNull PlotManager getPlotManager() {
1280        return this.plotManager;
1281    }
1282
1283    public int getWorldHash() {
1284        return this.worldHash;
1285    }
1286
1287    public @NonNull IndependentPlotGenerator getGenerator() {
1288        return this.generator;
1289    }
1290
1291    public boolean isAutoMerge() {
1292        return this.autoMerge;
1293    }
1294
1295    public boolean isMiscSpawnUnowned() {
1296        return this.miscSpawnUnowned;
1297    }
1298
1299    public boolean isMobSpawning() {
1300        return this.mobSpawning;
1301    }
1302
1303    public boolean isMobSpawnerSpawning() {
1304        return this.mobSpawnerSpawning;
1305    }
1306
1307    public BiomeType getPlotBiome() {
1308        return this.plotBiome;
1309    }
1310
1311    public boolean isPlotChat() {
1312        return this.plotChat;
1313    }
1314
1315    public boolean isForcingPlotChat() {
1316        return this.forcingPlotChat;
1317    }
1318
1319    public boolean isSchematicClaimSpecify() {
1320        return this.schematicClaimSpecify;
1321    }
1322
1323    public boolean isSchematicOnClaim() {
1324        return this.schematicOnClaim;
1325    }
1326
1327    public String getSchematicFile() {
1328        return this.schematicFile;
1329    }
1330
1331    public boolean isSpawnEggs() {
1332        return this.spawnEggs;
1333    }
1334
1335    public String getSignMaterial() {
1336        return this.signMaterial;
1337    }
1338
1339    public boolean isSpawnCustom() {
1340        return this.spawnCustom;
1341    }
1342
1343    public boolean isSpawnBreeding() {
1344        return this.spawnBreeding;
1345    }
1346
1347    public PlotAreaType getType() {
1348        return this.type;
1349    }
1350
1351    /**
1352     * Set the type of this plot area.
1353     *
1354     * @param type the type of the plot area.
1355     */
1356    public void setType(PlotAreaType type) {
1357        // TODO this should probably work only if type == null
1358        this.type = type;
1359    }
1360
1361    public PlotAreaTerrainType getTerrain() {
1362        return this.terrain;
1363    }
1364
1365    /**
1366     * Set the terrain generation type of this plot area.
1367     *
1368     * @param terrain the terrain type of the plot area.
1369     */
1370    public void setTerrain(PlotAreaTerrainType terrain) {
1371        this.terrain = terrain;
1372    }
1373
1374    public boolean isHomeAllowNonmember() {
1375        return this.homeAllowNonmember;
1376    }
1377
1378    /**
1379     * Get the location for non-members to be teleported to.
1380     *
1381     * @since 6.1.4
1382     */
1383    public BlockLoc nonmemberHome() {
1384        return this.nonmemberHome;
1385    }
1386
1387    /**
1388     * Get the default location for players to be teleported to. May be overridden by {@link #nonmemberHome} if the player is
1389     * not a member of the plot.
1390     *
1391     * @since 6.1.4
1392     */
1393    public BlockLoc defaultHome() {
1394        return this.defaultHome;
1395    }
1396
1397    protected void setDefaultHome(BlockLoc defaultHome) {
1398        this.defaultHome = defaultHome;
1399    }
1400
1401    /**
1402     * Get the maximum height players may build in. Exclusive.
1403     */
1404    public int getMaxBuildHeight() {
1405        return this.maxBuildHeight;
1406    }
1407
1408    /**
1409     * Get the minimum height players may build in. Inclusive.
1410     */
1411    public int getMinBuildHeight() {
1412        return this.minBuildHeight;
1413    }
1414
1415    /**
1416     * Get the min height from which PlotSquared will generate blocks. Inclusive.
1417     *
1418     * @since 6.6.0
1419     */
1420    public int getMinGenHeight() {
1421        return this.minGenHeight;
1422    }
1423
1424    /**
1425     * Get the max height to which PlotSquared will generate blocks. Inclusive.
1426     *
1427     * @since 6.6.0
1428     */
1429    public int getMaxGenHeight() {
1430        return this.maxGenHeight;
1431    }
1432
1433    public GameMode getGameMode() {
1434        return this.gameMode;
1435    }
1436
1437    public Map<String, PlotExpression> getPrices() {
1438        return this.prices;
1439    }
1440
1441    protected List<String> getSchematics() {
1442        return this.schematics;
1443    }
1444
1445    public boolean isRoadFlags() {
1446        return this.roadFlags;
1447    }
1448
1449    public FlagContainer getFlagContainer() {
1450        return this.flagContainer;
1451    }
1452
1453    public FlagContainer getRoadFlagContainer() {
1454        return this.roadFlagContainer;
1455    }
1456
1457    public void setAllowSigns(boolean allowSigns) {
1458        this.allowSigns = allowSigns;
1459    }
1460
1461}