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