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.plotsquared.core.PlotSquared;
022import com.plotsquared.core.configuration.Settings;
023import com.plotsquared.core.configuration.caption.Caption;
024import com.plotsquared.core.configuration.caption.TranslatableCaption;
025import com.plotsquared.core.database.DBFunc;
026import com.plotsquared.core.events.TeleportCause;
027import com.plotsquared.core.location.BlockLoc;
028import com.plotsquared.core.location.Location;
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.PlotCluster;
034import com.plotsquared.core.plot.PlotId;
035import com.plotsquared.core.util.ComponentHelper;
036import com.plotsquared.core.util.TabCompletions;
037import com.plotsquared.core.util.query.PlotQuery;
038import net.kyori.adventure.text.Component;
039import net.kyori.adventure.text.format.NamedTextColor;
040import net.kyori.adventure.text.format.Style;
041import net.kyori.adventure.text.minimessage.tag.Tag;
042import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
043
044import java.util.Collection;
045import java.util.Collections;
046import java.util.HashSet;
047import java.util.LinkedList;
048import java.util.List;
049import java.util.Objects;
050import java.util.Set;
051import java.util.UUID;
052import java.util.concurrent.TimeoutException;
053import java.util.stream.Collectors;
054import java.util.stream.Stream;
055
056@CommandDeclaration(command = "cluster",
057        aliases = "clusters",
058        category = CommandCategory.ADMINISTRATION,
059        requiredType = RequiredType.NONE,
060        permission = "plots.cluster")
061public class Cluster extends SubCommand {
062
063    private static final Component[] AVAILABLE_ARGS = Stream.of(
064            "list", "create", "delete", "resize", "invite", "kick", "leave", "helpers", "tp", "sethome"
065    ).map(s -> Component.text(s).style(Style.style(NamedTextColor.DARK_AQUA))).toArray(Component[]::new);
066    private static final Component SEPARATOR = Component.text(", ").style(Style.style(NamedTextColor.GRAY));
067
068    // list, create, delete, resize, invite, kick, leave, helpers, tp, sethome
069    @Override
070    public boolean onCommand(PlotPlayer<?> player, String[] args) {
071        if (args.length == 0) {
072            // return arguments
073            player.sendMessage(
074                    TranslatableCaption.of("cluster.cluster_available_args"),
075                    TagResolver.resolver("list", Tag.inserting(ComponentHelper.join(AVAILABLE_ARGS, SEPARATOR)))
076            );
077            return false;
078        }
079        String sub = args[0].toLowerCase();
080        switch (sub) {
081            case "l", "list" -> {
082                if (!player.hasPermission(Permission.PERMISSION_CLUSTER_LIST)) {
083                    player.sendMessage(
084                            TranslatableCaption.of("permission.no_permission"),
085                            TagResolver.resolver(
086                                    "node",
087                                    Tag.inserting(Permission.PERMISSION_CLUSTER_LIST)
088                            )
089                    );
090                    return false;
091                }
092                if (args.length != 1) {
093                    player.sendMessage(
094                            TranslatableCaption.of("commandconfig.command_syntax"),
095                            TagResolver.resolver("value", Tag.inserting(Component.text("/plot cluster list")))
096                    );
097                    return false;
098                }
099                PlotArea area = player.getApplicablePlotArea();
100                if (area == null) {
101                    player.sendMessage(TranslatableCaption.of("errors.not_in_plot_world"));
102                    return false;
103                }
104                Set<PlotCluster> clusters = area.getClusters();
105                player.sendMessage(
106                        TranslatableCaption.of("cluster.cluster_list_heading"),
107                        TagResolver.resolver("amount", Tag.inserting(Component.text(clusters.size())))
108                );
109                for (PlotCluster cluster : clusters) {
110                    // Ignore unmanaged clusters
111                    String name = "'" + cluster.getName() + "' : " + cluster;
112                    if (player.getUUID().equals(cluster.owner)) {
113                        player.sendMessage(
114                                TranslatableCaption.of("cluster.cluster_list_element_owner"),
115                                TagResolver.resolver("cluster", Tag.inserting(Component.text(name)))
116                        );
117                    } else if (cluster.helpers.contains(player.getUUID())) {
118                        player.sendMessage(
119                                TranslatableCaption.of("cluster.cluster_list_element_helpers"),
120                                TagResolver.resolver("cluster", Tag.inserting(Component.text(name)))
121                        );
122                    } else if (cluster.invited.contains(player.getUUID())) {
123                        player.sendMessage(
124                                TranslatableCaption.of("cluster.cluster_list_element_invited"),
125                                TagResolver.resolver("cluster", Tag.inserting(Component.text(name)))
126                        );
127                    } else {
128                        player.sendMessage(
129                                TranslatableCaption.of("cluster.cluster_list_element"),
130                                TagResolver.resolver("cluster", Tag.inserting(Component.text(cluster.toString())))
131                        );
132                    }
133                }
134                return true;
135            }
136            case "c", "create" -> {
137                if (!player.hasPermission(Permission.PERMISSION_CLUSTER_CREATE)) {
138                    player.sendMessage(
139                            TranslatableCaption.of("permission.no_permission"),
140                            TagResolver.resolver(
141                                    "node",
142                                    Tag.inserting(Permission.PERMISSION_CLUSTER_CREATE)
143                            )
144                    );
145                    return false;
146                }
147                PlotArea area = player.getApplicablePlotArea();
148                if (area == null) {
149                    player.sendMessage(TranslatableCaption.of("errors.not_in_plot_world"));
150                    return false;
151                }
152                if (args.length != 4) {
153                    player.sendMessage(
154                            TranslatableCaption.of("commandconfig.command_syntax"),
155                            TagResolver.resolver(
156                                    "value",
157                                    Tag.inserting(Component.text("/plot cluster create <name> <id-bot> <id-top>"))
158                            )
159                    );
160                    return false;
161                }
162                int currentClusters = Settings.Limit.GLOBAL ?
163                        player.getClusterCount() :
164                        player.getPlotCount(player.getLocation().getWorldName());
165                if (currentClusters >= player.getAllowedPlots()) {
166                    player.sendMessage(
167                            TranslatableCaption.of("permission.cant_claim_more_clusters"),
168                            TagResolver.resolver("amount", Tag.inserting(Component.text(player.getAllowedPlots())))
169                    );
170                }
171                PlotId pos1;
172                PlotId pos2;
173                // check pos1 / pos2
174                try {
175                    pos1 = PlotId.fromString(args[2]);
176                    pos2 = PlotId.fromString(args[3]);
177                } catch (IllegalArgumentException ignored) {
178                    player.sendMessage(TranslatableCaption.of("invalid.not_valid_plot_id"));
179                    return false;
180                }
181                // check if name is taken
182                String name = args[1];
183                if (area.getCluster(name) != null) {
184                    player.sendMessage(
185                            TranslatableCaption.of("alias.alias_is_taken"),
186                            TagResolver.resolver("alias", Tag.inserting(Component.text(name)))
187                    );
188                    return false;
189                }
190                if (pos2.getX() < pos1.getX() || pos2.getY() < pos1.getY()) {
191                    PlotId tmp = PlotId.of(Math.min(pos1.getX(), pos2.getX()), Math.min(pos1.getY(), pos2.getY()));
192                    pos2 = PlotId.of(Math.max(pos1.getX(), pos2.getX()), Math.max(pos1.getY(), pos2.getY()));
193                    pos1 = tmp;
194                }
195                //check if overlap
196                PlotCluster cluster = area.getFirstIntersectingCluster(pos1, pos2);
197                if (cluster != null) {
198                    player.sendMessage(
199                            TranslatableCaption.of("cluster.cluster_intersection"),
200                            TagResolver.resolver("cluster", Tag.inserting(Component.text(cluster.getName())))
201                    );
202                    return false;
203                }
204                // Check if it occupies existing plots
205                if (!area.contains(pos1) || !area.contains(pos2)) {
206                    player.sendMessage(
207                            TranslatableCaption.of("cluster.cluster_outside"),
208                            TagResolver.resolver("area", Tag.inserting(Component.text(area.toString())))
209                    );
210                    return false;
211                }
212                Set<Plot> plots = area.getPlotSelectionOwned(pos1, pos2);
213                if (!plots.isEmpty()) {
214                    if (!player.hasPermission(Permission.PERMISSION_CLUSTER_CREATE_OTHER)) {
215                        UUID uuid = player.getUUID();
216                        for (Plot plot : plots) {
217                            if (!plot.isOwner(uuid)) {
218                                player.sendMessage(
219                                        TranslatableCaption.of("permission.no_permission"),
220                                        TagResolver.resolver(
221                                                "node",
222                                                Tag.inserting(Permission.PERMISSION_CLUSTER_CREATE_OTHER)
223                                        )
224                                );
225                                return false;
226                            }
227                        }
228                    }
229                }
230                // Check allowed cluster size
231                cluster = new PlotCluster(area, pos1, pos2, player.getUUID());
232                int current;
233                if (Settings.Limit.GLOBAL) {
234                    current = player.getPlayerClusterCount();
235                } else {
236                    current = player.getPlayerClusterCount(player.getLocation().getWorldName());
237                }
238                int allowed = player.hasPermissionRange(
239                        Permission.PERMISSION_CLUSTER_SIZE,
240                        Settings.Limit.MAX_PLOTS
241                );
242                if (current + cluster.getArea() > allowed) {
243                    player.sendMessage(
244                            TranslatableCaption.of("permission.no_permission"),
245                            TagResolver.resolver(
246                                    "node",
247                                    Tag.inserting(Component.text(Permission.PERMISSION_CLUSTER_SIZE + "." + (current + cluster.getArea())))
248                            )
249                    );
250                    return false;
251                }
252                // create cluster
253                cluster.settings.setAlias(name);
254                area.addCluster(cluster);
255                DBFunc.createCluster(cluster);
256                // Add any existing plots to the current cluster
257                for (Plot plot : plots) {
258                    if (plot.hasOwner()) {
259                        if (!cluster.isAdded(plot.getOwner())) {
260                            cluster.invited.add(plot.getOwner());
261                            DBFunc.setInvited(cluster, plot.getOwner());
262                        }
263                    }
264                }
265                player.sendMessage(
266                        TranslatableCaption.of("cluster.cluster_created"),
267                        TagResolver.resolver("name", Tag.inserting(Component.text(name)))
268                );
269                return true;
270            }
271            case "disband", "del", "delete" -> {
272                if (!player.hasPermission(Permission.PERMISSION_CLUSTER_DELETE)) {
273                    player.sendMessage(
274                            TranslatableCaption.of("permission.no_permission"),
275                            TagResolver.resolver(
276                                    "node",
277                                    Tag.inserting(Permission.PERMISSION_CLUSTER_DELETE)
278                            )
279                    );
280                    return false;
281                }
282                if (args.length != 1 && args.length != 2) {
283                    player.sendMessage(
284                            TranslatableCaption.of("commandconfig.command_syntax"),
285                            TagResolver.resolver("value", Tag.inserting(Component.text("/plot cluster delete [name]")))
286                    );
287                    return false;
288                }
289                PlotArea area = player.getApplicablePlotArea();
290                if (area == null) {
291                    player.sendMessage(TranslatableCaption.of("errors.not_in_plot_world"));
292                    return false;
293                }
294                PlotCluster cluster;
295                if (args.length == 2) {
296                    cluster = area.getCluster(args[1]);
297                    if (cluster == null) {
298                        player.sendMessage(
299                                TranslatableCaption.of("cluster.invalid_cluster_name"),
300                                TagResolver.resolver("cluster", Tag.inserting(Component.text(args[1])))
301                        );
302                        return false;
303                    }
304                } else {
305                    cluster = area.getCluster(player.getLocation());
306                    if (cluster == null) {
307                        player.sendMessage(TranslatableCaption.of("errors.not_in_cluster"));
308                        return false;
309                    }
310                }
311                if (!cluster.owner.equals(player.getUUID())) {
312                    if (!player.hasPermission(Permission.PERMISSION_CLUSTER_DELETE_OTHER)) {
313                        player.sendMessage(
314                                TranslatableCaption.of("permission.no_permission"),
315                                TagResolver.resolver(
316                                        "node",
317                                        Tag.inserting(Permission.PERMISSION_CLUSTER_DELETE_OTHER)
318                                )
319                        );
320                        return false;
321                    }
322                }
323                DBFunc.delete(cluster);
324                player.sendMessage(TranslatableCaption.of("cluster.cluster_deleted"), TagResolver.resolver(
325                        "cluster",
326                        Tag.inserting(Component.text(cluster.toString()))
327                ));
328                return true;
329            }
330            case "res", "resize" -> {
331                if (!player.hasPermission(Permission.PERMISSION_CLUSTER_RESIZE)) {
332                    player.sendMessage(
333                            TranslatableCaption.of("permission.no_permission"),
334                            TagResolver.resolver(
335                                    "node",
336                                    Tag.inserting(Permission.PERMISSION_CLUSTER_RESIZE)
337                            )
338                    );
339                    return false;
340                }
341                if (args.length != 3) {
342                    player.sendMessage(
343                            TranslatableCaption.of("commandconfig.command_syntax"),
344                            TagResolver.resolver("value", Tag.inserting(Component.text("/plot cluster resize [name]")))
345                    );
346                    return false;
347                }
348                PlotId pos1;
349                PlotId pos2;
350                // check pos1 / pos2
351                try {
352                    pos1 = PlotId.fromString(args[2]);
353                    pos2 = PlotId.fromString(args[3]);
354                } catch (IllegalArgumentException ignored) {
355                    player.sendMessage(TranslatableCaption.of("invalid.not_valid_plot_id"));
356                    return false;
357                }
358                if (pos2.getX() < pos1.getX() || pos2.getY() < pos1.getY()) {
359                    pos1 = PlotId.of(Math.min(pos1.getX(), pos2.getX()), Math.min(pos1.getY(), pos2.getY()));
360                    pos2 = PlotId.of(Math.max(pos1.getX(), pos2.getX()), Math.max(pos1.getY(), pos2.getY()));
361                }
362                // check if in cluster
363                PlotArea area = player.getApplicablePlotArea();
364                if (area == null) {
365                    player.sendMessage(TranslatableCaption.of("errors.not_in_plot_world"));
366                    return false;
367                }
368                PlotCluster cluster = area.getCluster(player.getLocation());
369                if (cluster == null) {
370                    player.sendMessage(TranslatableCaption.of("errors.not_in_cluster"));
371                    return false;
372                }
373                if (!cluster.hasHelperRights(player.getUUID())) {
374                    if (!player.hasPermission(Permission.PERMISSION_CLUSTER_RESIZE_OTHER)) {
375                        player.sendMessage(
376                                TranslatableCaption.of("permission.no_permission"),
377                                TagResolver.resolver(
378                                        "node",
379                                        Tag.inserting(Permission.PERMISSION_CLUSTER_RESIZE_OTHER)
380                                )
381                        );
382                        return false;
383                    }
384                }
385                //check if overlap
386                PlotCluster intersect = area.getFirstIntersectingCluster(pos1, pos2);
387                if (intersect != null) {
388                    player.sendMessage(
389                            TranslatableCaption.of("cluster.cluster_intersection"),
390                            TagResolver.resolver("cluster", Tag.inserting(Component.text(intersect.getName())))
391                    );
392                    return false;
393                }
394                Set<Plot> existing = area.getPlotSelectionOwned(cluster.getP1(), cluster.getP2());
395                Set<Plot> newPlots = area.getPlotSelectionOwned(pos1, pos2);
396                Set<Plot> removed = new HashSet<>(existing);
397
398                removed.removeAll(newPlots);
399                // Check expand / shrink
400                if (!removed.isEmpty()) {
401                    if (!player.hasPermission(Permission.PERMISSION_CLUSTER_RESIZE_SHRINK)) {
402                        player.sendMessage(
403                                TranslatableCaption.of("permission.no_permission"),
404                                TagResolver.resolver(
405                                        "node",
406                                        Tag.inserting(Permission.PERMISSION_CLUSTER_RESIZE_SHRINK)
407                                )
408                        );
409                        return false;
410                    }
411                }
412                newPlots.removeAll(existing);
413                if (!newPlots.isEmpty()) {
414                    if (!player.hasPermission(Permission.PERMISSION_CLUSTER_RESIZE_EXPAND)) {
415                        player.sendMessage(
416                                TranslatableCaption.of("permission.no_permission"),
417                                TagResolver.resolver(
418                                        "node",
419                                        Tag.inserting(Permission.PERMISSION_CLUSTER_RESIZE_EXPAND)
420                                )
421                        );
422                        return false;
423                    }
424                }
425                // Check allowed cluster size
426                int current;
427                if (Settings.Limit.GLOBAL) {
428                    current = player.getPlayerClusterCount();
429                } else {
430                    current = player.getPlayerClusterCount(player.getLocation().getWorldName());
431                }
432                current -= cluster.getArea() + (1 + pos2.getX() - pos1.getX()) * (1 + pos2.getY() - pos1.getY());
433                int allowed = player.hasPermissionRange(
434                        Permission.PERMISSION_CLUSTER,
435                        Settings.Limit.MAX_PLOTS
436                );
437                if (current + cluster.getArea() > allowed) {
438                    player.sendMessage(
439                            TranslatableCaption.of("permission.no_permission"),
440                            TagResolver.resolver("node", Tag.inserting(Component.text(
441                                    Permission.PERMISSION_CLUSTER + "." + (current + cluster.getArea())
442                            )))
443                    );
444                    return false;
445                }
446                // resize cluster
447                DBFunc.resizeCluster(cluster, pos1, pos2);
448                player.sendMessage(TranslatableCaption.of("cluster.cluster_resized"));
449                return true;
450            }
451            case "add", "inv", "invite" -> {
452                if (!player.hasPermission(Permission.PERMISSION_CLUSTER_INVITE)) {
453                    player.sendMessage(
454                            TranslatableCaption.of("permission.no_permission"),
455                            TagResolver.resolver(
456                                    "node",
457                                    Tag.inserting(Permission.PERMISSION_CLUSTER_INVITE)
458                            )
459                    );
460                    return false;
461                }
462                if (args.length != 2) {
463                    player.sendMessage(
464                            TranslatableCaption.of("commandconfig.command_syntax"),
465                            TagResolver.resolver("value", Tag.inserting(Component.text("/plot cluster invite <player>")))
466                    );
467                    return false;
468                }
469                // check if in cluster
470                PlotArea area = player.getApplicablePlotArea();
471                if (area == null) {
472                    player.sendMessage(TranslatableCaption.of("errors.not_in_plot_world"));
473                }
474                PlotCluster cluster = area.getCluster(player.getLocation());
475                if (cluster == null) {
476                    player.sendMessage(TranslatableCaption.of("errors.not_in_cluster"));
477                    return false;
478                }
479                if (!cluster.hasHelperRights(player.getUUID())) {
480                    if (!player.hasPermission(Permission.PERMISSION_CLUSTER_INVITE_OTHER)) {
481                        player.sendMessage(
482                                TranslatableCaption.of("permission.no_permission"),
483                                TagResolver.resolver(
484                                        "node",
485                                        Tag.inserting(Permission.PERMISSION_CLUSTER_INVITE_OTHER)
486                                )
487                        );
488                        return false;
489                    }
490                }
491
492                PlotSquared.get().getImpromptuUUIDPipeline()
493                        .getSingle(args[1], (uuid, throwable) -> {
494                            if (throwable instanceof TimeoutException) {
495                                player.sendMessage(TranslatableCaption.of("players.fetching_players_timeout"));
496                            } else if (throwable != null) {
497                                player.sendMessage(
498                                        TranslatableCaption.of("errors.invalid_player"),
499                                        TagResolver.resolver("value", Tag.inserting(Component.text(args[1])))
500                                );
501                            } else {
502                                if (!cluster.isAdded(uuid)) {
503                                    // add the user if not added
504                                    cluster.invited.add(uuid);
505                                    DBFunc.setInvited(cluster, uuid);
506                                    final PlotPlayer<?> otherPlayer =
507                                            PlotSquared.platform().playerManager().getPlayerIfExists(uuid);
508                                    if (otherPlayer != null) {
509                                        player.sendMessage(
510                                                TranslatableCaption.of("cluster.cluster_invited"),
511                                                TagResolver.resolver("cluster", Tag.inserting(Component.text(cluster.getName())))
512                                        );
513                                    }
514                                }
515                                player.sendMessage(TranslatableCaption.of("cluster.cluster_added_user"));
516                            }
517                        });
518                return true;
519            }
520            case "k", "remove", "kick" -> {
521                if (!player.hasPermission(Permission.PERMISSION_CLUSTER_KICK)) {
522                    player.sendMessage(
523                            TranslatableCaption.of("permission.no_permission"),
524                            TagResolver.resolver(
525                                    "node",
526                                    Tag.inserting(Permission.PERMISSION_CLUSTER_KICK)
527                            )
528                    );
529                    return false;
530                }
531                if (args.length != 2) {
532                    player.sendMessage(
533                            TranslatableCaption.of("commandconfig.command_syntax"),
534                            TagResolver.resolver("value", Tag.inserting(Component.text("/plot cluster kick <player>")))
535                    );
536                    return false;
537                }
538                PlotArea area = player.getApplicablePlotArea();
539                if (area == null) {
540                    player.sendMessage(TranslatableCaption.of("errors.not_in_plot_world"));
541                }
542                PlotCluster cluster = area.getCluster(player.getLocation());
543                if (cluster == null) {
544                    player.sendMessage(TranslatableCaption.of("errors.not_in_cluster"));
545                    return false;
546                }
547                if (!cluster.hasHelperRights(player.getUUID())) {
548                    if (!player.hasPermission(Permission.PERMISSION_CLUSTER_KICK_OTHER)) {
549                        player.sendMessage(
550                                TranslatableCaption.of("permission.no_permission"),
551                                TagResolver.resolver(
552                                        "node",
553                                        Tag.inserting(Permission.PERMISSION_CLUSTER_KICK_OTHER)
554                                )
555                        );
556                        return false;
557                    }
558                }
559                // check uuid
560                PlotSquared.get().getImpromptuUUIDPipeline()
561                        .getSingle(args[1], (uuid, throwable) -> {
562                            if (throwable instanceof TimeoutException) {
563                                player.sendMessage(TranslatableCaption.of("players.fetching_players_timeout"));
564                            } else if (throwable != null) {
565                                player.sendMessage(
566                                        TranslatableCaption.of("errors.invalid_player"),
567                                        TagResolver.resolver("value", Tag.inserting(Component.text(args[1])))
568                                );
569                            } else {
570                                // Can't kick if the player is yourself, the owner, or not added to the cluster
571                                if (uuid.equals(player.getUUID()) || uuid.equals(cluster.owner)
572                                        || !cluster.isAdded(uuid)) {
573                                    player.sendMessage(
574                                            TranslatableCaption.of("cluster.cannot_kick_player"),
575                                            TagResolver.resolver("value", Tag.inserting(Component.text(cluster.getName())))
576                                    );
577                                } else {
578                                    if (cluster.helpers.contains(uuid)) {
579                                        cluster.helpers.remove(uuid);
580                                        DBFunc.removeHelper(cluster, uuid);
581                                    }
582                                    cluster.invited.remove(uuid);
583                                    DBFunc.removeInvited(cluster, uuid);
584
585                                    final PlotPlayer<?> player2 =
586                                            PlotSquared.platform().playerManager().getPlayerIfExists(uuid);
587                                    if (player2 != null) {
588                                        player.sendMessage(
589                                                TranslatableCaption.of("cluster.cluster_removed"),
590                                                TagResolver.resolver("cluster", Tag.inserting(Component.text(cluster.getName())))
591                                        );
592                                    }
593                                    removePlayerPlots(cluster, uuid, player2.getLocation().getWorldName());
594                                    player.sendMessage(TranslatableCaption.of("cluster.cluster_kicked_user"));
595                                }
596                            }
597                        });
598                return true;
599            }
600            case "quit", "leave" -> {
601                if (!player.hasPermission(Permission.PERMISSION_CLUSTER_LEAVE)) {
602                    player.sendMessage(
603                            TranslatableCaption.of("permission.no_permission"),
604                            TagResolver.resolver(
605                                    "node",
606                                    Tag.inserting(Permission.PERMISSION_CLUSTER_LEAVE)
607                            )
608                    );
609                    return false;
610                }
611                if (args.length != 1 && args.length != 2) {
612                    player.sendMessage(
613                            TranslatableCaption.of("commandconfig.command_syntax"),
614                            TagResolver.resolver("value", Tag.inserting(Component.text("/plot cluster leave [name]")))
615                    );
616                    return false;
617                }
618                PlotArea area = player.getApplicablePlotArea();
619                if (area == null) {
620                    player.sendMessage(TranslatableCaption.of("errors.not_in_plot_world"));
621                }
622                PlotCluster cluster;
623                if (args.length == 2) {
624                    cluster = area.getCluster(args[1]);
625                    if (cluster == null) {
626                        player.sendMessage(
627                                TranslatableCaption.of("cluster.invalid_cluster_name"),
628                                TagResolver.resolver("cluster", Tag.inserting(Component.text(args[1])))
629                        );
630                        return false;
631                    }
632                } else {
633                    cluster = area.getCluster(player.getLocation());
634                    if (cluster == null) {
635                        player.sendMessage(TranslatableCaption.of("errors.not_in_cluster"));
636                        return false;
637                    }
638                }
639                UUID uuid = player.getUUID();
640                if (!cluster.isAdded(uuid)) {
641                    player.sendMessage(TranslatableCaption.of("cluster.cluster_not_added"));
642                    return false;
643                }
644                if (uuid.equals(cluster.owner)) {
645                    player.sendMessage(TranslatableCaption.of("cluster.cluster_cannot_leave"));
646                    return false;
647                }
648                if (cluster.helpers.contains(uuid)) {
649                    cluster.helpers.remove(uuid);
650                    DBFunc.removeHelper(cluster, uuid);
651                }
652                cluster.invited.remove(uuid);
653                DBFunc.removeInvited(cluster, uuid);
654                player.sendMessage(
655                        TranslatableCaption.of("cluster.cluster_removed"),
656                        TagResolver.resolver("cluster", Tag.inserting(Component.text(cluster.getName())))
657                );
658                removePlayerPlots(cluster, uuid, player.getLocation().getWorldName());
659                return true;
660            }
661            case "members" -> {
662                if (!player.hasPermission(Permission.PERMISSION_CLUSTER_HELPERS)) {
663                    player.sendMessage(
664                            TranslatableCaption.of("permission.no_permission"),
665                            TagResolver.resolver(
666                                    "node",
667                                    Tag.inserting(Component.text(Permission.PERMISSION_CLUSTER_HELPERS.toString()))
668                            )
669                    );
670                    return false;
671                }
672                if (args.length != 3) {
673                    player.sendMessage(
674                            TranslatableCaption.of("commandconfig.command_syntax"),
675                            TagResolver.resolver(
676                                    "value",
677                                    Tag.inserting(Component.text("/plot cluster members <add | remove> <player>"))
678                            )
679                    );
680                    return false;
681                }
682                PlotArea area = player.getApplicablePlotArea();
683                if (area == null) {
684                    player.sendMessage(TranslatableCaption.of("errors.not_in_plot_world"));
685                }
686                PlotCluster cluster = area.getCluster(player.getLocation());
687                if (cluster == null) {
688                    player.sendMessage(TranslatableCaption.of("errors.not_in_cluster"));
689                    return false;
690                }
691
692                PlotSquared.get().getImpromptuUUIDPipeline()
693                        .getSingle(args[2], (uuid, throwable) -> {
694                            if (throwable instanceof TimeoutException) {
695                                player.sendMessage(TranslatableCaption.of("players.fetching_players_timeout"));
696                            } else if (throwable != null) {
697                                player.sendMessage(
698                                        TranslatableCaption.of("errors.invalid_player"),
699                                        TagResolver.resolver("value", Tag.inserting(Component.text(args[2])))
700                                );
701                            } else {
702                                if (args[1].equalsIgnoreCase("add")) {
703                                    cluster.helpers.add(uuid);
704                                    DBFunc.setHelper(cluster, uuid);
705                                    player.sendMessage(TranslatableCaption.of("cluster.cluster_added_helper"));
706                                } else if (args[1].equalsIgnoreCase("remove")) {
707                                    cluster.helpers.remove(uuid);
708                                    DBFunc.removeHelper(cluster, uuid);
709                                    player.sendMessage(TranslatableCaption.of("cluster.cluster_removed_helper"));
710                                } else {
711                                    player.sendMessage(
712                                            TranslatableCaption.of("commandconfig.command_syntax"),
713                                            TagResolver.resolver("value", Tag.inserting(Component.text(
714                                                    "/plot cluster members <add | remove> <player>"
715                                            )))
716                                    );
717                                }
718                            }
719                        });
720                return true;
721            }
722            case "spawn", "home", "tp" -> {
723                if (!player.hasPermission(Permission.PERMISSION_CLUSTER_TP)) {
724                    player.sendMessage(
725                            TranslatableCaption.of("permission.no_permission"),
726                            TagResolver.resolver(
727                                    "node",
728                                    Tag.inserting(Permission.PERMISSION_CLUSTER_TP)
729                            )
730                    );
731                    return false;
732                }
733                if (args.length != 2) {
734                    player.sendMessage(
735                            TranslatableCaption.of("commandconfig.command_syntax"),
736                            TagResolver.resolver("value", Tag.inserting(Component.text("/plot cluster tp <name>")))
737                    );
738                    return false;
739                }
740                PlotArea area = player.getApplicablePlotArea();
741                if (area == null) {
742                    player.sendMessage(TranslatableCaption.of("errors.not_in_plot_world"));
743                    return false;
744                }
745                PlotCluster cluster = area.getCluster(args[1]);
746                if (cluster == null) {
747                    player.sendMessage(
748                            TranslatableCaption.of("cluster.invalid_cluster_name"),
749                            TagResolver.resolver("cluster", Tag.inserting(Component.text(args[1])))
750                    );
751                    return false;
752                }
753                UUID uuid = player.getUUID();
754                if (!cluster.isAdded(uuid)) {
755                    if (!player.hasPermission(Permission.PERMISSION_CLUSTER_TP_OTHER)) {
756                        player.sendMessage(
757                                TranslatableCaption.of("permission.no_permission"),
758                                TagResolver.resolver(
759                                        "node",
760                                        Tag.inserting(Permission.PERMISSION_CLUSTER_TP_OTHER)
761                                )
762                        );
763                        return false;
764                    }
765                }
766                cluster.getHome(home -> player.teleport(home, TeleportCause.COMMAND_CLUSTER_TELEPORT));
767                player.sendMessage(TranslatableCaption.of("cluster.cluster_teleporting"));
768                return true;
769            }
770            case "i", "info", "show", "information" -> {
771                if (!player.hasPermission(Permission.PERMISSION_CLUSTER_INFO)) {
772                    player.sendMessage(
773                            TranslatableCaption.of("permission.no_permission"),
774                            TagResolver.resolver(
775                                    "node",
776                                    Tag.inserting(Permission.PERMISSION_CLUSTER_TP)
777                            )
778                    );
779                    return false;
780                }
781                if (args.length != 1 && args.length != 2) {
782                    player.sendMessage(
783                            TranslatableCaption.of("commandconfig.command_syntax"),
784                            TagResolver.resolver("value", Tag.inserting(Component.text("/plot cluster info [name]")))
785                    );
786                }
787                PlotArea area = player.getApplicablePlotArea();
788                if (area == null) {
789                    player.sendMessage(TranslatableCaption.of("errors.not_in_plot_world"));
790                    return false;
791                }
792                PlotCluster cluster;
793                if (args.length == 2) {
794                    cluster = area.getCluster(args[1]);
795                    if (cluster == null) {
796                        player.sendMessage(
797                                TranslatableCaption.of("cluster.invalid_cluster_name"),
798                                TagResolver.resolver("cluster", Tag.inserting(Component.text(args[1])))
799                        );
800                        return false;
801                    }
802                } else {
803                    cluster = area.getCluster(player.getLocation());
804                    if (cluster == null) {
805                        player.sendMessage(TranslatableCaption.of("errors.not_in_cluster"));
806                        return false;
807                    }
808                }
809                String id = cluster.toString();
810
811                PlotSquared.get().getImpromptuUUIDPipeline()
812                        .getSingle(cluster.owner, (username, throwable) -> {
813                            if (throwable instanceof TimeoutException) {
814                                player.sendMessage(TranslatableCaption.of("players.fetching_players_timeout"));
815                            } else {
816                                final String owner;
817                                owner = Objects.requireNonNullElse(username, "unknown");
818                                String name = cluster.getName();
819                                String size = (cluster.getP2().getX() - cluster.getP1().getX() + 1) + "x" + (
820                                        cluster.getP2().getY() - cluster.getP1().getY() + 1);
821                                String rights = cluster.isAdded(player.getUUID()) + "";
822                                Caption message = TranslatableCaption.of("cluster.cluster_info");
823                                TagResolver resolver = TagResolver.builder()
824                                        .tag("id", Tag.inserting(Component.text(id)))
825                                        .tag("owner", Tag.inserting(Component.text(owner)))
826                                        .tag("name", Tag.inserting(Component.text(name)))
827                                        .tag("size", Tag.inserting(Component.text(size)))
828                                        .tag("rights", Tag.inserting(Component.text(rights)))
829                                        .build();
830                                player.sendMessage(message, resolver);
831                            }
832                        });
833                return true;
834            }
835            case "sh", "setspawn", "sethome" -> {
836                if (!player.hasPermission(Permission.PERMISSION_CLUSTER_SETHOME)) {
837                    player.sendMessage(
838                            TranslatableCaption.of("permission.no_permission"),
839                            TagResolver.resolver("node", Tag.inserting(Permission.PERMISSION_CLUSTER_SETHOME))
840                    );
841                    return false;
842                }
843                if (args.length != 1 && args.length != 2) {
844                    player.sendMessage(
845                            TranslatableCaption.of("commandconfig.command_syntax"),
846                            TagResolver.resolver("value", Tag.inserting(Component.text("/plot cluster sethome")))
847                    );
848                    return false;
849                }
850                PlotArea area = player.getApplicablePlotArea();
851                if (area == null) {
852                    player.sendMessage(TranslatableCaption.of("errors.not_in_plot_world"));
853                }
854                PlotCluster cluster = area.getCluster(player.getLocation());
855                if (cluster == null) {
856                    player.sendMessage(TranslatableCaption.of("errors.not_in_cluster"));
857                    return false;
858                }
859                if (!cluster.hasHelperRights(player.getUUID())) {
860                    if (!player.hasPermission(Permission.PERMISSION_CLUSTER_SETHOME_OTHER)) {
861                        player.sendMessage(
862                                TranslatableCaption.of("permission.no_permission"),
863                                TagResolver.resolver(
864                                        "node",
865                                        Tag.inserting(Permission.PERMISSION_CLUSTER_SETHOME_OTHER)
866                                )
867                        );
868                        return false;
869                    }
870                }
871                Location base = cluster.getClusterBottom();
872                Location relative = player.getLocation().subtract(base.getX(), 0, base.getZ());
873                BlockLoc blockloc = new BlockLoc(relative.getX(), relative.getY(), relative.getZ());
874                cluster.settings.setPosition(blockloc);
875                DBFunc.setPosition(
876                        cluster,
877                        relative.getX() + "," + relative.getY() + "," + relative.getZ()
878                );
879                player.sendMessage(TranslatableCaption.of("position.position_set"));
880                return true;
881            }
882        }
883        player.sendMessage(
884                TranslatableCaption.of("cluster.cluster_available_args"),
885                TagResolver.resolver("list", Tag.inserting(ComponentHelper.join(AVAILABLE_ARGS, SEPARATOR)))
886        );
887        return false;
888    }
889
890    private void removePlayerPlots(final PlotCluster cluster, final UUID uuid, final String world) {
891        for (final Plot plot : PlotQuery.newQuery().inWorld(world).ownedBy(uuid)) {
892            PlotCluster current = plot.getCluster();
893            if (current != null && current.equals(cluster)) {
894                if (plot.getOwners().size() == 1) {
895                    plot.unclaim();
896                } else {
897                    for (UUID newOwner : plot.getOwners()) {
898                        if (!newOwner.equals(uuid)) {
899                            plot.setOwner(newOwner);
900                            break;
901                        }
902                    }
903                }
904            }
905        }
906    }
907
908    @Override
909    public Collection<Command> tab(final PlotPlayer<?> player, final String[] args, final boolean space) {
910        if (args.length == 1) {
911            final List<String> completions = new LinkedList<>();
912            if (player.hasPermission(Permission.PERMISSION_CLUSTER_LIST)) {
913                completions.add("list");
914            }
915            if (player.hasPermission(Permission.PERMISSION_CLUSTER_CREATE)) {
916                completions.add("create");
917            }
918            if (player.hasPermission(Permission.PERMISSION_CLUSTER_DELETE)) {
919                completions.add("delete");
920            }
921            if (player.hasPermission(Permission.PERMISSION_CLUSTER_RESIZE)) {
922                completions.add("resize");
923            }
924            if (player.hasPermission(Permission.PERMISSION_CLUSTER_INVITE)) {
925                completions.add("invite");
926            }
927            if (player.hasPermission(Permission.PERMISSION_CLUSTER_KICK)) {
928                completions.add("kick");
929            }
930            if (player.hasPermission(Permission.PERMISSION_CLUSTER_KICK)) {
931                completions.add("leave");
932            }
933            if (player.hasPermission(Permission.PERMISSION_CLUSTER_HELPERS)) {
934                completions.add("members");
935            }
936            if (player.hasPermission(Permission.PERMISSION_CLUSTER_INFO)) {
937                completions.add("info");
938            }
939            if (player.hasPermission(Permission.PERMISSION_CLUSTER_TP)) {
940                completions.add("tp");
941            }
942            if (player.hasPermission(Permission.PERMISSION_CLUSTER_SETHOME)) {
943                completions.add("sethome");
944            }
945            final List<Command> commands = completions.stream().filter(completion -> completion
946                            .toLowerCase()
947                            .startsWith(args[0].toLowerCase()))
948                    .map(completion -> new Command(
949                            null,
950                            true,
951                            completion,
952                            "",
953                            RequiredType.NONE,
954                            CommandCategory.ADMINISTRATION
955                    ) {
956                    }).collect(Collectors.toCollection(LinkedList::new));
957            if (player.hasPermission(Permission.PERMISSION_CLUSTER) && args[0].length() > 0) {
958                commands.addAll(TabCompletions.completePlayers(player, args[0], Collections.emptyList()));
959            }
960            return commands;
961        }
962        return TabCompletions.completePlayers(player, String.join(",", args).trim(), Collections.emptyList());
963    }
964
965}