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.Injector;
022import com.plotsquared.core.PlotSquared;
023import com.plotsquared.core.configuration.Settings;
024import com.plotsquared.core.configuration.caption.TranslatableCaption;
025import com.plotsquared.core.location.Location;
026import com.plotsquared.core.permissions.Permission;
027import com.plotsquared.core.player.ConsolePlayer;
028import com.plotsquared.core.player.MetaDataAccess;
029import com.plotsquared.core.player.PlayerMetaDataKeys;
030import com.plotsquared.core.player.PlotPlayer;
031import com.plotsquared.core.plot.Plot;
032import com.plotsquared.core.plot.PlotArea;
033import com.plotsquared.core.plot.world.SinglePlotArea;
034import com.plotsquared.core.util.EconHandler;
035import com.plotsquared.core.util.PlotExpression;
036import com.plotsquared.core.util.task.RunnableVal2;
037import com.plotsquared.core.util.task.RunnableVal3;
038import net.kyori.adventure.text.Component;
039import net.kyori.adventure.text.minimessage.tag.Tag;
040import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
041import org.apache.logging.log4j.LogManager;
042import org.apache.logging.log4j.Logger;
043
044import java.util.Arrays;
045import java.util.LinkedList;
046import java.util.List;
047import java.util.Objects;
048import java.util.Optional;
049import java.util.concurrent.CompletableFuture;
050
051import javax.annotation.Nonnull;
052import javax.annotation.Nullable;
053
054/**
055 * PlotSquared command class.
056 */
057@CommandDeclaration(command = "plot",
058        aliases = {"plots", "p", "plotsquared", "plot2", "p2", "ps", "2", "plotme", "plotz", "ap"})
059public class MainCommand extends Command {
060
061    private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + MainCommand.class.getSimpleName());
062
063    private static MainCommand instance;
064    public Help help;
065    public Toggle toggle;
066
067    private MainCommand() {
068        super(null, true);
069        instance = this;
070    }
071
072    public static MainCommand getInstance() {
073        if (instance == null) {
074            instance = new MainCommand();
075
076            final Injector injector = PlotSquared.platform().injector();
077            final List<Class<? extends Command>> commands = new LinkedList<>();
078            commands.add(Caps.class);
079            commands.add(Buy.class);
080            if (Settings.Web.LEGACY_WEBINTERFACE) {
081                LOGGER.warn("Legacy webinterface is used. Please note that it will be removed in future.");
082            }
083            commands.add(Load.class);
084            commands.add(Confirm.class);
085            commands.add(Template.class);
086            commands.add(Download.class);
087            commands.add(Setup.class);
088            commands.add(Area.class);
089            commands.add(DebugSaveTest.class);
090            commands.add(DebugLoadTest.class);
091            commands.add(CreateRoadSchematic.class);
092            commands.add(DebugAllowUnsafe.class);
093            commands.add(RegenAllRoads.class);
094            commands.add(Claim.class);
095            commands.add(Auto.class);
096            commands.add(HomeCommand.class);
097            commands.add(Visit.class);
098            commands.add(Set.class);
099            commands.add(Clear.class);
100            commands.add(Delete.class);
101            commands.add(Trust.class);
102            commands.add(Add.class);
103            commands.add(Leave.class);
104            commands.add(Deny.class);
105            commands.add(Remove.class);
106            commands.add(Info.class);
107            commands.add(Near.class);
108            commands.add(ListCmd.class);
109            commands.add(Debug.class);
110            commands.add(SchematicCmd.class);
111            commands.add(PluginCmd.class);
112            commands.add(Purge.class);
113            commands.add(Reload.class);
114            commands.add(Merge.class);
115            commands.add(DebugPaste.class);
116            commands.add(Unlink.class);
117            commands.add(Kick.class);
118            commands.add(Inbox.class);
119            commands.add(Comment.class);
120            commands.add(DatabaseCommand.class);
121            commands.add(Swap.class);
122            commands.add(Music.class);
123            commands.add(DebugRoadRegen.class);
124            commands.add(DebugExec.class);
125            commands.add(FlagCommand.class);
126            commands.add(Target.class);
127            commands.add(Move.class);
128            commands.add(Condense.class);
129            commands.add(Copy.class);
130            commands.add(Trim.class);
131            commands.add(Done.class);
132            commands.add(Continue.class);
133            commands.add(Middle.class);
134            commands.add(Grant.class);
135            commands.add(Owner.class);
136            commands.add(Desc.class);
137            commands.add(Biome.class);
138            commands.add(Alias.class);
139            commands.add(SetHome.class);
140            commands.add(Cluster.class);
141            commands.add(DebugImportWorlds.class);
142            commands.add(Backup.class);
143
144            if (Settings.Ratings.USE_LIKES) {
145                commands.add(Like.class);
146                commands.add(Dislike.class);
147            } else {
148                commands.add(Rate.class);
149            }
150
151            for (final Class<? extends Command> command : commands) {
152                try {
153                    injector.getInstance(command);
154                } catch (final Exception e) {
155                    LOGGER.error("Failed to register command {}", command.getCanonicalName(), e);
156                }
157            }
158
159            // Referenced commands
160            instance.toggle = injector.getInstance(Toggle.class);
161            instance.help = new Help(instance);
162        }
163        return instance;
164    }
165
166    public static boolean onCommand(final PlotPlayer<?> player, String... args) {
167        final EconHandler econHandler = PlotSquared.platform().econHandler();
168        if (args.length >= 1 && args[0].contains(":")) {
169            String[] split2 = args[0].split(":");
170            if (split2.length == 2) {
171                // Ref: c:v, this will push value to the last spot in the array
172                // ex. /p h:2 SomeUsername
173                // > /p h SomeUsername 2
174                String[] tmp = new String[args.length + 1];
175                tmp[0] = split2[0];
176                tmp[args.length] = split2[1];
177                if (args.length >= 2) {
178                    System.arraycopy(args, 1, tmp, 1, args.length - 1);
179                }
180                args = tmp;
181            }
182        }
183        try {
184            getInstance().execute(player, args, new RunnableVal3<>() {
185                @Override
186                public void run(final Command cmd, final Runnable success, final Runnable failure) {
187                    if (cmd.hasConfirmation(player)) {
188                        CmdConfirm.addPending(player, cmd.getUsage(), () -> {
189                            PlotArea area = player.getApplicablePlotArea();
190                            if (area != null && econHandler.isEnabled(area) && !player.hasPermission(Permission.PERMISSION_ADMIN_BYPASS_ECON)) {
191                                PlotExpression priceEval =
192                                        area.getPrices().get(cmd.getFullId());
193                                double price = priceEval != null ? priceEval.evaluate(0d) : 0d;
194                                if (econHandler.getMoney(player) < price) {
195                                    if (failure != null) {
196                                        failure.run();
197                                    }
198                                    return;
199                                }
200                            }
201                            if (success != null) {
202                                success.run();
203                            }
204                        });
205                        return;
206                    }
207                    PlotArea area = player.getApplicablePlotArea();
208                    if (area != null && econHandler.isEnabled(area) && !player.hasPermission(Permission.PERMISSION_ADMIN_BYPASS_ECON)) {
209                        PlotExpression priceEval = area.getPrices().get(cmd.getFullId());
210                        double price = priceEval != null ? priceEval.evaluate(0d) : 0d;
211                        if (price != 0d && econHandler.getMoney(player) < price) {
212                            if (failure != null) {
213                                failure.run();
214                            }
215                            return;
216                        }
217                    }
218                    if (success != null) {
219                        success.run();
220                    }
221                }
222            }, new RunnableVal2<>() {
223                @Override
224                public void run(Command cmd, CommandResult result) {
225                    // Post command stuff!?
226                }
227            }).thenAccept(result -> {
228                // TODO: Something with the command result
229            });
230        } catch (CommandException e) {
231            e.perform(player);
232        }
233        // Always true
234        return true;
235    }
236
237    @Override
238    public CompletableFuture<Boolean> execute(
239            final PlotPlayer<?> player, String[] args,
240            RunnableVal3<Command, Runnable, Runnable> confirm,
241            RunnableVal2<Command, CommandResult> whenDone
242    ) {
243        prepareArguments(new CommandExecutionData(player, args, confirm, whenDone, null))
244                .thenCompose(executionData -> {
245                    if (executionData.isEmpty()) {
246                        return CompletableFuture.completedFuture(false);
247                    }
248                    var data = executionData.get();
249                    try {
250                        return super.execute(data.player(), data.args(), data.confirm(), data.whenDone());
251                    } catch (CommandException e) {
252                        throw e;
253                    } catch (Throwable e) {
254                        LOGGER.error("A error occurred while executing plot command", e);
255                        String message = e.getMessage();
256                        if (message != null) {
257                            data.player().sendMessage(
258                                    TranslatableCaption.of("errors.error"),
259                                    TagResolver.resolver("value", Tag.inserting(Component.text(message)))
260                            );
261                        } else {
262                            data.player().sendMessage(
263                                    TranslatableCaption.of("errors.error_console"));
264                        }
265                    } finally {
266                        if (data.postCommandData() != null) {
267                            resetCommandScope(data.player(), data.postCommandData());
268                        }
269                    }
270                    return CompletableFuture.completedFuture(true);
271                });
272        return CompletableFuture.completedFuture(true);
273    }
274
275    private CompletableFuture<Optional<CommandExecutionData>> prepareArguments(CommandExecutionData data) {
276        if (data.args().length >= 2) {
277            PlotArea area = data.player().getApplicablePlotArea();
278            Plot newPlot = Plot.fromString(area, data.args()[0], data.player());
279            return preparePlotArgument(newPlot, data, area)
280                    .thenApply(d -> d.flatMap(x -> prepareFlagArgument(x, area)));
281        } else {
282            return CompletableFuture.completedFuture(Optional.of(data));
283        }
284    }
285
286    private CompletableFuture<Optional<CommandExecutionData>> preparePlotArgument(@Nullable Plot newPlot,
287                                                                        @Nonnull CommandExecutionData data,
288                                                                                  @Nullable PlotArea area) {
289        if (newPlot != null && (data.player() instanceof ConsolePlayer
290                || (area != null && area.equals(newPlot.getArea()))
291                || data.player().hasPermission(Permission.PERMISSION_ADMIN)
292                || data.player().hasPermission(Permission.PERMISSION_ADMIN_AREA_SUDO))
293                && !newPlot.isDenied(data.player().getUUID())) {
294            return fetchPlotCenterLocation(newPlot)
295                    .thenApply(newLoc -> {
296                        if (!data.player().canTeleport(newLoc)) {
297                            data.player().sendMessage(TranslatableCaption.of("border.denied"));
298                            return Optional.empty();
299                        }
300                        // Save meta
301                        var originalCommandMeta = setCommandScope(data.player(), new TemporaryCommandMeta(newLoc, newPlot));
302                        return Optional.of(new CommandExecutionData(
303                                data.player(),
304                                Arrays.copyOfRange(data.args(), 1, data.args().length), // Trimmed command
305                                data.confirm(),
306                                data.whenDone(),
307                                originalCommandMeta
308                        ));
309                    });
310        }
311        return CompletableFuture.completedFuture(Optional.of(data));
312    }
313
314    private Optional<CommandExecutionData> prepareFlagArgument(@Nonnull CommandExecutionData data, @Nonnull PlotArea area) {
315        if (data.args().length >= 2 && !data.args()[0].isEmpty() && data.args()[0].charAt(0) == '-') {
316            if ("f".equals(data.args()[0].substring(1))) {
317                return Optional.of(new CommandExecutionData(
318                        data.player(),
319                        Arrays.copyOfRange(data.args(), 1, data.args().length), // Trimmed command
320                        createForcedConfirmation(data.player(), area),
321                        data.whenDone(),
322                        data.postCommandData()
323                ));
324            } else {
325                data.player().sendMessage(TranslatableCaption.of("errors.invalid_command_flag"));
326                return Optional.empty();
327            }
328        }
329        return Optional.of(data);
330    }
331
332    private CompletableFuture<Location> fetchPlotCenterLocation(Plot plot) {
333        if (plot.getArea() instanceof SinglePlotArea && !plot.isLoaded()) {
334            return CompletableFuture.completedFuture(Location.at("", 0, 0, 0));
335        }
336        CompletableFuture<Location> future = new CompletableFuture<>();
337        plot.getCenter(future::complete);
338        return future;
339    }
340
341    private @Nonnull RunnableVal3<Command, Runnable, Runnable> createForcedConfirmation(@Nonnull PlotPlayer<?> player,
342                                                                                        @Nullable PlotArea area) {
343        return new RunnableVal3<>() {
344            @Override
345            public void run(Command cmd, Runnable success, Runnable failure) {
346                if (area != null && PlotSquared.platform().econHandler().isEnabled(area)
347                        && Optional.of(area.getPrices().get(cmd.getFullId()))
348                        .map(priceEval -> priceEval.evaluate(0d))
349                        .filter(price -> price != 0d)
350                        .filter(price -> PlotSquared.platform().econHandler().getMoney(player) < price)
351                        .isPresent()) {
352                    if (failure != null) {
353                        failure.run();
354                    }
355                    return;
356                }
357                if (success != null) {
358                    success.run();
359                }
360            }
361        };
362    }
363
364    private @Nonnull TemporaryCommandMeta setCommandScope(@Nonnull PlotPlayer<?> player, @Nonnull TemporaryCommandMeta commandMeta) {
365        Objects.requireNonNull(commandMeta.location());
366        Objects.requireNonNull(commandMeta.plot());
367        Location location;
368        Plot plot;
369        try (final MetaDataAccess<Location> locationMetaDataAccess
370                     = player.accessTemporaryMetaData(PlayerMetaDataKeys.TEMPORARY_LOCATION)) {
371            location = locationMetaDataAccess.get().orElse(null);
372            locationMetaDataAccess.set(commandMeta.location());
373        }
374        try (final MetaDataAccess<Plot> plotMetaDataAccess
375                     = player.accessTemporaryMetaData(PlayerMetaDataKeys.TEMPORARY_LAST_PLOT)) {
376            plot = plotMetaDataAccess.get().orElse(null);
377            plotMetaDataAccess.set(commandMeta.plot());
378        }
379        return new TemporaryCommandMeta(location, plot);
380    }
381
382    private void resetCommandScope(@Nonnull PlotPlayer<?> player, @Nonnull TemporaryCommandMeta commandMeta) {
383        try (final MetaDataAccess<Location> locationMetaDataAccess
384                     = player.accessTemporaryMetaData(PlayerMetaDataKeys.TEMPORARY_LOCATION)) {
385            if (commandMeta.location() == null) {
386                locationMetaDataAccess.remove();
387            } else {
388                locationMetaDataAccess.set(commandMeta.location());
389            }
390        }
391        try (final MetaDataAccess<Plot> plotMetaDataAccess
392                     = player.accessTemporaryMetaData(PlayerMetaDataKeys.TEMPORARY_LAST_PLOT)) {
393            if (commandMeta.plot() == null) {
394                plotMetaDataAccess.remove();
395            } else {
396                plotMetaDataAccess.set(commandMeta.plot());
397            }
398        }
399    }
400
401    private record CommandExecutionData(@Nonnull PlotPlayer<?> player, @Nonnull String[] args,
402                    @Nonnull RunnableVal3<Command, Runnable, Runnable> confirm,
403                    @Nonnull RunnableVal2<Command, CommandResult> whenDone,
404                    @Nullable TemporaryCommandMeta postCommandData) {}
405
406    private record TemporaryCommandMeta(@Nullable Location location, @Nullable Plot plot) {}
407
408    @Override
409    public boolean canExecute(PlotPlayer<?> player, boolean message) {
410        return true;
411    }
412
413}