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.command;
020
021import com.google.inject.Inject;
022import com.plotsquared.core.PlotSquared;
023import com.plotsquared.core.configuration.ConfigurationSection;
024import com.plotsquared.core.configuration.ConfigurationUtil;
025import com.plotsquared.core.configuration.Settings;
026import com.plotsquared.core.configuration.caption.CaptionHolder;
027import com.plotsquared.core.configuration.caption.TranslatableCaption;
028import com.plotsquared.core.configuration.file.YamlConfiguration;
029import com.plotsquared.core.events.TeleportCause;
030import com.plotsquared.core.generator.AugmentedUtils;
031import com.plotsquared.core.generator.HybridPlotWorld;
032import com.plotsquared.core.inject.annotations.WorldConfig;
033import com.plotsquared.core.inject.annotations.WorldFile;
034import com.plotsquared.core.inject.factory.HybridPlotWorldFactory;
035import com.plotsquared.core.location.Location;
036import com.plotsquared.core.permissions.Permission;
037import com.plotsquared.core.player.ConsolePlayer;
038import com.plotsquared.core.player.PlotPlayer;
039import com.plotsquared.core.plot.PlotArea;
040import com.plotsquared.core.plot.PlotAreaTerrainType;
041import com.plotsquared.core.plot.PlotAreaType;
042import com.plotsquared.core.plot.PlotId;
043import com.plotsquared.core.plot.world.PlotAreaManager;
044import com.plotsquared.core.plot.world.SinglePlotArea;
045import com.plotsquared.core.queue.GlobalBlockQueue;
046import com.plotsquared.core.queue.QueueCoordinator;
047import com.plotsquared.core.setup.PlotAreaBuilder;
048import com.plotsquared.core.util.FileUtils;
049import com.plotsquared.core.util.MathMan;
050import com.plotsquared.core.util.RegionUtil;
051import com.plotsquared.core.util.SchematicHandler;
052import com.plotsquared.core.util.SetupUtils;
053import com.plotsquared.core.util.StringMan;
054import com.plotsquared.core.util.TabCompletions;
055import com.plotsquared.core.util.WorldUtil;
056import com.plotsquared.core.util.task.RunnableVal3;
057import com.sk89q.worldedit.EditSession;
058import com.sk89q.worldedit.EditSessionBuilder;
059import com.sk89q.worldedit.LocalSession;
060import com.sk89q.worldedit.WorldEdit;
061import com.sk89q.worldedit.entity.Player;
062import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
063import com.sk89q.worldedit.extent.clipboard.io.BuiltInClipboardFormat;
064import com.sk89q.worldedit.extent.clipboard.io.ClipboardWriter;
065import com.sk89q.worldedit.function.operation.ForwardExtentCopy;
066import com.sk89q.worldedit.function.operation.Operations;
067import com.sk89q.worldedit.math.BlockVector3;
068import com.sk89q.worldedit.regions.CuboidRegion;
069import com.sk89q.worldedit.regions.Region;
070import com.sk89q.worldedit.world.World;
071import net.kyori.adventure.text.Component;
072import net.kyori.adventure.text.minimessage.tag.Tag;
073import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
074import org.checkerframework.checker.nullness.qual.NonNull;
075
076import java.io.File;
077import java.io.FileOutputStream;
078import java.io.IOException;
079import java.util.ArrayList;
080import java.util.Arrays;
081import java.util.Collection;
082import java.util.Collections;
083import java.util.HashMap;
084import java.util.LinkedList;
085import java.util.List;
086import java.util.Map;
087import java.util.Objects;
088import java.util.Set;
089import java.util.UUID;
090import java.util.stream.Collectors;
091
092@CommandDeclaration(command = "area",
093        permission = "plots.area",
094        category = CommandCategory.ADMINISTRATION,
095        requiredType = RequiredType.NONE,
096        aliases = "world",
097        usage = "/plot area <create | info | list | tp | regen>",
098        confirmation = true)
099public class Area extends SubCommand {
100
101    private final PlotAreaManager plotAreaManager;
102    private final YamlConfiguration worldConfiguration;
103    private final File worldFile;
104    private final HybridPlotWorldFactory hybridPlotWorldFactory;
105    private final SetupUtils setupUtils;
106    private final WorldUtil worldUtil;
107    private final GlobalBlockQueue blockQueue;
108
109    private final Map<UUID, Map<String, Object>> metaData = new HashMap<>();
110
111    @Inject
112    public Area(
113            final @NonNull PlotAreaManager plotAreaManager,
114            @WorldConfig final @NonNull YamlConfiguration worldConfiguration,
115            @WorldFile final @NonNull File worldFile,
116            final @NonNull HybridPlotWorldFactory hybridPlotWorldFactory,
117            final @NonNull SetupUtils setupUtils,
118            final @NonNull WorldUtil worldUtil,
119            final @NonNull GlobalBlockQueue blockQueue
120    ) {
121        this.plotAreaManager = plotAreaManager;
122        this.worldConfiguration = worldConfiguration;
123        this.worldFile = worldFile;
124        this.hybridPlotWorldFactory = hybridPlotWorldFactory;
125        this.setupUtils = setupUtils;
126        this.worldUtil = worldUtil;
127        this.blockQueue = blockQueue;
128    }
129
130    @Override
131    public boolean onCommand(final PlotPlayer<?> player, String[] args) {
132        if (args.length == 0) {
133            sendUsage(player);
134            return false;
135        }
136        switch (args[0].toLowerCase()) {
137            case "single" -> {
138                if (player instanceof ConsolePlayer) {
139                    player.sendMessage(RequiredType.CONSOLE.getErrorMessage());
140                    return false;
141                }
142                if (!player.hasPermission(Permission.PERMISSION_AREA_CREATE)) {
143                    player.sendMessage(
144                            TranslatableCaption.of("permission.no_permission"),
145                            TagResolver.resolver(
146                                    "node",
147                                    Tag.inserting(Permission.PERMISSION_AREA_CREATE)
148                            )
149                    );
150                    return false;
151                }
152                if (args.length < 2) {
153                    player.sendMessage(
154                            TranslatableCaption.of("single.single_area_needs_name"),
155                            TagResolver.resolver("command", Tag.inserting(Component.text("/plot area single <name>")))
156                    );
157                    return false;
158                }
159                final PlotArea existingArea = this.plotAreaManager.getPlotArea(player.getLocation().getWorldName(), args[1]);
160                if (existingArea != null && existingArea.getId().equalsIgnoreCase(args[1])) {
161                    player.sendMessage(TranslatableCaption.of("single.single_area_name_taken"));
162                    return false;
163                }
164                final LocalSession localSession = WorldEdit.getInstance().getSessionManager().getIfPresent(player.toActor());
165                if (localSession == null) {
166                    player.sendMessage(TranslatableCaption.of("single.single_area_missing_selection"));
167                    return false;
168                }
169                Region playerSelectedRegion = null;
170                try {
171                    playerSelectedRegion = localSession.getSelection(((Player) player.toActor()).getWorld());
172                } catch (final Exception ignored) {
173                }
174                if (playerSelectedRegion == null) {
175                    player.sendMessage(TranslatableCaption.of("single.single_area_missing_selection"));
176                    return false;
177                }
178                if (playerSelectedRegion.getWidth() != playerSelectedRegion.getLength()) {
179                    player.sendMessage(TranslatableCaption.of("single.single_area_not_square"));
180                    return false;
181                }
182                if (this.plotAreaManager.getPlotAreas(
183                        Objects.requireNonNull(playerSelectedRegion.getWorld()).getName(),
184                        CuboidRegion.makeCuboid(playerSelectedRegion)
185                ).length != 0) {
186                    player.sendMessage(TranslatableCaption.of("single.single_area_overlapping"));
187                    return false;
188                }
189                // Alter the region
190                final BlockVector3 playerSelectionMin = playerSelectedRegion.getMinimumPoint();
191                final BlockVector3 playerSelectionMax = playerSelectedRegion.getMaximumPoint();
192                // Create a new selection that spans the entire vertical range of the world
193                World world = playerSelectedRegion.getWorld();
194                final CuboidRegion selectedRegion =
195                        new CuboidRegion(
196                                playerSelectedRegion.getWorld(),
197                                BlockVector3.at(playerSelectionMin.getX(), world.getMinY(), playerSelectionMin.getZ()),
198                                BlockVector3.at(playerSelectionMax.getX(), world.getMaxY(), playerSelectionMax.getZ())
199                        );
200                // There's only one plot in the area...
201                final PlotId plotId = PlotId.of(1, 1);
202                final HybridPlotWorld hybridPlotWorld = this.hybridPlotWorldFactory
203                        .create(
204                                player.getLocation().getWorldName(),
205                                args[1],
206                                Objects.requireNonNull(PlotSquared.platform()).defaultGenerator(),
207                                plotId,
208                                plotId
209                        );
210                // Plot size is the same as the region width
211                hybridPlotWorld.PLOT_WIDTH = hybridPlotWorld.SIZE = (short) selectedRegion.getWidth();
212                // We use a schematic generator
213                hybridPlotWorld.setTerrain(PlotAreaTerrainType.NONE);
214                // It is always a partial plot world
215                hybridPlotWorld.setType(PlotAreaType.PARTIAL);
216                // We save the schematic :D
217                hybridPlotWorld.PLOT_SCHEMATIC = true;
218                // Set the road width to 0
219                hybridPlotWorld.ROAD_WIDTH = hybridPlotWorld.ROAD_OFFSET_X = hybridPlotWorld.ROAD_OFFSET_Z = 0;
220                // Set the plot height to the selection height
221                hybridPlotWorld.PLOT_HEIGHT = hybridPlotWorld.ROAD_HEIGHT = hybridPlotWorld.WALL_HEIGHT = playerSelectionMin.getBlockY();
222                // No sign plz
223                hybridPlotWorld.setAllowSigns(false);
224                final File parentFile = FileUtils.getFile(
225                        PlotSquared.platform().getDirectory(),
226                        Settings.Paths.SCHEMATICS + File.separator + "GEN_ROAD_SCHEMATIC" + File.separator + hybridPlotWorld.getWorldName() + File.separator
227                                + hybridPlotWorld.getId()
228                );
229                if (!parentFile.exists() && !parentFile.mkdirs()) {
230                    player.sendMessage(TranslatableCaption.of("single.single_area_could_not_make_directories"));
231                    return false;
232                }
233                final File file = new File(parentFile, "plot.schem");
234                try (final ClipboardWriter clipboardWriter = BuiltInClipboardFormat.SPONGE_SCHEMATIC.getWriter(new FileOutputStream(
235                        file))) {
236                    final BlockArrayClipboard clipboard = new BlockArrayClipboard(selectedRegion);
237                    EditSessionBuilder editSessionBuilder = WorldEdit.getInstance().newEditSessionBuilder();
238                    editSessionBuilder.world(selectedRegion.getWorld());
239                    final EditSession editSession = editSessionBuilder.build();
240                    final ForwardExtentCopy forwardExtentCopy =
241                            new ForwardExtentCopy(editSession, selectedRegion, clipboard, selectedRegion.getMinimumPoint());
242                    forwardExtentCopy.setCopyingBiomes(true);
243                    forwardExtentCopy.setCopyingEntities(true);
244                    Operations.complete(forwardExtentCopy);
245                    clipboardWriter.write(clipboard);
246                } catch (final Exception e) {
247                    player.sendMessage(TranslatableCaption.of("single.single_area_failed_to_save"));
248                    e.printStackTrace();
249                    return false;
250                }
251
252                // Setup schematic
253                try {
254                    hybridPlotWorld.setupSchematics();
255                } catch (final SchematicHandler.UnsupportedFormatException e) {
256                    e.printStackTrace();
257                }
258
259                // Calculate the offset
260                final BlockVector3 singlePos1 = selectedRegion.getMinimumPoint();
261
262                // Now the schematic is saved, which is wonderful!
263                PlotAreaBuilder singleBuilder = PlotAreaBuilder.ofPlotArea(hybridPlotWorld).plotManager(PlotSquared
264                                .platform()
265                                .pluginName())
266                        .generatorName(PlotSquared.platform().pluginName()).maximumId(plotId).minimumId(plotId);
267                Runnable singleRun = () -> {
268                    final String path =
269                            "worlds." + hybridPlotWorld.getWorldName() + ".areas." + hybridPlotWorld.getId() + '-' + singleBuilder
270                                    .minimumId() + '-'
271                                    + singleBuilder.maximumId();
272                    final int offsetX = singlePos1.getX();
273                    final int offsetZ = singlePos1.getZ();
274                    if (offsetX != 0) {
275                        this.worldConfiguration.set(path + ".road.offset.x", offsetX);
276                    }
277                    if (offsetZ != 0) {
278                        this.worldConfiguration.set(path + ".road.offset.z", offsetZ);
279                    }
280                    final String worldName = this.setupUtils.setupWorld(singleBuilder);
281                    if (this.worldUtil.isWorld(worldName)) {
282                        PlotSquared.get().loadWorld(worldName, null);
283                        player.sendMessage(TranslatableCaption.of("single.single_area_created"));
284                    } else {
285                        player.sendMessage(
286                                TranslatableCaption.of("errors.error_create"),
287                                TagResolver.resolver("world", Tag.inserting(Component.text(hybridPlotWorld.getWorldName())))
288                        );
289                    }
290                };
291                singleRun.run();
292                return true;
293            }
294            case "c", "setup", "create" -> {
295                if (!player.hasPermission(Permission.PERMISSION_AREA_CREATE)) {
296                    player.sendMessage(
297                            TranslatableCaption.of("permission.no_permission"),
298                            TagResolver.resolver(
299                                    "node",
300                                    Tag.inserting(Permission.PERMISSION_AREA_CREATE)
301                            )
302                    );
303                    return false;
304                }
305                switch (args.length) {
306                    case 1:
307                        player.sendMessage(
308                                TranslatableCaption.of("commandconfig.command_syntax"),
309                                TagResolver.resolver(
310                                        "value",
311                                        Tag.inserting(Component.text("/plot area create [world[:id]] [<modifier>=<value>]..."))
312                                )
313                        );
314                        return false;
315                    case 2:
316                        switch (args[1].toLowerCase()) {
317                            case "pos1" -> { // Set position 1
318                                HybridPlotWorld area = (HybridPlotWorld) metaData.computeIfAbsent(
319                                                player.getUUID(),
320                                                missingUUID -> new HashMap<>()
321                                        )
322                                        .get("area_create_area");
323                                if (area == null) {
324                                    player.sendMessage(
325                                            TranslatableCaption.of("commandconfig.command_syntax"),
326                                            TagResolver.resolver(
327                                                    "value",
328                                                    Tag.inserting(Component.text(
329                                                            "/plot area create [world[:id]] [<modifier>=<value>]..."))
330                                            )
331                                    );
332                                    return false;
333                                }
334                                Location location = player.getLocation();
335                                metaData.computeIfAbsent(player.getUUID(), missingUUID -> new HashMap<>()).put(
336                                        "area_pos1",
337                                        location
338                                );
339                                player.sendMessage(
340                                        TranslatableCaption.of("set.set_attribute"),
341                                        TagResolver.builder()
342                                                .tag("attribute", Tag.inserting(Component.text("area_pos1")))
343                                                .tag("value", Tag.inserting(
344                                                        Component.text(location.getX())
345                                                                .append(Component.text(","))
346                                                                .append(Component.text(location.getZ()))
347                                                ))
348                                                .build()
349                                );
350                                player.sendMessage(
351                                        TranslatableCaption.of("area.set_pos2"),
352                                        TagResolver.resolver("command", Tag.inserting(Component.text("/plot area create pos2")))
353                                );
354                                return true;
355                            }
356                            case "pos2" -> {  // Set position 2 and finish creation for type=2 (partial)
357                                final HybridPlotWorld area =
358                                        (HybridPlotWorld) metaData.computeIfAbsent(
359                                                        player.getUUID(),
360                                                        missingUUID -> new HashMap<>()
361                                                )
362                                                .get("area_create_area");
363                                if (area == null) {
364                                    player.sendMessage(
365                                            TranslatableCaption.of("commandconfig.command_syntax"),
366                                            TagResolver.resolver(
367                                                    "value",
368                                                    Tag.inserting(Component.text(
369                                                            "/plot area create [world[:id]] [<modifier>=<value>]..."))
370                                            )
371                                    );
372                                    return false;
373                                }
374                                Location pos1 = player.getLocation();
375                                Location pos2 =
376                                        (Location) metaData.computeIfAbsent(player.getUUID(), missingUUID -> new HashMap<>()).get(
377                                                "area_pos1");
378                                int dx = Math.abs(pos1.getX() - pos2.getX());
379                                int dz = Math.abs(pos1.getZ() - pos2.getZ());
380                                int numX = Math.max(1, (dx + 1 + area.ROAD_WIDTH + area.SIZE / 2) / area.SIZE);
381                                int numZ = Math.max(1, (dz + 1 + area.ROAD_WIDTH + area.SIZE / 2) / area.SIZE);
382                                int ddx = dx - (numX * area.SIZE - area.ROAD_WIDTH);
383                                int ddz = dz - (numZ * area.SIZE - area.ROAD_WIDTH);
384                                int bx = Math.min(pos1.getX(), pos2.getX()) + ddx;
385                                int bz = Math.min(pos1.getZ(), pos2.getZ()) + ddz;
386                                int tx = Math.max(pos1.getX(), pos2.getX()) - ddx;
387                                int tz = Math.max(pos1.getZ(), pos2.getZ()) - ddz;
388                                int lower = (area.ROAD_WIDTH & 1) == 0 ? area.ROAD_WIDTH / 2 - 1 : area.ROAD_WIDTH / 2;
389                                final int offsetX = bx - (area.ROAD_WIDTH == 0 ? 0 : lower);
390                                final int offsetZ = bz - (area.ROAD_WIDTH == 0 ? 0 : lower);
391                                // Height doesn't matter for this region
392                                final CuboidRegion region = RegionUtil.createRegion(bx, tx, 0, 0, bz, tz);
393                                final Set<PlotArea> areas = this.plotAreaManager.getPlotAreasSet(area.getWorldName(), region);
394                                if (!areas.isEmpty()) {
395                                    player.sendMessage(
396                                            TranslatableCaption.of("cluster.cluster_intersection"),
397                                            TagResolver.resolver(
398                                                    "cluster",
399                                                    Tag.inserting(areas.iterator().next())
400                                            )
401                                    );
402                                    return false;
403                                }
404                                PlotAreaBuilder builder = PlotAreaBuilder.ofPlotArea(area).plotManager(PlotSquared
405                                                .platform()
406                                                .pluginName())
407                                        .generatorName(PlotSquared.platform().pluginName()).minimumId(PlotId.of(1, 1))
408                                        .maximumId(PlotId.of(numX, numZ));
409                                final String path =
410                                        "worlds." + area.getWorldName() + ".areas." + area.getId() + '-' + builder.minimumId() + '-' + builder
411                                                .maximumId();
412                                Runnable run = () -> {
413                                    if (offsetX != 0) {
414                                        this.worldConfiguration.set(path + ".road.offset.x", offsetX);
415                                    }
416                                    if (offsetZ != 0) {
417                                        this.worldConfiguration.set(path + ".road.offset.z", offsetZ);
418                                    }
419                                    final String world = this.setupUtils.setupWorld(builder);
420                                    if (this.worldUtil.isWorld(world)) {
421                                        PlotSquared.get().loadWorld(world, null);
422                                        player.teleport(this.worldUtil.getSpawn(world), TeleportCause.COMMAND_AREA_CREATE);
423                                        player.sendMessage(TranslatableCaption.of("setup.setup_finished"));
424                                        if (area.getTerrain() != PlotAreaTerrainType.ALL) {
425                                            QueueCoordinator queue = blockQueue.getNewQueue(worldUtil.getWeWorld(world));
426                                            queue.setChunkConsumer(chunk -> AugmentedUtils.generateChunk(
427                                                    world,
428                                                    chunk.getX(),
429                                                    chunk.getZ(),
430                                                    null
431                                            ));
432                                            queue.addReadChunks(region.getChunks());
433                                            queue.enqueue();
434                                        }
435                                    } else {
436                                        player.sendMessage(
437                                                TranslatableCaption.of("errors.error_create"),
438                                                TagResolver.resolver("world", Tag.inserting(Component.text(area.getWorldName())))
439                                        );
440                                    }
441                                };
442                                if (hasConfirmation(player)) {
443                                    CmdConfirm.addPending(player, getCommandString() + " create pos2 (Creates world)", run);
444                                } else {
445                                    run.run();
446                                }
447                                return true;
448                            }
449                        }
450                    default: // Start creation
451                        String[] split = args[1].split(":");
452                        String id;
453                        if (split.length == 2) {
454                            id = split[1];
455                        } else {
456                            id = null;
457                        }
458                        PlotAreaBuilder builder = PlotAreaBuilder.newBuilder();
459                        builder.worldName(split[0]);
460                        final HybridPlotWorld pa =
461                                this.hybridPlotWorldFactory.create(
462                                        builder.worldName(),
463                                        id,
464                                        PlotSquared.platform().defaultGenerator(),
465                                        null,
466                                        null
467                                );
468                        PlotArea other = this.plotAreaManager.getPlotArea(pa.getWorldName(), id);
469                        if (other != null && Objects.equals(pa.getId(), other.getId())) {
470                            player.sendMessage(
471                                    TranslatableCaption.of("setup.setup_world_taken"),
472                                    TagResolver.resolver("value", Tag.inserting(Component.text(pa.getId())))
473                            );
474                            return false;
475                        }
476                        Set<PlotArea> areas = this.plotAreaManager.getPlotAreasSet(pa.getWorldName());
477                        if (!areas.isEmpty()) {
478                            PlotArea area = areas.iterator().next();
479                            pa.setType(area.getType());
480                        }
481                        pa.SIZE = (short) (pa.PLOT_WIDTH + pa.ROAD_WIDTH);
482                        for (int i = 2; i < args.length; i++) {
483                            String[] pair = args[i].split("=");
484                            if (pair.length != 2) {
485                                player.sendMessage(
486                                        TranslatableCaption.of("commandconfig.command_syntax_extended"),
487                                        TagResolver.builder()
488                                                .tag("value1", Tag.inserting(Component.text(getCommandString())))
489                                                .tag(
490                                                        "value2",
491                                                        Tag.inserting(Component.text("create [world[:id]] [<modifier>=<value>]..."))
492                                                )
493                                                .build()
494                                );
495                                return false;
496                            }
497                            switch (pair[0].toLowerCase()) {
498                                case "s", "size" -> {
499                                    pa.PLOT_WIDTH = Integer.parseInt(pair[1]);
500                                    pa.SIZE = (short) (pa.PLOT_WIDTH + pa.ROAD_WIDTH);
501                                }
502                                case "g", "gap" -> {
503                                    pa.ROAD_WIDTH = Integer.parseInt(pair[1]);
504                                    pa.SIZE = (short) (pa.PLOT_WIDTH + pa.ROAD_WIDTH);
505                                }
506                                case "h", "height" -> {
507                                    int value = Integer.parseInt(pair[1]);
508                                    pa.PLOT_HEIGHT = value;
509                                    pa.ROAD_HEIGHT = value;
510                                    pa.WALL_HEIGHT = value;
511                                }
512                                case "f", "floor" -> pa.TOP_BLOCK = ConfigurationUtil.BLOCK_BUCKET.parseString(pair[1]);
513                                case "m", "main" -> pa.MAIN_BLOCK = ConfigurationUtil.BLOCK_BUCKET.parseString(pair[1]);
514                                case "w", "wall" -> pa.WALL_FILLING = ConfigurationUtil.BLOCK_BUCKET.parseString(pair[1]);
515                                case "b", "border" -> pa.WALL_BLOCK = ConfigurationUtil.BLOCK_BUCKET.parseString(pair[1]);
516                                case "terrain" -> {
517                                    pa.setTerrain(PlotAreaTerrainType.fromString(pair[1])
518                                            .orElseThrow(() -> new IllegalArgumentException(pair[1] + " is not a valid terrain.")));
519                                    builder.terrainType(pa.getTerrain());
520                                }
521                                case "type" -> {
522                                    pa.setType(PlotAreaType.fromString(pair[1])
523                                            .orElseThrow(() -> new IllegalArgumentException(pair[1] + " is not a valid type.")));
524                                    builder.plotAreaType(pa.getType());
525                                }
526                                default -> {
527                                    player.sendMessage(
528                                            TranslatableCaption.of("commandconfig.command_syntax_extended"),
529                                            TagResolver.builder()
530                                                    .tag("value1", Tag.inserting(Component.text(getCommandString())))
531                                                    .tag(
532                                                            "value2",
533                                                            Tag.inserting(Component.text(
534                                                                    " create [world[:id]] [<modifier>=<value>]..."))
535                                                    )
536                                                    .build()
537                                    );
538                                    return false;
539                                }
540                            }
541                        }
542                        if (pa.getType() != PlotAreaType.PARTIAL) {
543                            if (this.worldUtil.isWorld(pa.getWorldName())) {
544                                player.sendMessage(
545                                        TranslatableCaption.of("setup.setup_world_taken"),
546                                        TagResolver.resolver("value", Tag.inserting(Component.text(pa.getWorldName())))
547                                );
548                                return false;
549                            }
550                            Runnable run = () -> {
551                                String path = "worlds." + pa.getWorldName();
552                                if (!this.worldConfiguration.contains(path)) {
553                                    this.worldConfiguration.createSection(path);
554                                }
555                                ConfigurationSection section = this.worldConfiguration.getConfigurationSection(path);
556                                pa.saveConfiguration(section);
557                                pa.loadConfiguration(section);
558                                builder.plotManager(PlotSquared.platform().pluginName());
559                                builder.generatorName(PlotSquared.platform().pluginName());
560                                String world = this.setupUtils.setupWorld(builder);
561                                if (this.worldUtil.isWorld(world)) {
562                                    player.teleport(this.worldUtil.getSpawn(world), TeleportCause.COMMAND_AREA_CREATE);
563                                    player.sendMessage(TranslatableCaption.of("setup.setup_finished"));
564                                } else {
565                                    player.sendMessage(
566                                            TranslatableCaption.of("errors.error_create"),
567                                            TagResolver.resolver("world", Tag.inserting(Component.text(pa.getWorldName())))
568                                    );
569                                }
570                                try {
571                                    this.worldConfiguration.save(this.worldFile);
572                                } catch (IOException e) {
573                                    e.printStackTrace();
574                                }
575                            };
576                            if (hasConfirmation(player)) {
577                                CmdConfirm.addPending(player, getCommandString() + ' ' + StringMan.join(args, " "), run);
578                            } else {
579                                run.run();
580                            }
581                            return true;
582                        }
583                        if (pa.getId() == null) {
584                            player.sendMessage(
585                                    TranslatableCaption.of("commandconfig.command_syntax"),
586                                    TagResolver.resolver("value", Tag.inserting(Component.text(getUsage())))
587                            );
588                            player.sendMessage(
589                                    TranslatableCaption.of("commandconfig.command_syntax_extended"),
590                                    TagResolver.builder()
591                                            .tag("value1", Tag.inserting(Component.text(getCommandString())))
592                                            .tag(
593                                                    "value2",
594                                                    Tag.inserting(Component.text(
595                                                            " create [world[:id]] [<modifier>=<value>]..."))
596                                            )
597                                            .build()
598                            );
599                            return false;
600                        }
601                        if (this.worldUtil.isWorld(pa.getWorldName())) {
602                            if (!player.getLocation().getWorldName().equals(pa.getWorldName())) {
603                                player.teleport(this.worldUtil.getSpawn(pa.getWorldName()), TeleportCause.COMMAND_AREA_CREATE);
604                            }
605                        } else {
606                            builder.terrainType(PlotAreaTerrainType.NONE);
607                            builder.plotAreaType(PlotAreaType.NORMAL);
608                            this.setupUtils.setupWorld(builder);
609                            player.teleport(this.worldUtil.getSpawn(pa.getWorldName()), TeleportCause.COMMAND_AREA_CREATE);
610                        }
611                        metaData.computeIfAbsent(player.getUUID(), missingUUID -> new HashMap<>()).put("area_create_area", pa);
612                        player.sendMessage(
613                                TranslatableCaption.of("single.get_position"),
614                                TagResolver.resolver("command", Tag.inserting(Component.text(getCommandString())))
615                        );
616                        break;
617                }
618                return true;
619            }
620            case "i", "info" -> {
621                if (!player.hasPermission(Permission.PERMISSION_AREA_INFO)) {
622                    player.sendMessage(
623                            TranslatableCaption.of("permission.no_permission"),
624                            TagResolver.resolver(
625                                    "node",
626                                    Tag.inserting(Permission.PERMISSION_AREA_INFO)
627                            )
628                    );
629                    return false;
630                }
631                PlotArea area;
632                switch (args.length) {
633                    case 1 -> area = player.getApplicablePlotArea();
634                    case 2 -> area = this.plotAreaManager.getPlotAreaByString(args[1]);
635                    default -> {
636                        player.sendMessage(
637                                TranslatableCaption.of("commandconfig.command_syntax_extended"),
638                                TagResolver.builder()
639                                        .tag("value1", Tag.inserting(Component.text(getCommandString())))
640                                        .tag("value2", Tag.inserting(Component.text(" info [area]")))
641                                        .build()
642                        );
643                        return false;
644                    }
645                }
646                if (area == null) {
647                    if (args.length == 2) {
648                        player.sendMessage(
649                                TranslatableCaption.of("errors.not_valid_plot_world"),
650                                TagResolver.resolver("value", Tag.inserting(Component.text(args[1])))
651                        );
652                    } else {
653                        player.sendMessage(TranslatableCaption.of("errors.not_in_plot_world"));
654                    }
655                    return false;
656                }
657                String name;
658                double percent;
659                int claimed = area.getPlotCount();
660                int clusters = area.getClusters().size();
661                String region;
662                String generator = String.valueOf(area.getGenerator());
663                if (area.getType() == PlotAreaType.PARTIAL) {
664                    PlotId min = area.getMin();
665                    PlotId max = area.getMax();
666                    name = area.getWorldName() + ';' + area.getId() + ';' + min + ';' + max;
667                    int size = (max.getX() - min.getX() + 1) * (max.getY() - min.getY() + 1);
668                    percent = claimed == 0 ? 0 : size / (double) claimed;
669                    region = area.getRegion().toString();
670                } else {
671                    name = area.getWorldName();
672                    percent = claimed == 0 ? 0 : 100d * claimed / Integer.MAX_VALUE;
673                    region = "N/A";
674                }
675                TagResolver resolver = TagResolver.builder()
676                        .tag(
677                                "header",
678                                Tag.inserting(TranslatableCaption.of("info.plot_info_header").toComponent(player))
679                        )
680                        .tag("name", Tag.inserting(Component.text(name)))
681                        .tag("type", Tag.inserting(Component.text(area.getType().name())))
682                        .tag("terrain", Tag.inserting(Component.text(area.getTerrain().name())))
683                        .tag("usage", Tag.inserting(Component.text(String.format("%.2f", percent))))
684                        .tag("claimed", Tag.inserting(Component.text(claimed)))
685                        .tag("clusters", Tag.inserting(Component.text(clusters)))
686                        .tag("region", Tag.inserting(Component.text(region)))
687                        .tag("generator", Tag.inserting(Component.text(generator)))
688                        .tag(
689                                "footer",
690                                Tag.inserting(TranslatableCaption.of("info.plot_info_footer").toComponent(player))
691                        )
692                        .build();
693                player.sendMessage(TranslatableCaption.of("info.area_info_format"), resolver);
694                return true;
695            }
696            case "l", "list" -> {
697                if (!player.hasPermission(Permission.PERMISSION_AREA_LIST)) {
698                    player.sendMessage(
699                            TranslatableCaption.of("permission.no_permission"),
700                            TagResolver.resolver(
701                                    "node",
702                                    Tag.inserting(Permission.PERMISSION_AREA_LIST)
703                            )
704                    );
705                    return false;
706                }
707                int page;
708                switch (args.length) {
709                    case 1:
710                        page = 0;
711                        break;
712                    case 2:
713                        if (MathMan.isInteger(args[1])) {
714                            page = Integer.parseInt(args[1]) - 1;
715                            break;
716                        }
717                    default:
718                        player.sendMessage(
719                                TranslatableCaption.of("commandconfig.command_syntax_extended"),
720                                TagResolver.builder()
721                                        .tag("value1", Tag.inserting(Component.text(getCommandString())))
722                                        .tag("value2", Tag.inserting(Component.text(" list [#]")))
723                                        .build()
724                        );
725                        return false;
726                }
727                final List<PlotArea> areas = new ArrayList<>(Arrays.asList(this.plotAreaManager.getAllPlotAreas()));
728                paginate(player, areas, 8, page, new RunnableVal3<Integer, PlotArea, CaptionHolder>() {
729                    @Override
730                    public void run(Integer i, PlotArea area, CaptionHolder caption) {
731                        String name;
732                        double percent;
733                        int claimed = area.getPlotCount();
734                        int clusters = area.getClusters().size();
735                        String region;
736                        String generator = String.valueOf(area.getGenerator());
737                        if (area.getType() == PlotAreaType.PARTIAL) {
738                            PlotId min = area.getMin();
739                            PlotId max = area.getMax();
740                            name = area.getWorldName() + ';' + area.getId() + ';' + min + ';' + max;
741                            int size = (max.getX() - min.getX() + 1) * (max.getY() - min.getY() + 1);
742                            percent = claimed == 0 ? 0 : claimed / (double) size;
743                            region = area.getRegion().toString();
744                        } else {
745                            name = area.getWorldName();
746                            percent = claimed == 0 ? 0 : (double) claimed / Short.MAX_VALUE * Short.MAX_VALUE;
747                            region = "N/A";
748                        }
749                        Component tooltip = MINI_MESSAGE.deserialize(
750                                TranslatableCaption.of("info.area_list_tooltip").getComponent(player),
751                                TagResolver.builder()
752                                        .tag("claimed", Tag.inserting(Component.text(claimed)))
753                                        .tag("usage", Tag.inserting(Component.text(String.format("%.2f", percent) + "%")))
754                                        .tag("clusters", Tag.inserting(Component.text(clusters)))
755                                        .tag("region", Tag.inserting(Component.text(region)))
756                                        .tag("generator", Tag.inserting(Component.text(generator)))
757                                        .build()
758                        );
759                        TagResolver resolver = TagResolver.builder()
760                                .tag("hover_info", Tag.inserting(tooltip))
761                                .tag("command_tp", Tag.preProcessParsed("/plot area tp " + name))
762                                .tag("command_info", Tag.preProcessParsed("/plot area info " + name))
763                                .tag("number", Tag.inserting(Component.text(i)))
764                                .tag("area_name", Tag.inserting(Component.text(name)))
765                                .tag("area_type", Tag.inserting(Component.text(area.getType().name())))
766                                .tag("area_terrain", Tag.inserting(Component.text(area.getTerrain().name())))
767                                .build();
768                        caption.set(TranslatableCaption.of("info.area_list_item"));
769                        caption.setTagResolvers(resolver);
770                    }
771                }, "/plot area list", TranslatableCaption.of("list.area_list_header_paged"));
772                return true;
773            }
774            case "regen", "clear", "reset", "regenerate" -> {
775                if (!player.hasPermission(Permission.PERMISSION_AREA_REGEN)) {
776                    player.sendMessage(
777                            TranslatableCaption.of("permission.no_permission"),
778                            TagResolver.resolver(
779                                    "node",
780                                    Tag.inserting(Permission.PERMISSION_AREA_REGEN)
781                            )
782                    );
783                    return false;
784                }
785                final PlotArea area = player.getApplicablePlotArea();
786                if (area == null) {
787                    player.sendMessage(TranslatableCaption.of("errors.not_in_plot_world"));
788                    return false;
789                }
790                if (area.getType() != PlotAreaType.PARTIAL) {
791                    player.sendMessage(
792                            TranslatableCaption.of("single.delete_world_region"),
793                            TagResolver.resolver("world", Tag.inserting(Component.text(area.getWorldName())))
794                    );
795                    return false;
796                }
797                QueueCoordinator queue = blockQueue.getNewQueue(worldUtil.getWeWorld(area.getWorldName()));
798                queue.setChunkConsumer(chunk -> AugmentedUtils.generateChunk(
799                        area.getWorldName(),
800                        chunk.getX(),
801                        chunk.getZ(),
802                        null
803                ));
804                queue.addReadChunks(area.getRegion().getChunks());
805                queue.setCompleteTask(() -> player.sendMessage(TranslatableCaption.of("single.regeneration_complete")));
806                queue.enqueue();
807                return true;
808            }
809            case "goto", "v", "teleport", "visit", "tp" -> {
810                if (!player.hasPermission(Permission.PERMISSION_AREA_TP)) {
811                    player.sendMessage(
812                            TranslatableCaption.of("permission.no_permission"),
813                            TagResolver.resolver("node", Tag.inserting(Permission.PERMISSION_AREA_TP))
814                    );
815                    return false;
816                }
817                if (args.length != 2) {
818                    player.sendMessage(
819                            TranslatableCaption.of("commandconfig.command_syntax"),
820                            TagResolver.resolver("value", Tag.inserting(Component.text("/plot area tp [area]")))
821                    );
822                    return false;
823                }
824                PlotArea area = this.plotAreaManager.getPlotAreaByString(args[1]);
825                if (area == null) {
826                    player.sendMessage(
827                            TranslatableCaption.of("errors.not_valid_plot_world"),
828                            TagResolver.resolver("value", Tag.inserting(Component.text(args[1])))
829                    );
830                    return false;
831                }
832                Location center;
833                if (area instanceof SinglePlotArea) {
834                    ((SinglePlotArea) area).loadWorld(PlotId.of(0, 0));
835                    center = this.worldUtil.getSpawn(PlotId.of(0, 0).toUnderscoreSeparatedString());
836                    player.teleport(center, TeleportCause.COMMAND_AREA_TELEPORT);
837                } else if (area.getType() != PlotAreaType.PARTIAL) {
838                    center = this.worldUtil.getSpawn(area.getWorldName());
839                    player.teleport(center, TeleportCause.COMMAND_AREA_TELEPORT);
840                } else {
841                    CuboidRegion region = area.getRegion();
842                    center = Location.at(area.getWorldName(),
843                            region.getMinimumPoint().getX() + (region.getMaximumPoint().getX() - region
844                                    .getMinimumPoint()
845                                    .getX()) / 2, 0,
846                            region.getMinimumPoint().getZ() + (region.getMaximumPoint().getZ() - region
847                                    .getMinimumPoint()
848                                    .getZ()) / 2
849                    );
850                    this.worldUtil.getHighestBlock(area.getWorldName(), center.getX(), center.getZ(),
851                            y -> player.teleport(center.withY(1 + y), TeleportCause.COMMAND_AREA_TELEPORT)
852                    );
853                }
854                return true;
855            }
856            case "delete", "remove" -> {
857                player.sendMessage(TranslatableCaption.of("single.worldcreation_location"));
858                return true;
859            }
860        }
861        sendUsage(player);
862        return false;
863    }
864
865    @Override
866    public Collection<Command> tab(final PlotPlayer<?> player, final String[] args, final boolean space) {
867        if (args.length == 1) {
868            final List<String> completions = new LinkedList<>();
869            if (player.hasPermission(Permission.PERMISSION_AREA_CREATE)) {
870                completions.add("create");
871            }
872            if (player.hasPermission(Permission.PERMISSION_AREA_CREATE)) {
873                completions.add("single");
874            }
875            if (player.hasPermission(Permission.PERMISSION_AREA_LIST)) {
876                completions.add("list");
877            }
878            if (player.hasPermission(Permission.PERMISSION_AREA_INFO)) {
879                completions.add("info");
880            }
881            if (player.hasPermission(Permission.PERMISSION_AREA_TP)) {
882                completions.add("tp");
883            }
884            final List<Command> commands = completions.stream().filter(completion -> completion
885                            .toLowerCase()
886                            .startsWith(args[0].toLowerCase()))
887                    .map(completion -> new Command(
888                            null,
889                            true,
890                            completion,
891                            "",
892                            RequiredType.NONE,
893                            CommandCategory.ADMINISTRATION
894                    ) {
895                    }).collect(Collectors.toCollection(LinkedList::new));
896            if (player.hasPermission(Permission.PERMISSION_AREA) && args[0].length() > 0) {
897                commands.addAll(TabCompletions.completePlayers(player, args[0], Collections.emptyList()));
898            }
899            return commands;
900        }
901        return TabCompletions.completePlayers(player, String.join(",", args).trim(), Collections.emptyList());
902    }
903
904}