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.Settings;
024import com.plotsquared.core.configuration.caption.Caption;
025import com.plotsquared.core.configuration.caption.CaptionHolder;
026import com.plotsquared.core.configuration.caption.Templates;
027import com.plotsquared.core.configuration.caption.TranslatableCaption;
028import com.plotsquared.core.database.DBFunc;
029import com.plotsquared.core.permissions.Permission;
030import com.plotsquared.core.player.PlotPlayer;
031import com.plotsquared.core.plot.Plot;
032import com.plotsquared.core.plot.PlotArea;
033import com.plotsquared.core.plot.flag.implementations.DoneFlag;
034import com.plotsquared.core.plot.flag.implementations.PriceFlag;
035import com.plotsquared.core.plot.flag.implementations.ServerPlotFlag;
036import com.plotsquared.core.plot.world.PlotAreaManager;
037import com.plotsquared.core.util.EconHandler;
038import com.plotsquared.core.util.MathMan;
039import com.plotsquared.core.util.PlayerManager;
040import com.plotsquared.core.util.StringComparison;
041import com.plotsquared.core.util.StringMan;
042import com.plotsquared.core.util.TabCompletions;
043import com.plotsquared.core.util.query.PlotQuery;
044import com.plotsquared.core.util.query.SortingStrategy;
045import com.plotsquared.core.util.task.RunnableVal3;
046import com.plotsquared.core.uuid.UUIDMapping;
047import net.kyori.adventure.text.Component;
048import net.kyori.adventure.text.TextComponent;
049import net.kyori.adventure.text.minimessage.Template;
050import org.checkerframework.checker.nullness.qual.NonNull;
051
052import java.util.ArrayList;
053import java.util.Arrays;
054import java.util.Collection;
055import java.util.Collections;
056import java.util.Iterator;
057import java.util.LinkedList;
058import java.util.List;
059import java.util.UUID;
060import java.util.concurrent.ExecutionException;
061import java.util.concurrent.TimeUnit;
062import java.util.concurrent.TimeoutException;
063import java.util.function.Consumer;
064import java.util.stream.Collectors;
065
066@CommandDeclaration(command = "list",
067        aliases = {"l", "find", "search"},
068        permission = "plots.list",
069        category = CommandCategory.INFO,
070        usage = "/plot list <forsale | mine | shared | world | top | all | unowned | player | world | done | fuzzy <search...>> [#]")
071public class ListCmd extends SubCommand {
072
073    private final PlotAreaManager plotAreaManager;
074    private final EconHandler econHandler;
075
076    @Inject
077    public ListCmd(final @NonNull PlotAreaManager plotAreaManager, final @NonNull EconHandler econHandler) {
078        this.plotAreaManager = plotAreaManager;
079        this.econHandler = econHandler;
080    }
081
082    private String[] getArgumentList(PlotPlayer<?> player) {
083        List<String> args = new ArrayList<>();
084        if (this.econHandler != null && player.hasPermission(Permission.PERMISSION_LIST_FOR_SALE)) {
085            args.add("forsale");
086        }
087        if (player.hasPermission(Permission.PERMISSION_LIST_MINE)) {
088            args.add("mine");
089        }
090        if (player.hasPermission(Permission.PERMISSION_LIST_SHARED)) {
091            args.add("shared");
092        }
093        if (player.hasPermission(Permission.PERMISSION_LIST_WORLD)) {
094            args.add("world");
095        }
096        if (player.hasPermission(Permission.PERMISSION_LIST_TOP)) {
097            args.add("top");
098        }
099        if (player.hasPermission(Permission.PERMISSION_LIST_ALL)) {
100            args.add("all");
101        }
102        if (player.hasPermission(Permission.PERMISSION_LIST_UNOWNED)) {
103            args.add("unowned");
104        }
105        if (player.hasPermission(Permission.PERMISSION_LIST_PLAYER)) {
106            args.add("<player>");
107        }
108        if (player.hasPermission(Permission.PERMISSION_LIST_WORLD)) {
109            args.add("<world>");
110        }
111        if (player.hasPermission(Permission.PERMISSION_LIST_DONE)) {
112            args.add("done");
113        }
114        if (player.hasPermission(Permission.PERMISSION_LIST_EXPIRED)) {
115            args.add("expired");
116        }
117        if (player.hasPermission(Permission.PERMISSION_LIST_FUZZY)) {
118            args.add("fuzzy <search...>");
119        }
120        return args.toArray(new String[args.size()]);
121    }
122
123    public void noArgs(PlotPlayer<?> player) {
124        player.sendMessage(
125                TranslatableCaption.of("commandconfig.subcommand_set_options_header"),
126                Templates.of("values", Arrays.toString(getArgumentList(player)))
127        );
128    }
129
130    @Override
131    public boolean onCommand(PlotPlayer<?> player, String[] args) {
132        if (args.length < 1) {
133            noArgs(player);
134            return false;
135        }
136
137        final int page;
138        if (args.length > 1) {
139            int tempPage = -1;
140            try {
141                tempPage = Integer.parseInt(args[args.length - 1]);
142                --tempPage;
143                if (tempPage < 0) {
144                    tempPage = 0;
145                }
146            } catch (NumberFormatException ignored) {
147            }
148            page = tempPage;
149        } else {
150            page = 0;
151        }
152
153        String world = player.getLocation().getWorldName();
154        PlotArea area = player.getApplicablePlotArea();
155        String arg = args[0].toLowerCase();
156        final boolean[] sort = new boolean[]{true};
157
158        final Consumer<PlotQuery> plotConsumer = query -> {
159            if (query == null) {
160                player.sendMessage(
161                        TranslatableCaption.of("commandconfig.did_you_mean"),
162                        Template.of(
163                                "value",
164                                new StringComparison<>(args[0], new String[]{"mine", "shared", "world", "all"}).getBestMatch()
165                        )
166                );
167                return;
168            }
169
170            if (area != null) {
171                query.relativeToArea(area);
172            }
173
174            if (sort[0]) {
175                query.withSortingStrategy(SortingStrategy.SORT_BY_CREATION);
176            }
177
178            final List<Plot> plots = query.asList();
179
180            if (plots.isEmpty()) {
181                player.sendMessage(TranslatableCaption.of("invalid.found_no_plots"));
182                return;
183            }
184            displayPlots(player, plots, 12, page, args);
185        };
186
187        switch (arg) {
188            case "mine" -> {
189                if (!player.hasPermission(Permission.PERMISSION_LIST_MINE)) {
190                    player.sendMessage(
191                            TranslatableCaption.of("permission.no_permission"),
192                            Templates.of("node", "plots.list.mine")
193                    );
194                    return false;
195                }
196                sort[0] = false;
197                plotConsumer.accept(PlotQuery
198                        .newQuery()
199                        .ownersInclude(player)
200                        .whereBasePlot()
201                        .withSortingStrategy(SortingStrategy.SORT_BY_TEMP));
202            }
203            case "shared" -> {
204                if (!player.hasPermission(Permission.PERMISSION_LIST_SHARED)) {
205                    player.sendMessage(
206                            TranslatableCaption.of("permission.no_permission"),
207                            Templates.of("node", "plots.list.shared")
208                    );
209                    return false;
210                }
211                plotConsumer.accept(PlotQuery
212                        .newQuery()
213                        .withMember(player.getUUID())
214                        .thatPasses(plot -> !plot.isOwnerAbs(player.getUUID())));
215            }
216            case "world" -> {
217                if (!player.hasPermission(Permission.PERMISSION_LIST_WORLD)) {
218                    player.sendMessage(
219                            TranslatableCaption.of("permission.no_permission"),
220                            Templates.of("node", "plots.list.world")
221                    );
222                    return false;
223                }
224                if (!player.hasPermission("plots.list.world." + world)) {
225                    player.sendMessage(
226                            TranslatableCaption.of("permission.no_permission"),
227                            Templates.of("node", "plots.list.world." + world)
228                    );
229                    return false;
230                }
231                plotConsumer.accept(PlotQuery.newQuery().inWorld(world));
232            }
233            case "expired" -> {
234                if (!player.hasPermission(Permission.PERMISSION_LIST_EXPIRED)) {
235                    player.sendMessage(
236                            TranslatableCaption.of("permission.no_permission"),
237                            Templates.of("node", "plots.list.expired")
238                    );
239                    return false;
240                }
241                if (PlotSquared.platform().expireManager() == null) {
242                    plotConsumer.accept(PlotQuery.newQuery().noPlots());
243                } else {
244                    plotConsumer.accept(PlotQuery.newQuery().expiredPlots());
245                }
246            }
247            case "area" -> {
248                if (!player.hasPermission(Permission.PERMISSION_LIST_AREA)) {
249                    player.sendMessage(
250                            TranslatableCaption.of("permission.no_permission"),
251                            Templates.of("node", "plots.list.area")
252                    );
253                    return false;
254                }
255                if (!player.hasPermission("plots.list.world." + world)) {
256                    player.sendMessage(
257                            TranslatableCaption.of("permission.no_permission"),
258                            Templates.of("node", "plots.list.world." + world)
259                    );
260                    return false;
261                }
262                if (area == null) {
263                    plotConsumer.accept(PlotQuery.newQuery().noPlots());
264                } else {
265                    plotConsumer.accept(PlotQuery.newQuery().inArea(area));
266                }
267            }
268            case "all" -> {
269                if (!player.hasPermission(Permission.PERMISSION_LIST_ALL)) {
270                    player.sendMessage(
271                            TranslatableCaption.of("permission.no_permission"),
272                            Templates.of("node", "plots.list.all")
273                    );
274                    return false;
275                }
276                plotConsumer.accept(PlotQuery.newQuery().allPlots());
277            }
278            case "done" -> {
279                if (!player.hasPermission(Permission.PERMISSION_LIST_DONE)) {
280                    player.sendMessage(
281                            TranslatableCaption.of("permission.no_permission"),
282                            Templates.of("node", "plots.list.done")
283                    );
284                    return false;
285                }
286                sort[0] = false;
287                plotConsumer.accept(PlotQuery
288                        .newQuery()
289                        .allPlots()
290                        .thatPasses(DoneFlag::isDone)
291                        .withSortingStrategy(SortingStrategy.SORT_BY_DONE));
292            }
293            case "top" -> {
294                if (!player.hasPermission(Permission.PERMISSION_LIST_TOP)) {
295                    player.sendMessage(
296                            TranslatableCaption.of("permission.no_permission"),
297                            Templates.of("node", "plots.list.top")
298                    );
299                    return false;
300                }
301                sort[0] = false;
302                plotConsumer.accept(PlotQuery.newQuery().allPlots().withSortingStrategy(SortingStrategy.SORT_BY_RATING));
303            }
304            case "forsale" -> {
305                if (!player.hasPermission(Permission.PERMISSION_LIST_FOR_SALE)) {
306                    player.sendMessage(
307                            TranslatableCaption.of("permission.no_permission"),
308                            Templates.of("node", "plots.list.forsale")
309                    );
310                    return false;
311                }
312                if (this.econHandler.isSupported()) {
313                    break;
314                }
315                plotConsumer.accept(PlotQuery.newQuery().allPlots().thatPasses(plot -> plot.getFlag(PriceFlag.class) > 0));
316            }
317            case "unowned" -> {
318                if (!player.hasPermission(Permission.PERMISSION_LIST_UNOWNED)) {
319                    player.sendMessage(
320                            TranslatableCaption.of("permission.no_permission"),
321                            Templates.of("node", "plots.list.unowned")
322                    );
323                    return false;
324                }
325                plotConsumer.accept(PlotQuery.newQuery().allPlots().thatPasses(plot -> plot.getOwner() == null));
326            }
327            case "fuzzy" -> {
328                if (!player.hasPermission(Permission.PERMISSION_LIST_FUZZY)) {
329                    player.sendMessage(
330                            TranslatableCaption.of("permission.no_permission"),
331                            Templates.of("node", "plots.list.fuzzy")
332                    );
333                    return false;
334                }
335                if (args.length < (page == -1 ? 2 : 3)) {
336                    player.sendMessage(
337                            TranslatableCaption.of("commandconfig.command_syntax"),
338                            Templates.of("value", "/plot list fuzzy <search...> [#]")
339                    );
340                    return false;
341                }
342                String term;
343                if (MathMan.isInteger(args[args.length - 1])) {
344                    term = StringMan.join(Arrays.copyOfRange(args, 1, args.length - 1), " ");
345                } else {
346                    term = StringMan.join(Arrays.copyOfRange(args, 1, args.length), " ");
347                }
348                sort[0] = false;
349                plotConsumer.accept(PlotQuery.newQuery().plotsBySearch(term));
350            }
351            default -> {
352                if (this.plotAreaManager.hasPlotArea(args[0])) {
353                    if (!player.hasPermission(Permission.PERMISSION_LIST_WORLD)) {
354                        player.sendMessage(
355                                TranslatableCaption.of("permission.no_permission"),
356                                Templates.of("node", "plots.list.world")
357                        );
358                        return false;
359                    }
360                    if (!player.hasPermission("plots.list.world." + args[0])) {
361                        player.sendMessage(
362                                TranslatableCaption.of("permission.no_permission"),
363                                Templates.of("node", "plots.list.world." + args[0])
364                        );
365                        return false;
366                    }
367                    plotConsumer.accept(PlotQuery.newQuery().inWorld(args[0]));
368                    break;
369                }
370                PlotSquared.get().getImpromptuUUIDPipeline().getSingle(args[0], (uuid, throwable) -> {
371                    if (throwable instanceof TimeoutException) {
372                        player.sendMessage(TranslatableCaption.of("players.fetching_players_timeout"));
373                    } else if (throwable != null) {
374                        if (uuid == null) {
375                            try {
376                                uuid = UUID.fromString(args[0]);
377                            } catch (Exception ignored) {
378                            }
379                        }
380                    }
381                    if (uuid == null) {
382                        player.sendMessage(TranslatableCaption.of("errors.invalid_player"), Templates.of("value", args[0]));
383                    } else {
384                        if (!player.hasPermission(Permission.PERMISSION_LIST_PLAYER)) {
385                            player.sendMessage(
386                                    TranslatableCaption.of("permission.no_permission"),
387                                    Templates.of("node", "plots.list.player")
388                            );
389                        } else {
390                            sort[0] = false;
391                            plotConsumer.accept(PlotQuery
392                                    .newQuery()
393                                    .ownersInclude(uuid)
394                                    .whereBasePlot()
395                                    .withSortingStrategy(SortingStrategy.SORT_BY_TEMP));
396                        }
397                    }
398                });
399            }
400        }
401
402        return true;
403    }
404
405    public void displayPlots(final PlotPlayer<?> player, List<Plot> plots, int pageSize, int page, String[] args) {
406        // Header
407        plots.removeIf(plot -> !plot.isBasePlot());
408        this.paginate(player, plots, pageSize, page, new RunnableVal3<>() {
409            @Override
410            public void run(Integer i, Plot plot, CaptionHolder caption) {
411                Caption color;
412                if (plot.getOwner() == null) {
413                    color = TranslatableCaption.of("info.plot_list_no_owner");
414                } else if (plot.isOwner(player.getUUID()) || plot.getOwner().equals(DBFunc.EVERYONE)) {
415                    color = TranslatableCaption.of("info.plot_list_owned_by");
416                } else if (plot.isAdded(player.getUUID())) {
417                    color = TranslatableCaption.of("info.plot_list_added_to");
418                } else if (plot.isDenied(player.getUUID())) {
419                    color = TranslatableCaption.of("info.plot_list_denied_on");
420                } else {
421                    color = TranslatableCaption.of("info.plot_list_default");
422                }
423                Component trusted = MINI_MESSAGE.parse(
424                        TranslatableCaption.of("info.plot_info_trusted").getComponent(player),
425                        Template.of("trusted", PlayerManager.getPlayerList(plot.getTrusted(), player))
426                );
427                Component members = MINI_MESSAGE.parse(
428                        TranslatableCaption.of("info.plot_info_members").getComponent(player),
429                        Template.of("members", PlayerManager.getPlayerList(plot.getMembers(), player))
430                );
431                Template command_tp = Template.of("command_tp", "/plot visit " + plot.getArea() + ";" + plot.getId());
432                Template command_info = Template.of("command_info", "/plot info " + plot.getArea() + ";" + plot.getId());
433                Template hover_info =
434                        Template.of(
435                                "hover_info",
436                                MINI_MESSAGE.serialize(Component
437                                        .text()
438                                        .append(trusted)
439                                        .append(Component.newline())
440                                        .append(members)
441                                        .asComponent())
442                        );
443                Template numberTemplate = Template.of("number", String.valueOf(i));
444                Template plotTemplate = Template.of(
445                        "plot",
446                        MINI_MESSAGE.parse(color.getComponent(player), Template.of("plot", plot.toString()))
447                );
448
449                String prefix = "";
450                String online = TranslatableCaption.of("info.plot_list_player_online").getComponent(player);
451                String offline = TranslatableCaption.of("info.plot_list_player_offline").getComponent(player);
452                String unknown = TranslatableCaption.of("info.plot_list_player_unknown").getComponent(player);
453                String server = TranslatableCaption.of("info.plot_list_player_server").getComponent(player);
454                String everyone = TranslatableCaption.of("info.plot_list_player_everyone").getComponent(player);
455                TextComponent.Builder builder = Component.text();
456                if (plot.getFlag(ServerPlotFlag.class)) {
457                    Template serverTemplate = Template.of(
458                            "info.server",
459                            TranslatableCaption.of("info.server").getComponent(player)
460                    );
461                    builder.append(MINI_MESSAGE.parse(server, serverTemplate));
462                } else {
463                    try {
464                        final List<UUIDMapping> names = PlotSquared.get().getImpromptuUUIDPipeline().getNames(plot.getOwners())
465                                .get(Settings.UUID.BLOCKING_TIMEOUT, TimeUnit.MILLISECONDS);
466                        for (final UUIDMapping uuidMapping : names) {
467                            PlotPlayer<?> pp = PlotSquared.platform().playerManager().getPlayerIfExists(uuidMapping.getUuid());
468                            Template prefixTemplate = Template.of("prefix", prefix);
469                            Template playerTemplate = Template.of("player", uuidMapping.getUsername());
470                            if (pp != null) {
471                                builder.append(MINI_MESSAGE.parse(online, prefixTemplate, playerTemplate));
472                            } else if (uuidMapping.getUsername().equalsIgnoreCase("unknown")) {
473                                Template unknownTemplate = Template.of(
474                                        "info.unknown",
475                                        TranslatableCaption.of("info.unknown").getComponent(player)
476                                );
477                                builder.append(MINI_MESSAGE.parse(unknown, unknownTemplate));
478                            } else if (uuidMapping.getUuid().equals(DBFunc.EVERYONE)) {
479                                Template everyoneTemplate = Template.of(
480                                        "info.everyone",
481                                        TranslatableCaption.of("info.everyone").getComponent(player)
482                                );
483                                builder.append(MINI_MESSAGE.parse(everyone, everyoneTemplate));
484                            } else {
485                                builder.append(MINI_MESSAGE.parse(offline, prefixTemplate, playerTemplate));
486                            }
487                            prefix = ", ";
488                        }
489                    } catch (InterruptedException | ExecutionException e) {
490                        final StringBuilder playerBuilder = new StringBuilder();
491                        final Iterator<UUID> uuidIterator = plot.getOwners().iterator();
492                        while (uuidIterator.hasNext()) {
493                            final UUID uuid = uuidIterator.next();
494                            playerBuilder.append(uuid);
495                            if (uuidIterator.hasNext()) {
496                                playerBuilder.append(", ");
497                            }
498                        }
499                        player.sendMessage(
500                                TranslatableCaption.of("errors.invalid_player"),
501                                Templates.of("value", playerBuilder.toString())
502                        );
503                    } catch (TimeoutException e) {
504                        player.sendMessage(TranslatableCaption.of("players.fetching_players_timeout"));
505                    }
506                }
507                Template players = Template.of("players", builder.asComponent());
508                caption.set(TranslatableCaption.of("info.plot_list_item"));
509                caption.setTemplates(command_tp, command_info, hover_info, numberTemplate, plotTemplate, players);
510            }
511        }, "/plot list " + args[0], TranslatableCaption.of("list.plot_list_header_paged"));
512    }
513
514    @Override
515    public Collection<Command> tab(PlotPlayer<?> player, String[] args, boolean space) {
516        final List<String> completions = new LinkedList<>();
517        if (this.econHandler.isSupported() && player.hasPermission(Permission.PERMISSION_LIST_FOR_SALE)) {
518            completions.add("forsale");
519        }
520        if (player.hasPermission(Permission.PERMISSION_LIST_MINE)) {
521            completions.add("mine");
522        }
523        if (player.hasPermission(Permission.PERMISSION_LIST_SHARED)) {
524            completions.add("shared");
525        }
526        if (player.hasPermission(Permission.PERMISSION_LIST_WORLD)) {
527            completions.addAll(PlotSquared.platform().worldManager().getWorlds());
528        }
529        if (player.hasPermission(Permission.PERMISSION_LIST_TOP)) {
530            completions.add("top");
531        }
532        if (player.hasPermission(Permission.PERMISSION_LIST_ALL)) {
533            completions.add("all");
534        }
535        if (player.hasPermission(Permission.PERMISSION_LIST_UNOWNED)) {
536            completions.add("unowned");
537        }
538        if (player.hasPermission(Permission.PERMISSION_LIST_DONE)) {
539            completions.add("done");
540        }
541        if (player.hasPermission(Permission.PERMISSION_LIST_EXPIRED)) {
542            completions.add("expired");
543        }
544
545        final List<Command> commands = completions.stream().filter(completion -> completion
546                        .toLowerCase()
547                        .startsWith(args[0].toLowerCase()))
548                .map(completion -> new Command(null, true, completion, "", RequiredType.NONE, CommandCategory.TELEPORT) {
549                }).collect(Collectors.toCollection(LinkedList::new));
550
551        if (player.hasPermission(Permission.PERMISSION_LIST_PLAYER) && args[0].length() > 0) {
552            commands.addAll(TabCompletions.completePlayers(player, args[0], Collections.emptyList()));
553        }
554
555        return commands;
556    }
557
558}