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