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}