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.TranslatableCaption;
027import com.plotsquared.core.database.DBFunc;
028import com.plotsquared.core.permissions.Permission;
029import com.plotsquared.core.player.PlotPlayer;
030import com.plotsquared.core.plot.Plot;
031import com.plotsquared.core.plot.PlotArea;
032import com.plotsquared.core.plot.flag.implementations.DoneFlag;
033import com.plotsquared.core.plot.flag.implementations.PriceFlag;
034import com.plotsquared.core.plot.flag.implementations.ServerPlotFlag;
035import com.plotsquared.core.plot.world.PlotAreaManager;
036import com.plotsquared.core.util.EconHandler;
037import com.plotsquared.core.util.MathMan;
038import com.plotsquared.core.util.PlayerManager;
039import com.plotsquared.core.util.StringComparison;
040import com.plotsquared.core.util.StringMan;
041import com.plotsquared.core.util.TabCompletions;
042import com.plotsquared.core.util.query.PlotQuery;
043import com.plotsquared.core.util.query.SortingStrategy;
044import com.plotsquared.core.util.task.RunnableVal3;
045import com.plotsquared.core.uuid.UUIDMapping;
046import net.kyori.adventure.text.Component;
047import net.kyori.adventure.text.TextComponent;
048import net.kyori.adventure.text.minimessage.tag.Tag;
049import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
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                TagResolver.resolver("values", Tag.inserting(Component.text(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                        TagResolver.resolver(
163                                "value",
164                                Tag.inserting(Component.text(
165                                        new StringComparison<>(
166                                                args[0],
167                                                new String[]{"mine", "shared", "world", "all"}
168                                        ).getBestMatch()
169                                ))
170                        )
171                );
172                return;
173            }
174
175            if (area != null) {
176                query.relativeToArea(area);
177            }
178
179            if (sort[0]) {
180                query.withSortingStrategy(SortingStrategy.SORT_BY_CREATION);
181            }
182
183            final List<Plot> plots = query.asList();
184
185            if (plots.isEmpty()) {
186                player.sendMessage(TranslatableCaption.of("invalid.found_no_plots"));
187                return;
188            }
189            displayPlots(player, plots, 12, page, args);
190        };
191
192        switch (arg) {
193            case "mine" -> {
194                if (!player.hasPermission(Permission.PERMISSION_LIST_MINE)) {
195                    player.sendMessage(
196                            TranslatableCaption.of("permission.no_permission"),
197                            TagResolver.resolver("node", Tag.inserting(Component.text("plots.list.mine")))
198                    );
199                    return false;
200                }
201                sort[0] = false;
202                plotConsumer.accept(PlotQuery
203                        .newQuery()
204                        .ownersInclude(player)
205                        .whereBasePlot()
206                        .withSortingStrategy(SortingStrategy.SORT_BY_TEMP));
207            }
208            case "shared" -> {
209                if (!player.hasPermission(Permission.PERMISSION_LIST_SHARED)) {
210                    player.sendMessage(
211                            TranslatableCaption.of("permission.no_permission"),
212                            TagResolver.resolver("node", Tag.inserting(Component.text("plots.list.shared")))
213                    );
214                    return false;
215                }
216                plotConsumer.accept(PlotQuery
217                        .newQuery()
218                        .withMember(player.getUUID())
219                        .thatPasses(plot -> !plot.isOwnerAbs(player.getUUID())));
220            }
221            case "world" -> {
222                if (!player.hasPermission(Permission.PERMISSION_LIST_WORLD)) {
223                    player.sendMessage(
224                            TranslatableCaption.of("permission.no_permission"),
225                            TagResolver.resolver("node", Tag.inserting(Component.text("plots.list.world")))
226                    );
227                    return false;
228                }
229                if (!player.hasPermission("plots.list.world." + world)) {
230                    player.sendMessage(
231                            TranslatableCaption.of("permission.no_permission"),
232                            TagResolver.resolver("node", Tag.inserting(Component.text("plots.list.world." + world)))
233                    );
234                    return false;
235                }
236                plotConsumer.accept(PlotQuery.newQuery().inWorld(world));
237            }
238            case "expired" -> {
239                if (!player.hasPermission(Permission.PERMISSION_LIST_EXPIRED)) {
240                    player.sendMessage(
241                            TranslatableCaption.of("permission.no_permission"),
242                            TagResolver.resolver("node", Tag.inserting(Component.text("plots.list.expired")))
243                    );
244                    return false;
245                }
246                if (PlotSquared.platform().expireManager() == null) {
247                    plotConsumer.accept(PlotQuery.newQuery().noPlots());
248                } else {
249                    plotConsumer.accept(PlotQuery.newQuery().expiredPlots());
250                }
251            }
252            case "area" -> {
253                if (!player.hasPermission(Permission.PERMISSION_LIST_AREA)) {
254                    player.sendMessage(
255                            TranslatableCaption.of("permission.no_permission"),
256                            TagResolver.resolver("node", Tag.inserting(Component.text("plots.list.area")))
257                    );
258                    return false;
259                }
260                if (!player.hasPermission("plots.list.world." + world)) {
261                    player.sendMessage(
262                            TranslatableCaption.of("permission.no_permission"),
263                            TagResolver.resolver("node", Tag.inserting(Component.text("plots.list.world." + world)))
264                    );
265                    return false;
266                }
267                if (area == null) {
268                    plotConsumer.accept(PlotQuery.newQuery().noPlots());
269                } else {
270                    plotConsumer.accept(PlotQuery.newQuery().inArea(area));
271                }
272            }
273            case "all" -> {
274                if (!player.hasPermission(Permission.PERMISSION_LIST_ALL)) {
275                    player.sendMessage(
276                            TranslatableCaption.of("permission.no_permission"),
277                            TagResolver.resolver("node", Tag.inserting(Component.text("plots.list.all")))
278                    );
279                    return false;
280                }
281                plotConsumer.accept(PlotQuery.newQuery().allPlots());
282            }
283            case "done" -> {
284                if (!player.hasPermission(Permission.PERMISSION_LIST_DONE)) {
285                    player.sendMessage(
286                            TranslatableCaption.of("permission.no_permission"),
287                            TagResolver.resolver("node", Tag.inserting(Component.text("plots.list.done")))
288                    );
289                    return false;
290                }
291                sort[0] = false;
292                plotConsumer.accept(PlotQuery
293                        .newQuery()
294                        .allPlots()
295                        .thatPasses(DoneFlag::isDone)
296                        .withSortingStrategy(SortingStrategy.SORT_BY_DONE));
297            }
298            case "top" -> {
299                if (!player.hasPermission(Permission.PERMISSION_LIST_TOP)) {
300                    player.sendMessage(
301                            TranslatableCaption.of("permission.no_permission"),
302                            TagResolver.resolver("node", Tag.inserting(Component.text("plots.list.top")))
303                    );
304                    return false;
305                }
306                sort[0] = false;
307                plotConsumer.accept(PlotQuery.newQuery().allPlots().withSortingStrategy(SortingStrategy.SORT_BY_RATING));
308            }
309            case "forsale" -> {
310                if (!player.hasPermission(Permission.PERMISSION_LIST_FOR_SALE)) {
311                    player.sendMessage(
312                            TranslatableCaption.of("permission.no_permission"),
313                            TagResolver.resolver("node", Tag.inserting(Component.text("plots.list.forsale")))
314                    );
315                    return false;
316                }
317                if (this.econHandler.isSupported()) {
318                    break;
319                }
320                plotConsumer.accept(PlotQuery.newQuery().allPlots().thatPasses(plot -> plot.getFlag(PriceFlag.class) > 0));
321            }
322            case "unowned" -> {
323                if (!player.hasPermission(Permission.PERMISSION_LIST_UNOWNED)) {
324                    player.sendMessage(
325                            TranslatableCaption.of("permission.no_permission"),
326                            TagResolver.resolver("node", Tag.inserting(Component.text("plots.list.unowned")))
327                    );
328                    return false;
329                }
330                plotConsumer.accept(PlotQuery.newQuery().allPlots().thatPasses(plot -> plot.getOwner() == null));
331            }
332            case "fuzzy" -> {
333                if (!player.hasPermission(Permission.PERMISSION_LIST_FUZZY)) {
334                    player.sendMessage(
335                            TranslatableCaption.of("permission.no_permission"),
336                            TagResolver.resolver("node", Tag.inserting(Component.text("plots.list.fuzzy")))
337                    );
338                    return false;
339                }
340                if (args.length < (page == -1 ? 2 : 3)) {
341                    player.sendMessage(
342                            TranslatableCaption.of("commandconfig.command_syntax"),
343                            TagResolver.resolver("value", Tag.inserting(Component.text("/plot list fuzzy <search...> [#]")))
344                    );
345                    return false;
346                }
347                String term;
348                if (MathMan.isInteger(args[args.length - 1])) {
349                    term = StringMan.join(Arrays.copyOfRange(args, 1, args.length - 1), " ");
350                } else {
351                    term = StringMan.join(Arrays.copyOfRange(args, 1, args.length), " ");
352                }
353                sort[0] = false;
354                plotConsumer.accept(PlotQuery.newQuery().plotsBySearch(term));
355            }
356            default -> {
357                if (this.plotAreaManager.hasPlotArea(args[0])) {
358                    if (!player.hasPermission(Permission.PERMISSION_LIST_WORLD)) {
359                        player.sendMessage(
360                                TranslatableCaption.of("permission.no_permission"),
361                                TagResolver.resolver("node", Tag.inserting(Component.text("plots.list.world")))
362                        );
363                        return false;
364                    }
365                    if (!player.hasPermission("plots.list.world." + args[0])) {
366                        player.sendMessage(
367                                TranslatableCaption.of("permission.no_permission"),
368                                TagResolver.resolver("node", Tag.inserting(Component.text("plots.list.world." + args[0])))
369                        );
370                        return false;
371                    }
372                    plotConsumer.accept(PlotQuery.newQuery().inWorld(args[0]));
373                    break;
374                }
375                PlotSquared.get().getImpromptuUUIDPipeline().getSingle(args[0], (uuid, throwable) -> {
376                    if (throwable instanceof TimeoutException) {
377                        player.sendMessage(TranslatableCaption.of("players.fetching_players_timeout"));
378                    } else if (throwable != null) {
379                        if (uuid == null) {
380                            try {
381                                uuid = UUID.fromString(args[0]);
382                            } catch (Exception ignored) {
383                            }
384                        }
385                    }
386                    if (uuid == null) {
387                        player.sendMessage(
388                                TranslatableCaption.of("errors.invalid_player"),
389                                TagResolver.resolver("value", Tag.inserting(Component.text(args[0])))
390                        );
391                    } else {
392                        if (!player.hasPermission(Permission.PERMISSION_LIST_PLAYER)) {
393                            player.sendMessage(
394                                    TranslatableCaption.of("permission.no_permission"),
395                                    TagResolver.resolver("node", Tag.inserting(Component.text("plots.list.player")))
396                            );
397                        } else {
398                            sort[0] = false;
399                            plotConsumer.accept(PlotQuery
400                                    .newQuery()
401                                    .ownersInclude(uuid)
402                                    .whereBasePlot()
403                                    .withSortingStrategy(SortingStrategy.SORT_BY_TEMP));
404                        }
405                    }
406                });
407            }
408        }
409
410        return true;
411    }
412
413    public void displayPlots(final PlotPlayer<?> player, List<Plot> plots, int pageSize, int page, String[] args) {
414        // Header
415        plots.removeIf(plot -> !plot.isBasePlot());
416        this.paginate(player, plots, pageSize, page, new RunnableVal3<>() {
417            @Override
418            public void run(Integer i, Plot plot, CaptionHolder caption) {
419                Caption color;
420                if (plot.getOwner() == null) {
421                    color = TranslatableCaption.of("info.plot_list_no_owner");
422                } else if (plot.isOwner(player.getUUID()) || plot.getOwner().equals(DBFunc.EVERYONE)) {
423                    color = TranslatableCaption.of("info.plot_list_owned_by");
424                } else if (plot.isAdded(player.getUUID())) {
425                    color = TranslatableCaption.of("info.plot_list_added_to");
426                } else if (plot.isDenied(player.getUUID())) {
427                    color = TranslatableCaption.of("info.plot_list_denied_on");
428                } else {
429                    color = TranslatableCaption.of("info.plot_list_default");
430                }
431                Component trusted = MINI_MESSAGE.deserialize(
432                        TranslatableCaption.of("info.plot_info_trusted").getComponent(player),
433                        TagResolver.resolver("trusted", Tag.inserting(PlayerManager.getPlayerList(plot.getTrusted(), player)))
434                );
435                Component members = MINI_MESSAGE.deserialize(
436                        TranslatableCaption.of("info.plot_info_members").getComponent(player),
437                        TagResolver.resolver("members", Tag.inserting(PlayerManager.getPlayerList(plot.getMembers(), player)))
438                );
439                TagResolver.Builder finalResolver = TagResolver.builder();
440                finalResolver.tag(
441                        "command_tp",
442                        Tag.preProcessParsed("/plot visit " + plot.getArea() + ";" + plot.getId())
443                );
444                finalResolver.tag(
445                        "command_info",
446                        Tag.preProcessParsed("/plot info " + plot.getArea() + ";" + plot.getId())
447                );
448                finalResolver.tag("hover_info", Tag.inserting(
449                        Component.text()
450                                .append(trusted)
451                                .append(Component.newline())
452                                .append(members)
453                                .asComponent()
454                ));
455                finalResolver.tag("number", Tag.inserting(Component.text(i)));
456                finalResolver.tag("plot", Tag.inserting(MINI_MESSAGE.deserialize(
457                        color.getComponent(player), TagResolver.resolver("plot", Tag.inserting(Component.text(plot.toString())))
458                )));
459                String prefix = "";
460                String online = TranslatableCaption.of("info.plot_list_player_online").getComponent(player);
461                String offline = TranslatableCaption.of("info.plot_list_player_offline").getComponent(player);
462                String unknown = TranslatableCaption.of("info.plot_list_player_unknown").getComponent(player);
463                String server = TranslatableCaption.of("info.plot_list_player_server").getComponent(player);
464                String everyone = TranslatableCaption.of("info.plot_list_player_everyone").getComponent(player);
465                TextComponent.Builder builder = Component.text();
466                if (plot.getFlag(ServerPlotFlag.class)) {
467                    TagResolver serverResolver = TagResolver.resolver(
468                            "info.server",
469                            Tag.inserting(TranslatableCaption.of("info.server").toComponent(player))
470                    );
471                    builder.append(MINI_MESSAGE.deserialize(server, serverResolver));
472                } else {
473                    try {
474                        final List<UUIDMapping> names = PlotSquared.get().getImpromptuUUIDPipeline().getNames(plot.getOwners())
475                                .get(Settings.UUID.BLOCKING_TIMEOUT, TimeUnit.MILLISECONDS);
476                        for (final UUIDMapping uuidMapping : names) {
477                            PlotPlayer<?> pp = PlotSquared.platform().playerManager().getPlayerIfExists(uuidMapping.uuid());
478                            TagResolver resolver = TagResolver.builder()
479                                    .tag("prefix", Tag.inserting(Component.text(prefix)))
480                                    .tag("player", Tag.inserting(Component.text(uuidMapping.username())))
481                                    .build();
482                            if (pp != null) {
483                                builder.append(MINI_MESSAGE.deserialize(online, resolver));
484                            } else if (uuidMapping.username().equalsIgnoreCase("unknown")) {
485                                TagResolver unknownResolver = TagResolver.resolver(
486                                        "info.unknown",
487                                        Tag.inserting(TranslatableCaption.of("info.unknown").toComponent(player))
488                                );
489                                builder.append(MINI_MESSAGE.deserialize(unknown, unknownResolver));
490                            } else if (uuidMapping.uuid().equals(DBFunc.EVERYONE)) {
491                                TagResolver everyoneResolver = TagResolver.resolver(
492                                        "info.everyone",
493                                        Tag.inserting(TranslatableCaption.of("info.everyone").toComponent(player))
494                                );
495                                builder.append(MINI_MESSAGE.deserialize(everyone, everyoneResolver));
496                            } else {
497                                builder.append(MINI_MESSAGE.deserialize(offline, resolver));
498                            }
499                            prefix = ", ";
500                        }
501                    } catch (InterruptedException | ExecutionException e) {
502                        final StringBuilder playerBuilder = new StringBuilder();
503                        final Iterator<UUID> uuidIterator = plot.getOwners().iterator();
504                        while (uuidIterator.hasNext()) {
505                            final UUID uuid = uuidIterator.next();
506                            playerBuilder.append(uuid);
507                            if (uuidIterator.hasNext()) {
508                                playerBuilder.append(", ");
509                            }
510                        }
511                        player.sendMessage(
512                                TranslatableCaption.of("errors.invalid_player"),
513                                TagResolver.resolver("value", Tag.inserting(Component.text(playerBuilder.toString())))
514                        );
515                    } catch (TimeoutException e) {
516                        player.sendMessage(TranslatableCaption.of("players.fetching_players_timeout"));
517                    }
518                }
519                finalResolver.tag("players", Tag.inserting(builder.asComponent()));
520                caption.set(TranslatableCaption.of("info.plot_list_item"));
521                caption.setTagResolvers(finalResolver.build());
522            }
523        }, "/plot list " + args[0], TranslatableCaption.of("list.plot_list_header_paged"));
524    }
525
526    @Override
527    public Collection<Command> tab(PlotPlayer<?> player, String[] args, boolean space) {
528        final List<String> completions = new LinkedList<>();
529        if (this.econHandler.isSupported() && player.hasPermission(Permission.PERMISSION_LIST_FOR_SALE)) {
530            completions.add("forsale");
531        }
532        if (player.hasPermission(Permission.PERMISSION_LIST_MINE)) {
533            completions.add("mine");
534        }
535        if (player.hasPermission(Permission.PERMISSION_LIST_SHARED)) {
536            completions.add("shared");
537        }
538        if (player.hasPermission(Permission.PERMISSION_LIST_WORLD)) {
539            completions.addAll(PlotSquared.platform().worldManager().getWorlds());
540        }
541        if (player.hasPermission(Permission.PERMISSION_LIST_TOP)) {
542            completions.add("top");
543        }
544        if (player.hasPermission(Permission.PERMISSION_LIST_ALL)) {
545            completions.add("all");
546        }
547        if (player.hasPermission(Permission.PERMISSION_LIST_UNOWNED)) {
548            completions.add("unowned");
549        }
550        if (player.hasPermission(Permission.PERMISSION_LIST_DONE)) {
551            completions.add("done");
552        }
553        if (player.hasPermission(Permission.PERMISSION_LIST_EXPIRED)) {
554            completions.add("expired");
555        }
556
557        final List<Command> commands = completions.stream().filter(completion -> completion
558                        .toLowerCase()
559                        .startsWith(args[0].toLowerCase()))
560                .map(completion -> new Command(null, true, completion, "", RequiredType.NONE, CommandCategory.TELEPORT) {
561                }).collect(Collectors.toCollection(LinkedList::new));
562
563        if (player.hasPermission(Permission.PERMISSION_LIST_PLAYER) && args[0].length() > 0) {
564            commands.addAll(TabCompletions.completePlayers(player, args[0], Collections.emptyList()));
565        }
566
567        return commands;
568    }
569
570}