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