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.CaptionUtility;
025import com.plotsquared.core.configuration.caption.StaticCaption;
026import com.plotsquared.core.configuration.caption.TranslatableCaption;
027import com.plotsquared.core.events.PlotFlagAddEvent;
028import com.plotsquared.core.events.PlotFlagRemoveEvent;
029import com.plotsquared.core.events.Result;
030import com.plotsquared.core.permissions.Permission;
031import com.plotsquared.core.player.PlotPlayer;
032import com.plotsquared.core.plot.Plot;
033import com.plotsquared.core.plot.flag.FlagParseException;
034import com.plotsquared.core.plot.flag.GlobalFlagContainer;
035import com.plotsquared.core.plot.flag.InternalFlag;
036import com.plotsquared.core.plot.flag.PlotFlag;
037import com.plotsquared.core.plot.flag.types.IntegerFlag;
038import com.plotsquared.core.plot.flag.types.ListFlag;
039import com.plotsquared.core.util.EventDispatcher;
040import com.plotsquared.core.util.MathMan;
041import com.plotsquared.core.util.StringComparison;
042import com.plotsquared.core.util.StringMan;
043import com.plotsquared.core.util.helpmenu.HelpMenu;
044import com.plotsquared.core.util.task.RunnableVal2;
045import com.plotsquared.core.util.task.RunnableVal3;
046import net.kyori.adventure.text.Component;
047import net.kyori.adventure.text.TextComponent;
048import net.kyori.adventure.text.format.Style;
049import net.kyori.adventure.text.minimessage.tag.Tag;
050import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
051import org.checkerframework.checker.nullness.qual.NonNull;
052import org.checkerframework.checker.nullness.qual.Nullable;
053
054import java.util.ArrayList;
055import java.util.Arrays;
056import java.util.Collection;
057import java.util.Collections;
058import java.util.HashMap;
059import java.util.Iterator;
060import java.util.List;
061import java.util.Locale;
062import java.util.Map;
063import java.util.concurrent.CompletableFuture;
064import java.util.stream.Collectors;
065import java.util.stream.Stream;
066
067@CommandDeclaration(command = "flag",
068        aliases = {"f", "flag"},
069        usage = "/plot flag <set | remove | add | list | info> <flag> <value>",
070        category = CommandCategory.SETTINGS,
071        requiredType = RequiredType.NONE,
072        permission = "plots.flag")
073@SuppressWarnings("unused")
074public final class FlagCommand extends Command {
075
076    private final EventDispatcher eventDispatcher;
077
078    @Inject
079    public FlagCommand(final @NonNull EventDispatcher eventDispatcher) {
080        super(MainCommand.getInstance(), true);
081        this.eventDispatcher = eventDispatcher;
082    }
083
084    private static boolean sendMessage(PlotPlayer<?> player) {
085        player.sendMessage(
086                TranslatableCaption.of("commandconfig.command_syntax"),
087                TagResolver.resolver(
088                        "value",
089                        Tag.inserting(Component.text("/plot flag <set | remove | add | list | info> <flag> <value>"))
090                )
091        );
092        return true;
093    }
094
095    private static boolean checkPermValue(
096            final @NonNull PlotPlayer<?> player,
097            final @NonNull PlotFlag<?, ?> flag, @NonNull String key, @NonNull String value
098    ) {
099        key = key.toLowerCase();
100        value = value.toLowerCase();
101        String perm = Permission.PERMISSION_SET_FLAG_KEY_VALUE.format(key.toLowerCase(), value.toLowerCase());
102        if (flag instanceof IntegerFlag && MathMan.isInteger(value)) {
103            try {
104                int numeric = Integer.parseInt(value);
105                // Getting full permission without ".<amount>" at the end
106                perm = perm.substring(0, perm.length() - value.length() - 1);
107                boolean result = false;
108                if (numeric >= 0) {
109                    int checkRange = PlotSquared.get().getPlatform().equalsIgnoreCase("bukkit") ?
110                            numeric :
111                            Settings.Limit.MAX_PLOTS;
112                    result = player.hasPermissionRange(perm, checkRange) >= numeric;
113                }
114                if (!result) {
115                    player.sendMessage(
116                            TranslatableCaption.of("permission.no_permission"),
117                            TagResolver.resolver(
118                                    "node",
119                                    Tag.inserting(Component.text(perm + "." + numeric))
120                            )
121                    );
122                }
123                return result;
124            } catch (NumberFormatException ignore) {
125            }
126        } else if (flag instanceof final ListFlag<?, ?> listFlag) {
127            try {
128                PlotFlag<? extends List<?>, ?> parsedFlag = listFlag.parse(value);
129                for (final Object entry : parsedFlag.getValue()) {
130                    final String permission = Permission.PERMISSION_SET_FLAG_KEY_VALUE.format(
131                            key.toLowerCase(),
132                            entry.toString().toLowerCase()
133                    );
134                    final boolean result = player.hasPermission(permission);
135                    if (!result) {
136                        player.sendMessage(
137                                TranslatableCaption.of("permission.no_permission"),
138                                TagResolver.resolver("node", Tag.inserting(Component.text(permission)))
139                        );
140                        return false;
141                    }
142                }
143            } catch (final FlagParseException e) {
144                player.sendMessage(
145                        TranslatableCaption.of("flag.flag_parse_error"),
146                        TagResolver.builder()
147                                .tag("flag_name", Tag.inserting(Component.text(flag.getName())))
148                                .tag("flag_value", Tag.inserting(Component.text(e.getValue())))
149                                .tag("error", Tag.inserting(e.getErrorMessage().toComponent(player)))
150                                .build()
151                );
152                return false;
153            } catch (final Exception e) {
154                return false;
155            }
156            return true;
157        }
158        boolean result;
159        String basePerm = Permission.PERMISSION_SET_FLAG_KEY.format(key.toLowerCase());
160        if (flag.isValuedPermission()) {
161            result = player.hasKeyedPermission(basePerm, value);
162        } else {
163            result = player.hasPermission(basePerm);
164            perm = basePerm;
165        }
166        if (!result) {
167            player.sendMessage(
168                    TranslatableCaption.of("permission.no_permission"),
169                    TagResolver.resolver("node", Tag.inserting(Component.text(perm)))
170            );
171        }
172        return result;
173    }
174
175    /**
176     * Checks if the player is allowed to modify the flags at their current location
177     *
178     * @return {@code true} if the player is allowed to modify the flags at their current location
179     */
180    private static boolean checkRequirements(final @NonNull PlotPlayer<?> player) {
181        final Plot plot = player.getCurrentPlot();
182        if (plot == null) {
183            player.sendMessage(TranslatableCaption.of("errors.not_in_plot"));
184            return false;
185        }
186        if (!plot.hasOwner()) {
187            player.sendMessage(TranslatableCaption.of("working.plot_not_claimed"));
188            return false;
189        }
190        if (!plot.isOwner(player.getUUID()) && !player.hasPermission(Permission.PERMISSION_SET_FLAG_OTHER)) {
191            player.sendMessage(
192                    TranslatableCaption.of("permission.no_permission"),
193                    TagResolver.resolver("node", Tag.inserting(Permission.PERMISSION_SET_FLAG_OTHER))
194            );
195            return false;
196        }
197        return true;
198    }
199
200    /**
201     * Attempt to extract the plot flag from the command arguments. If the flag cannot
202     * be found, a flag suggestion may be sent to the player.
203     *
204     * @param player Player executing the command
205     * @param arg    String to extract flag from
206     * @return The flag, if found, else null
207     */
208    @Nullable
209    private static PlotFlag<?, ?> getFlag(
210            final @NonNull PlotPlayer<?> player,
211            final @NonNull String arg
212    ) {
213        if (arg.length() > 0) {
214            final PlotFlag<?, ?> flag = GlobalFlagContainer.getInstance().getFlagFromString(arg);
215            if (flag instanceof InternalFlag || flag == null) {
216                boolean suggested = false;
217                try {
218                    final StringComparison<PlotFlag<?, ?>> stringComparison =
219                            new StringComparison<>(
220                                    arg,
221                                    GlobalFlagContainer.getInstance().getFlagMap().values(),
222                                    PlotFlag::getName
223                            );
224                    final String best = stringComparison.getBestMatch();
225                    if (best != null) {
226                        player.sendMessage(
227                                TranslatableCaption.of("flag.not_valid_flag_suggested"),
228                                TagResolver.resolver("value", Tag.inserting(Component.text(best)))
229                        );
230                        suggested = true;
231                    }
232                } catch (final Exception ignored) { /* Happens sometimes because of mean code */ }
233                if (!suggested) {
234                    player.sendMessage(TranslatableCaption.of("flag.not_valid_flag"));
235                }
236                return null;
237            }
238            return flag;
239        }
240        return null;
241    }
242
243    @Override
244    public CompletableFuture<Boolean> execute(
245            PlotPlayer<?> player, String[] args,
246            RunnableVal3<Command, Runnable, Runnable> confirm,
247            RunnableVal2<Command, CommandResult> whenDone
248    ) throws CommandException {
249        if (args.length == 0 || !Arrays
250                .asList("set", "s", "list", "l", "delete", "remove", "r", "add", "a", "info", "i")
251                .contains(args[0].toLowerCase(Locale.ENGLISH))) {
252            new HelpMenu(player).setCategory(CommandCategory.SETTINGS)
253                    .setCommands(this.getCommands()).generateMaxPages()
254                    .generatePage(0, getParent().toString(), player).render();
255            return CompletableFuture.completedFuture(true);
256        }
257        return super.execute(player, args, confirm, whenDone);
258    }
259
260    @Override
261    public Collection<Command> tab(
262            final PlotPlayer<?> player, final String[] args,
263            final boolean space
264    ) {
265        if (args.length == 1) {
266            return Stream
267                    .of("set", "add", "remove", "delete", "info", "list")
268                    .filter(value -> value.startsWith(args[0].toLowerCase(Locale.ENGLISH)))
269                    .map(value -> new Command(null, false, value, "", RequiredType.NONE, null) {
270                    }).collect(Collectors.toList());
271        } else if (Arrays.asList("set", "add", "remove", "delete", "info")
272                .contains(args[0].toLowerCase(Locale.ENGLISH)) && args.length == 2) {
273            return GlobalFlagContainer.getInstance().getRecognizedPlotFlags().stream()
274                    .filter(flag -> !(flag instanceof InternalFlag))
275                    .filter(flag -> flag.getName().startsWith(args[1].toLowerCase(Locale.ENGLISH)))
276                    .map(flag -> new Command(null, false, flag.getName(), "", RequiredType.NONE, null) {
277                    }).collect(Collectors.toList());
278        } else if (Arrays.asList("set", "add", "remove", "delete")
279                .contains(args[0].toLowerCase(Locale.ENGLISH)) && args.length == 3) {
280            try {
281                final PlotFlag<?, ?> flag =
282                        GlobalFlagContainer.getInstance().getFlagFromString(args[1]);
283                if (flag != null) {
284                    Stream<String> stream = flag.getTabCompletions().stream();
285                    if (flag instanceof ListFlag && args[2].contains(",")) {
286                        final String[] split = args[2].split(",");
287                        // Prefix earlier values onto all suggestions
288                        StringBuilder prefix = new StringBuilder();
289                        for (int i = 0; i < split.length - 1; i++) {
290                            prefix.append(split[i]).append(",");
291                        }
292                        final String cmp;
293                        if (!args[2].endsWith(",")) {
294                            cmp = split[split.length - 1];
295                        } else {
296                            prefix.append(split[split.length - 1]).append(",");
297                            cmp = "";
298                        }
299                        return stream
300                                .filter(value -> value.startsWith(cmp.toLowerCase(Locale.ENGLISH))).map(
301                                        value -> new Command(null, false, prefix + value, "",
302                                                RequiredType.NONE, null
303                                        ) {
304                                        }).collect(Collectors.toList());
305                    } else {
306                        return stream
307                                .filter(value -> value.startsWith(args[2].toLowerCase(Locale.ENGLISH)))
308                                .map(value -> new Command(null, false, value, "", RequiredType.NONE,
309                                        null
310                                ) {
311                                }).collect(Collectors.toList());
312                    }
313                }
314            } catch (final Exception ignored) {
315            }
316        }
317        return tabOf(player, args, space);
318    }
319
320    @CommandDeclaration(command = "set",
321            aliases = {"s", "set"},
322            usage = "/plot flag set <flag> <value>",
323            category = CommandCategory.SETTINGS,
324            requiredType = RequiredType.NONE,
325            permission = "plots.set.flag")
326    public void set(
327            final Command command, final PlotPlayer<?> player, final String[] args,
328            final RunnableVal3<Command, Runnable, Runnable> confirm,
329            final RunnableVal2<Command, CommandResult> whenDone
330    ) {
331        if (!checkRequirements(player)) {
332            return;
333        }
334        if (args.length < 2) {
335            player.sendMessage(
336                    TranslatableCaption.of("commandconfig.command_syntax"),
337                    TagResolver.resolver("value", Tag.inserting(Component.text("/plot flag set <flag> <value>")))
338            );
339            return;
340        }
341        final PlotFlag<?, ?> plotFlag = getFlag(player, args[0]);
342        if (plotFlag == null) {
343            return;
344        }
345        Plot plot = player.getCurrentPlot();
346        PlotFlagAddEvent event = eventDispatcher.callFlagAdd(plotFlag, plot);
347        if (event.getEventResult() == Result.DENY) {
348            player.sendMessage(
349                    TranslatableCaption.of("events.event_denied"),
350                    TagResolver.resolver("value", Tag.inserting(Component.text("Flag set")))
351            );
352            return;
353        }
354        boolean force = event.getEventResult() == Result.FORCE;
355        String value = StringMan.join(Arrays.copyOfRange(args, 1, args.length), " ");
356        if (!force && !checkPermValue(player, plotFlag, args[0], value)) {
357            return;
358        }
359        value = CaptionUtility.stripClickEvents(plotFlag, value);
360        final PlotFlag<?, ?> parsed;
361        try {
362            parsed = plotFlag.parse(value);
363        } catch (final FlagParseException e) {
364            player.sendMessage(
365                    TranslatableCaption.of("flag.flag_parse_error"),
366                    TagResolver.builder()
367                            .tag("flag_name", Tag.inserting(Component.text(plotFlag.getName())))
368                            .tag("flag_value", Tag.inserting(Component.text(e.getValue())))
369                            .tag("error", Tag.inserting(e.getErrorMessage().toComponent(player)))
370                            .build()
371            );
372            return;
373        }
374        plot.setFlag(parsed);
375        player.sendMessage(
376                TranslatableCaption.of("flag.flag_added"),
377                TagResolver.builder()
378                        .tag("flag", Tag.inserting(Component.text(args[0])))
379                        .tag("value", Tag.inserting(Component.text(parsed.toString())))
380                        .build()
381        );
382    }
383
384    @SuppressWarnings({"unchecked", "rawtypes"})
385    @CommandDeclaration(command = "add",
386            aliases = {"a", "add"},
387            usage = "/plot flag add <flag> <value>",
388            category = CommandCategory.SETTINGS,
389            requiredType = RequiredType.NONE,
390            permission = "plots.flag.add")
391    public void add(
392            final Command command, PlotPlayer<?> player, final String[] args,
393            final RunnableVal3<Command, Runnable, Runnable> confirm,
394            final RunnableVal2<Command, CommandResult> whenDone
395    ) {
396        if (!checkRequirements(player)) {
397            return;
398        }
399        if (args.length < 2) {
400            player.sendMessage(
401                    TranslatableCaption.of("commandconfig.command_syntax"),
402                    TagResolver.resolver("value", Tag.inserting(Component.text("/plot flag add <flag> <values>")))
403            );
404            return;
405        }
406        final PlotFlag<?, ?> plotFlag = getFlag(player, args[0]);
407        if (plotFlag == null) {
408            return;
409        }
410        Plot plot = player.getCurrentPlot();
411        PlotFlagAddEvent event = eventDispatcher.callFlagAdd(plotFlag, plot);
412        if (event.getEventResult() == Result.DENY) {
413            player.sendMessage(
414                    TranslatableCaption.of("events.event_denied"),
415                    TagResolver.resolver("value", Tag.inserting(Component.text("Flag add")))
416            );
417            return;
418        }
419        boolean force = event.getEventResult() == Result.FORCE;
420        final PlotFlag localFlag = player.getCurrentPlot().getFlagContainer()
421                .getFlag(event.getFlag().getClass());
422        if (!force) {
423            for (String entry : args[1].split(",")) {
424                if (!checkPermValue(player, event.getFlag(), args[0], entry)) {
425                    return;
426                }
427            }
428        }
429        final String value = StringMan.join(Arrays.copyOfRange(args, 1, args.length), " ");
430        final PlotFlag parsed;
431        try {
432            parsed = event.getFlag().parse(value);
433        } catch (FlagParseException e) {
434            player.sendMessage(
435                    TranslatableCaption.of("flag.flag_parse_error"),
436                    TagResolver.builder()
437                            .tag("flag_name", Tag.inserting(Component.text(plotFlag.getName())))
438                            .tag("flag_value", Tag.inserting(Component.text(e.getValue())))
439                            .tag("error", Tag.inserting(e.getErrorMessage().toComponent(player)))
440                            .build()
441            );
442            return;
443        }
444        boolean result =
445                player.getCurrentPlot().setFlag(localFlag.merge(parsed.getValue()));
446        if (!result) {
447            player.sendMessage(TranslatableCaption.of("flag.flag_not_added"));
448            return;
449        }
450        player.sendMessage(
451                TranslatableCaption.of("flag.flag_added"),
452                TagResolver.builder()
453                        .tag("flag", Tag.inserting(Component.text(args[0])))
454                        .tag("value", Tag.inserting(Component.text(parsed.toString())))
455                        .build()
456        );
457    }
458
459    @SuppressWarnings({"unchecked", "rawtypes"})
460    @CommandDeclaration(command = "remove",
461            aliases = {"r", "remove", "delete"},
462            usage = "/plot flag remove <flag> [values]",
463            category = CommandCategory.SETTINGS,
464            requiredType = RequiredType.NONE,
465            permission = "plots.flag.remove")
466    public void remove(
467            final Command command, PlotPlayer<?> player, final String[] args,
468            final RunnableVal3<Command, Runnable, Runnable> confirm,
469            final RunnableVal2<Command, CommandResult> whenDone
470    ) {
471        if (!checkRequirements(player)) {
472            return;
473        }
474        if (args.length != 1 && args.length != 2) {
475            player.sendMessage(
476                    TranslatableCaption.of("commandconfig.command_syntax"),
477                    TagResolver.resolver("value", Tag.inserting(Component.text("/plot flag remove <flag> [values]")))
478            );
479            return;
480        }
481        PlotFlag<?, ?> flag = getFlag(player, args[0]);
482        if (flag == null) {
483            return;
484        }
485        final Plot plot = player.getCurrentPlot();
486        final PlotFlag<?, ?> flagWithOldValue = plot.getFlagContainer().getFlag(flag.getClass());
487        PlotFlagRemoveEvent event = eventDispatcher.callFlagRemove(flag, plot);
488        if (event.getEventResult() == Result.DENY) {
489            player.sendMessage(
490                    TranslatableCaption.of("events.event_denied"),
491                    TagResolver.resolver("value", Tag.inserting(Component.text("Flag remove")))
492            );
493            return;
494        }
495        boolean force = event.getEventResult() == Result.FORCE;
496        flag = event.getFlag();
497        if (!force && !player.hasPermission(Permission.PERMISSION_SET_FLAG_KEY.format(args[0].toLowerCase()))) {
498            if (args.length != 2) {
499                player.sendMessage(
500                        TranslatableCaption.of("permission.no_permission"),
501                        TagResolver.resolver(
502                                "node",
503                                Tag.inserting(Component.text(Permission.PERMISSION_SET_FLAG_KEY.format(args[0].toLowerCase())))
504                        )
505                );
506                return;
507            }
508        }
509        if (args.length == 2 && flag instanceof final ListFlag<?, ?> listFlag) {
510            String value = StringMan.join(Arrays.copyOfRange(args, 1, args.length), " ");
511            final List<?> list =
512                    new ArrayList<>(plot.getFlag((Class<? extends ListFlag<?, ?>>) listFlag.getClass()));
513            final PlotFlag parsedFlag;
514            try {
515                parsedFlag = listFlag.parse(value);
516            } catch (final FlagParseException e) {
517                player.sendMessage(
518                        TranslatableCaption.of("flag.flag_parse_error"),
519                        TagResolver.builder()
520                                .tag("flag_name", Tag.inserting(Component.text(flag.getName())))
521                                .tag("flag_value", Tag.inserting(Component.text(e.getValue())))
522                                .tag("error", Tag.inserting(e.getErrorMessage().toComponent(player)))
523                                .build()
524                );
525                return;
526            }
527            if (((List<?>) parsedFlag.getValue()).isEmpty()) {
528                player.sendMessage(TranslatableCaption.of("flag.flag_not_removed"));
529                return;
530            }
531            if (list.removeAll((List) parsedFlag.getValue())) {
532                if (list.isEmpty()) {
533                    if (plot.removeFlag(flag)) {
534                        player.sendMessage(
535                                TranslatableCaption.of("flag.flag_removed"),
536                                TagResolver.builder()
537                                        .tag("flag", Tag.inserting(Component.text(args[0])))
538                                        .tag("value", Tag.inserting(Component.text(flag.toString())))
539                                        .build()
540                        );
541                        return;
542                    } else {
543                        player.sendMessage(TranslatableCaption.of("flag.flag_not_removed"));
544                        return;
545                    }
546                } else {
547                    PlotFlag<?, ?> plotFlag = parsedFlag.createFlagInstance(list);
548                    PlotFlagAddEvent addEvent = eventDispatcher.callFlagAdd(plotFlag, plot);
549                    if (addEvent.getEventResult() == Result.DENY) {
550                        player.sendMessage(
551                                TranslatableCaption.of("events.event_denied"),
552                                TagResolver.resolver(
553                                        "value",
554                                        Tag.inserting(Component.text("Re-addition of " + plotFlag.getName()))
555                                )
556                        );
557                        return;
558                    }
559                    if (plot.setFlag(addEvent.getFlag())) {
560                        player.sendMessage(TranslatableCaption.of("flag.flag_partially_removed"));
561                        return;
562                    } else {
563                        player.sendMessage(TranslatableCaption.of("flag.flag_not_removed"));
564                        return;
565                    }
566                }
567            } else {
568                player.sendMessage(TranslatableCaption.of("flag.flag_not_removed"));
569                return;
570            }
571        } else {
572            boolean result = plot.removeFlag(flag);
573            if (!result) {
574                player.sendMessage(TranslatableCaption.of("flag.flag_not_removed"));
575                return;
576            }
577        }
578        player.sendMessage(
579                TranslatableCaption.of("flag.flag_removed"),
580                TagResolver.builder()
581                        .tag("flag", Tag.inserting(Component.text(args[0])))
582                        .tag("value", Tag.inserting(Component.text(flag.toString())))
583                        .build()
584        );
585    }
586
587    @CommandDeclaration(command = "list",
588            aliases = {"l", "list", "flags"},
589            usage = "/plot flag list",
590            category = CommandCategory.SETTINGS,
591            requiredType = RequiredType.NONE,
592            permission = "plots.flag.list")
593    public void list(
594            final Command command, final PlotPlayer<?> player, final String[] args,
595            final RunnableVal3<Command, Runnable, Runnable> confirm,
596            final RunnableVal2<Command, CommandResult> whenDone
597    ) {
598        if (!checkRequirements(player)) {
599            return;
600        }
601
602        final Map<Component, ArrayList<String>> flags = new HashMap<>();
603        for (PlotFlag<?, ?> plotFlag : GlobalFlagContainer.getInstance().getRecognizedPlotFlags()) {
604            if (plotFlag instanceof InternalFlag) {
605                continue;
606            }
607            final Component category = plotFlag.getFlagCategory().toComponent(player);
608            final Collection<String> flagList = flags.computeIfAbsent(category, k -> new ArrayList<>());
609            flagList.add(plotFlag.getName());
610        }
611
612        for (final Map.Entry<Component, ArrayList<String>> entry : flags.entrySet()) {
613            Collections.sort(entry.getValue());
614            Component category =
615                    MINI_MESSAGE.deserialize(
616                            TranslatableCaption.of("flag.flag_list_categories").getComponent(player),
617                            TagResolver.resolver("category", Tag.inserting(entry.getKey().style(Style.empty())))
618                    );
619            TextComponent.Builder builder = Component.text().append(category);
620            final Iterator<String> flagIterator = entry.getValue().iterator();
621            while (flagIterator.hasNext()) {
622                final String flag = flagIterator.next();
623                builder.append(MINI_MESSAGE
624                        .deserialize(
625                                TranslatableCaption.of("flag.flag_list_flag").getComponent(player),
626                                TagResolver.builder()
627                                        .tag("command", Tag.preProcessParsed("/plot flag info " + flag))
628                                        .tag("flag", Tag.inserting(Component.text(flag)))
629                                        .tag("suffix", Tag.inserting(Component.text(flagIterator.hasNext() ? ", " : "")))
630                                        .build()
631                        ));
632            }
633            player.sendMessage(StaticCaption.of(MINI_MESSAGE.serialize(builder.build())));
634        }
635    }
636
637    @CommandDeclaration(command = "info",
638            aliases = {"i", "info"},
639            usage = "/plot flag info <flag>",
640            category = CommandCategory.SETTINGS,
641            requiredType = RequiredType.NONE,
642            permission = "plots.flag.info")
643    public void info(
644            final Command command, final PlotPlayer<?> player, final String[] args,
645            final RunnableVal3<Command, Runnable, Runnable> confirm,
646            final RunnableVal2<Command, CommandResult> whenDone
647    ) {
648        if (!checkRequirements(player)) {
649            return;
650        }
651        if (args.length < 1) {
652            player.sendMessage(
653                    TranslatableCaption.of("commandconfig.command_syntax"),
654                    TagResolver.resolver("value", Tag.inserting(Component.text("/plot flag info <flag>")))
655            );
656            return;
657        }
658        final PlotFlag<?, ?> plotFlag = getFlag(player, args[0]);
659        if (plotFlag != null) {
660            player.sendMessage(TranslatableCaption.of("flag.flag_info_header"));
661            // Flag name
662            player.sendMessage(
663                    TranslatableCaption.of("flag.flag_info_name"),
664                    TagResolver.resolver("flag", Tag.inserting(Component.text(plotFlag.getName())))
665            );
666            // Flag category
667            player.sendMessage(
668                    TranslatableCaption.of("flag.flag_info_category"),
669                    TagResolver.resolver(
670                            "value",
671                            Tag.inserting(plotFlag.getFlagCategory().toComponent(player))
672                    )
673            );
674            // Flag description
675            // TODO maybe merge and \n instead?
676            player.sendMessage(TranslatableCaption.of("flag.flag_info_description"));
677            player.sendMessage(plotFlag.getFlagDescription());
678            // Flag example
679            player.sendMessage(
680                    TranslatableCaption.of("flag.flag_info_example"),
681                    TagResolver.builder()
682                            .tag("command", Tag.preProcessParsed("/plot flag set"))
683                            .tag("flag", Tag.preProcessParsed(plotFlag.getName()))
684                            .tag("value", Tag.preProcessParsed(plotFlag.getExample()))
685                            .build()
686            );
687            // Default value
688            final String defaultValue = player.getCurrentPlot().getArea().getFlagContainer()
689                    .getFlagErased(plotFlag.getClass()).toString();
690            player.sendMessage(
691                    TranslatableCaption.of("flag.flag_info_default_value"),
692                    TagResolver.resolver("value", Tag.inserting(Component.text(defaultValue)))
693            );
694            // Footer. Done this way to prevent the duplicate-message-thingy from catching it
695            player.sendMessage(TranslatableCaption.of("flag.flag_info_footer"));
696        }
697    }
698
699}