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