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