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