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