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