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.common.collect.Lists;
022import com.google.inject.Inject;
023import com.plotsquared.core.configuration.Settings;
024import com.plotsquared.core.configuration.caption.TranslatableCaption;
025import com.plotsquared.core.location.Location;
026import com.plotsquared.core.permissions.Permission;
027import com.plotsquared.core.player.ConsolePlayer;
028import com.plotsquared.core.player.PlotPlayer;
029import com.plotsquared.core.plot.Plot;
030import com.plotsquared.core.plot.PlotArea;
031import com.plotsquared.core.plot.schematic.Schematic;
032import com.plotsquared.core.plot.world.PlotAreaManager;
033import com.plotsquared.core.util.SchematicHandler;
034import com.plotsquared.core.util.StringMan;
035import com.plotsquared.core.util.TabCompletions;
036import com.plotsquared.core.util.task.RunnableVal;
037import com.plotsquared.core.util.task.TaskManager;
038import net.kyori.adventure.text.Component;
039import net.kyori.adventure.text.minimessage.tag.Tag;
040import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
041import org.checkerframework.checker.nullness.qual.NonNull;
042
043import java.net.URL;
044import java.util.ArrayList;
045import java.util.Collection;
046import java.util.Collections;
047import java.util.LinkedList;
048import java.util.List;
049import java.util.UUID;
050import java.util.stream.Collectors;
051
052@CommandDeclaration(command = "schematic",
053        permission = "plots.schematic",
054        aliases = "schem",
055        category = CommandCategory.SCHEMATIC,
056        usage = "/plot schematic <save | saveall | paste | list>")
057public class SchematicCmd extends SubCommand {
058
059    private final PlotAreaManager plotAreaManager;
060    private final SchematicHandler schematicHandler;
061    private boolean running = false;
062
063    @Inject
064    public SchematicCmd(
065            final @NonNull PlotAreaManager plotAreaManager,
066            final @NonNull SchematicHandler schematicHandler
067    ) {
068        this.plotAreaManager = plotAreaManager;
069        this.schematicHandler = schematicHandler;
070    }
071
072    @Override
073    public boolean onCommand(final PlotPlayer<?> player, String[] args) {
074        if (args.length < 1) {
075            player.sendMessage(
076                    TranslatableCaption.of("commandconfig.command_syntax"),
077                    TagResolver.resolver("value", Tag.inserting(Component.text("Possible values: save, paste, exportall, list")))
078            );
079            return true;
080        }
081        String arg = args[0].toLowerCase();
082        switch (arg) {
083            case "paste" -> {
084                if (!player.hasPermission(Permission.PERMISSION_SCHEMATIC_PASTE)) {
085                    player.sendMessage(
086                            TranslatableCaption.of("permission.no_permission"),
087                            TagResolver.resolver(
088                                    "node",
089                                    Tag.inserting(Permission.PERMISSION_SCHEMATIC_PASTE)
090                            )
091                    );
092                    return false;
093                }
094                if (args.length < 2) {
095                    player.sendMessage(
096                            TranslatableCaption.of("commandconfig.command_syntax"),
097                            TagResolver.resolver(
098                                    "value",
099                                    Tag.inserting(Component.text("Possible values: save, paste, exportall, list"))
100                            )
101                    );
102                    break;
103                }
104                Location loc = player.getLocation();
105                final Plot plot = loc.getPlotAbs();
106                if (plot == null) {
107                    player.sendMessage(TranslatableCaption.of("errors.not_in_plot"));
108                    return false;
109                }
110                if (!plot.hasOwner()) {
111                    player.sendMessage(TranslatableCaption.of("info.plot_unowned"));
112                    return false;
113                }
114                if (!plot.isOwner(player.getUUID()) && !player.hasPermission("plots.admin.command.schematic.paste")) {
115                    player.sendMessage(TranslatableCaption.of("permission.no_plot_perms"));
116                    return false;
117                }
118                if (plot.getVolume() > Integer.MAX_VALUE) {
119                    player.sendMessage(TranslatableCaption.of("schematics.schematic_too_large"));
120                    return false;
121                }
122                if (this.running) {
123                    player.sendMessage(TranslatableCaption.of("error.task_in_process"));
124                    return false;
125                }
126                final String location = args[1];
127                this.running = true;
128                TaskManager.runTaskAsync(() -> {
129                    Schematic schematic = null;
130                    if (location.startsWith("url:")) {
131                        try {
132                            UUID uuid = UUID.fromString(location.substring(4));
133                            URL base = new URL(Settings.Web.URL);
134                            URL url = new URL(base, "uploads/" + uuid + ".schematic");
135                            schematic = this.schematicHandler.getSchematic(url);
136                        } catch (Exception e) {
137                            e.printStackTrace();
138                            player.sendMessage(
139                                    TranslatableCaption.of("schematics.schematic_invalid"),
140                                    TagResolver.resolver(
141                                            "reason",
142                                            Tag.inserting(Component.text("non-existent url: " + location))
143                                    )
144                            );
145                            SchematicCmd.this.running = false;
146                            return;
147                        }
148                    } else {
149                        try {
150                            schematic = this.schematicHandler.getSchematic(location);
151                        } catch (SchematicHandler.UnsupportedFormatException e) {
152                            e.printStackTrace();
153                        }
154                    }
155                    if (schematic == null) {
156                        SchematicCmd.this.running = false;
157                        player.sendMessage(
158                                TranslatableCaption.of("schematics.schematic_invalid"),
159                                TagResolver.resolver(
160                                        "reason",
161                                        Tag.inserting(Component.text("non-existent or not in gzip format"))
162                                )
163                        );
164                        return;
165                    }
166                    this.schematicHandler.paste(
167                            schematic,
168                            plot,
169                            0,
170                            plot.getArea().getMinBuildHeight(),
171                            0,
172                            false,
173                            player,
174                            new RunnableVal<>() {
175                                @Override
176                                public void run(Boolean value) {
177                                    SchematicCmd.this.running = false;
178                                    if (value) {
179                                        player.sendMessage(TranslatableCaption.of("schematics.schematic_paste_success"));
180                                    } else {
181                                        player.sendMessage(TranslatableCaption.of("schematics.schematic_paste_failed"));
182                                    }
183                                }
184                            }
185                    );
186                });
187            }
188            case "saveall", "exportall" -> {
189                if (!(player instanceof ConsolePlayer)) {
190                    player.sendMessage(TranslatableCaption.of("console.not_console"));
191                    return false;
192                }
193                if (args.length != 2) {
194                    player.sendMessage(TranslatableCaption.of("schematics.schematic_exportall_world_args"));
195                    player.sendMessage(
196                            TranslatableCaption.of("commandconfig.command_syntax"),
197                            TagResolver.resolver(
198                                    "value",
199                                    Tag.inserting(Component.text("Use /plot schematic exportall <area>"))
200                            )
201                    );
202                    return false;
203                }
204                PlotArea area = this.plotAreaManager.getPlotAreaByString(args[1]);
205                if (area == null) {
206                    player.sendMessage(
207                            TranslatableCaption.of("errors.not_valid_plot_world"),
208                            TagResolver.resolver("value", Tag.inserting(Component.text(args[1])))
209                    );
210                    return false;
211                }
212                Collection<Plot> plots = area.getPlots();
213                if (plots.isEmpty()) {
214                    player.sendMessage(TranslatableCaption.of("schematic.schematic_exportall_world"));
215                    player.sendMessage(
216                            TranslatableCaption.of("commandconfig.command_syntax"),
217                            TagResolver.resolver("value", Tag.inserting(Component.text("Use /plot sch exportall <area>")))
218                    );
219                    return false;
220                }
221                boolean result = this.schematicHandler.exportAll(plots, null, null,
222                        () -> player.sendMessage(TranslatableCaption.of("schematics.schematic_exportall_finished"))
223                );
224                if (!result) {
225                    player.sendMessage(TranslatableCaption.of("error.task_in_process"));
226                    return false;
227                } else {
228                    player.sendMessage(TranslatableCaption.of("schematics.schematic_exportall_started"));
229                    player.sendMessage(
230                            TranslatableCaption.of("schematics.plot_to_schem"),
231                            TagResolver.resolver("amount", Tag.inserting(Component.text(plots.size())))
232                    );
233                }
234            }
235            case "export", "save" -> {
236                if (!player.hasPermission(Permission.PERMISSION_SCHEMATIC_SAVE)) {
237                    player.sendMessage(
238                            TranslatableCaption.of("permission.no_permission"),
239                            TagResolver.resolver(
240                                    "node",
241                                    Tag.inserting(Permission.PERMISSION_SCHEMATIC_SAVE)
242                            )
243                    );
244                    return false;
245                }
246                if (this.running) {
247                    player.sendMessage(TranslatableCaption.of("error.task_in_process"));
248                    return false;
249                }
250                Location location = player.getLocation();
251                Plot plot = location.getPlotAbs();
252                if (plot == null) {
253                    player.sendMessage(TranslatableCaption.of("errors.not_in_plot"));
254                    return false;
255                }
256                if (!plot.hasOwner()) {
257                    player.sendMessage(TranslatableCaption.of("info.plot_unowned"));
258                    return false;
259                }
260                if (plot.getVolume() > Integer.MAX_VALUE) {
261                    player.sendMessage(TranslatableCaption.of("schematics.schematic_too_large"));
262                    return false;
263                }
264                if (!plot.isOwner(player.getUUID()) && !player.hasPermission("plots.admin.command.schematic.save")) {
265                    player.sendMessage(TranslatableCaption.of("permission.no_plot_perms"));
266                    return false;
267                }
268                ArrayList<Plot> plots = Lists.newArrayList(plot);
269                boolean result = this.schematicHandler.exportAll(plots, null, null, () -> {
270                    player.sendMessage(TranslatableCaption.of("schematics.schematic_exportall_single_finished"));
271                    SchematicCmd.this.running = false;
272                });
273                if (!result) {
274                    player.sendMessage(TranslatableCaption.of("error.task_in_process"));
275                    return false;
276                } else {
277                    player.sendMessage(TranslatableCaption.of("schematics.schematic_exportall_started"));
278                }
279            }
280            case "list" -> {
281                if (!player.hasPermission(Permission.PERMISSION_SCHEMATIC_LIST)) {
282                    player.sendMessage(
283                            TranslatableCaption.of("permission.no_permission"),
284                            TagResolver.resolver(
285                                    "node",
286                                    Tag.inserting(Permission.PERMISSION_SCHEMATIC_LIST)
287                            )
288                    );
289                    return false;
290                }
291                final String string = StringMan.join(this.schematicHandler.getSchematicNames(), "$2, $1");
292                player.sendMessage(
293                        TranslatableCaption.of("schematics.schematic_list"),
294                        TagResolver.resolver("list", Tag.inserting(Component.text(string)))
295                );
296            }
297            default -> player.sendMessage(
298                    TranslatableCaption.of("commandconfig.command_syntax"),
299                    TagResolver.resolver("value", Tag.inserting(Component.text("Possible values: save, paste, exportall, list")))
300            );
301        }
302        return true;
303    }
304
305    @Override
306    public Collection<Command> tab(final PlotPlayer<?> player, final String[] args, final boolean space) {
307        if (args.length == 1) {
308            final List<String> completions = new LinkedList<>();
309            if (player.hasPermission(Permission.PERMISSION_SCHEMATIC_SAVE)) {
310                completions.add("save");
311            }
312            if (player.hasPermission(Permission.PERMISSION_SCHEMATIC_LIST)) {
313                completions.add("list");
314            }
315            if (player.hasPermission(Permission.PERMISSION_SCHEMATIC_PASTE)) {
316                completions.add("paste");
317            }
318            final List<Command> commands = completions.stream().filter(completion -> completion
319                            .toLowerCase()
320                            .startsWith(args[0].toLowerCase()))
321                    .map(completion -> new Command(
322                            null,
323                            true,
324                            completion,
325                            "",
326                            RequiredType.NONE,
327                            CommandCategory.ADMINISTRATION
328                    ) {
329                    }).collect(Collectors.toCollection(LinkedList::new));
330            if (player.hasPermission(Permission.PERMISSION_SCHEMATIC) && args[0].length() > 0) {
331                commands.addAll(TabCompletions.completePlayers(player, args[0], Collections.emptyList()));
332            }
333            return commands;
334        }
335        return TabCompletions.completePlayers(player, String.join(",", args).trim(), Collections.emptyList());
336    }
337
338}