001/*
002 * PlotSquared, a land and world management plugin for Minecraft.
003 * Copyright (C) IntellectualSites <https://intellectualsites.com>
004 * Copyright (C) IntellectualSites team and contributors
005 *
006 * This program is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 *
011 * This program is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
014 * GNU General Public License for more details.
015 *
016 * You should have received a copy of the GNU General Public License
017 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
018 */
019package com.plotsquared.core.command;
020
021import com.google.inject.Inject;
022import com.plotsquared.core.configuration.Settings;
023import com.plotsquared.core.configuration.caption.TranslatableCaption;
024import com.plotsquared.core.events.TeleportCause;
025import com.plotsquared.core.permissions.Permission;
026import com.plotsquared.core.player.PlotPlayer;
027import com.plotsquared.core.plot.Plot;
028import com.plotsquared.core.plot.PlotArea;
029import com.plotsquared.core.plot.PlotId;
030import com.plotsquared.core.plot.world.PlotAreaManager;
031import com.plotsquared.core.util.MathMan;
032import com.plotsquared.core.util.TabCompletions;
033import com.plotsquared.core.util.query.PlotQuery;
034import com.plotsquared.core.util.query.SortingStrategy;
035import com.plotsquared.core.util.task.RunnableVal2;
036import com.plotsquared.core.util.task.RunnableVal3;
037import net.kyori.adventure.text.Component;
038import net.kyori.adventure.text.minimessage.tag.Tag;
039import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
040import org.checkerframework.checker.nullness.qual.NonNull;
041
042import java.util.ArrayList;
043import java.util.Collection;
044import java.util.List;
045import java.util.concurrent.CompletableFuture;
046
047@CommandDeclaration(command = "home",
048        permission = "plots.home",
049        usage = "/plot home [<page> | <alias> | <area;x;y> | <area> <x;y> | <area> <page>]",
050        aliases = {"h"},
051        requiredType = RequiredType.PLAYER,
052        category = CommandCategory.TELEPORT)
053public class HomeCommand extends Command {
054
055    private final PlotAreaManager plotAreaManager;
056
057    @Inject
058    public HomeCommand(final @NonNull PlotAreaManager plotAreaManager) {
059        super(MainCommand.getInstance(), true);
060        this.plotAreaManager = plotAreaManager;
061    }
062
063    private void home(
064            final @NonNull PlotPlayer<?> player,
065            final @NonNull PlotQuery query, final int page,
066            final RunnableVal3<Command, Runnable, Runnable> confirm,
067            final RunnableVal2<Command, CommandResult> whenDone
068    ) {
069        List<Plot> plots = query.asList();
070        if (plots.isEmpty()) {
071            player.sendMessage(TranslatableCaption.of("invalid.found_no_plots"));
072            return;
073        } else if (plots.size() < page || page < 1) {
074            player.sendMessage(
075                    TranslatableCaption.of("invalid.number_not_in_range"),
076                    TagResolver.builder()
077                            .tag("min", Tag.inserting(Component.text(1)))
078                            .tag("max", Tag.inserting(Component.text(plots.size())))
079                            .build()
080            );
081            return;
082        }
083        Plot plot = plots.get(page - 1);
084        confirm.run(this, () -> plot.teleportPlayer(player, TeleportCause.COMMAND_HOME, result -> {
085            if (result) {
086                whenDone.run(this, CommandResult.SUCCESS);
087            } else {
088                whenDone.run(HomeCommand.this, CommandResult.FAILURE);
089            }
090        }), () -> whenDone.run(HomeCommand.this, CommandResult.FAILURE));
091    }
092
093    @NonNull
094    private PlotQuery query(final @NonNull PlotPlayer<?> player) {
095        // everything plots need to have in common here
096        return PlotQuery.newQuery().thatPasses(plot -> plot.isOwner(player.getUUID()));
097    }
098
099    @Override
100    public CompletableFuture<Boolean> execute(
101            PlotPlayer<?> player, String[] args,
102            RunnableVal3<Command, Runnable, Runnable> confirm,
103            RunnableVal2<Command, CommandResult> whenDone
104    ) throws CommandException {
105        // /plot home <number> (or page, whatever it's called)
106        // /plot home <alias>
107        // /plot home <[area;]x;y>
108        // /plot home <area> <x;y>
109        // /plot home <area> <page>
110        if (!player.hasPermission(Permission.PERMISSION_VISIT_OWNED) && !player.hasPermission(Permission.PERMISSION_HOME)) {
111            player.sendMessage(
112                    TranslatableCaption.of("permission.no_permission"),
113                    TagResolver.resolver("node", Tag.inserting(Component.text(Permission.PERMISSION_VISIT_OWNED.toString())))
114            );
115            return CompletableFuture.completedFuture(false);
116        }
117        if (args.length > 2) {
118            sendUsage(player);
119            return CompletableFuture.completedFuture(false);
120        }
121        PlotQuery query = query(player);
122        int page = 1; // page = index + 1
123        String identifier;
124        PlotArea plotArea;
125        boolean basePlotOnly = true;
126        switch (args.length) {
127            case 1 -> {
128                identifier = args[0];
129                if (MathMan.isInteger(identifier)) {
130                    try {
131                        page = Integer.parseInt(identifier);
132                    } catch (NumberFormatException ignored) {
133                        player.sendMessage(
134                                TranslatableCaption.of("invalid.not_a_number"),
135                                TagResolver.resolver("value", Tag.inserting(Component.text(identifier)))
136                        );
137                        return CompletableFuture.completedFuture(false);
138                    }
139                    sortBySettings(query, player);
140                    break;
141                }
142                // either plot id or alias
143                Plot fromId = Plot.getPlotFromString(player, identifier, false);
144                if (fromId != null && fromId.isOwner(player.getUUID())) {
145                    // it was a valid plot id
146                    basePlotOnly = false;
147                    query.withPlot(fromId);
148                    break;
149                }
150                // allow for plot home within a plot area
151                plotArea = this.plotAreaManager.getPlotAreaByString(args[0]);
152                if (plotArea != null) {
153                    query.inArea(plotArea);
154                    break;
155                }
156                // it wasn't a valid plot id, trying to find plot by alias
157                query.withAlias(identifier);
158            }
159            case 2 -> {
160                // we assume args[0] is a plot area and args[1] an identifier
161                plotArea = this.plotAreaManager.getPlotAreaByString(args[0]);
162                identifier = args[1];
163                if (plotArea == null) {
164                    // invalid command, therefore no plots
165                    query.noPlots();
166                    break;
167                }
168                query.inArea(plotArea);
169                if (MathMan.isInteger(identifier)) {
170                    // identifier is a page number
171                    try {
172                        page = Integer.parseInt(identifier);
173                    } catch (NumberFormatException ignored) {
174                        player.sendMessage(
175                                TranslatableCaption.of("invalid.not_a_number"),
176                                TagResolver.resolver("value", Tag.inserting(Component.text(identifier)))
177                        );
178                        return CompletableFuture.completedFuture(false);
179                    }
180                    query.withSortingStrategy(SortingStrategy.SORT_BY_CREATION);
181                    break;
182                }
183                // identifier needs to be a plot id then
184                PlotId id = PlotId.fromStringOrNull(identifier);
185                if (id == null) {
186                    // invalid command, therefore no plots
187                    query.noPlots();
188                    break;
189                }
190                // we can try to get this plot
191                Plot plot = plotArea.getPlot(id);
192                if (plot == null) {
193                    query.noPlots();
194                    break;
195                }
196                // as the query already filters by owner, this is fine
197                basePlotOnly = false;
198                query.withPlot(plot);
199            }
200            case 0 -> sortBySettings(query, player);
201        }
202        if (basePlotOnly) {
203            query.whereBasePlot();
204        }
205        home(player, query, page, confirm, whenDone);
206        return CompletableFuture.completedFuture(true);
207    }
208
209    private void sortBySettings(PlotQuery plotQuery, PlotPlayer<?> player) {
210        // Player may not be in a plot world when attempting to get to a plot home
211        PlotArea area = player.getApplicablePlotArea();
212        if (Settings.Teleport.PER_WORLD_VISIT && area != null) {
213            plotQuery.relativeToArea(area)
214                    .withSortingStrategy(SortingStrategy.SORT_BY_CREATION);
215        } else {
216            plotQuery.withSortingStrategy(SortingStrategy.SORT_BY_TEMP);
217        }
218    }
219
220    @Override
221    public Collection<Command> tab(PlotPlayer<?> player, String[] args, boolean space) {
222        final List<Command> completions = new ArrayList<>();
223        switch (args.length - 1) {
224            case 0 -> {
225                completions.addAll(
226                        TabCompletions.completeAreas(args[0]));
227                if (args[0].isEmpty()) {
228                    // if no input is given, only suggest 1 - 3
229                    completions.addAll(
230                            TabCompletions.asCompletions("1", "2", "3"));
231                    break;
232                }
233                // complete more numbers from the already given input
234                completions.addAll(
235                        TabCompletions.completeNumbers(args[0], 10, 999));
236            }
237            case 1 -> completions.addAll(
238                    TabCompletions.completeNumbers(args[1], 10, 999));
239        }
240        return completions;
241    }
242
243}