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