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 cloud.commandframework.services.ServicePipeline;
022import com.google.inject.Inject;
023import com.plotsquared.core.PlotSquared;
024import com.plotsquared.core.configuration.Settings;
025import com.plotsquared.core.configuration.caption.TranslatableCaption;
026import com.plotsquared.core.database.DBFunc;
027import com.plotsquared.core.events.PlayerAutoPlotEvent;
028import com.plotsquared.core.events.PlotAutoMergeEvent;
029import com.plotsquared.core.events.Result;
030import com.plotsquared.core.permissions.Permission;
031import com.plotsquared.core.permissions.PermissionHandler;
032import com.plotsquared.core.player.MetaDataAccess;
033import com.plotsquared.core.player.PlayerMetaDataKeys;
034import com.plotsquared.core.player.PlotPlayer;
035import com.plotsquared.core.plot.Plot;
036import com.plotsquared.core.plot.PlotArea;
037import com.plotsquared.core.plot.world.PlotAreaManager;
038import com.plotsquared.core.services.plots.AutoService;
039import com.plotsquared.core.util.EconHandler;
040import com.plotsquared.core.util.EventDispatcher;
041import com.plotsquared.core.util.PlotExpression;
042import com.plotsquared.core.util.task.AutoClaimFinishTask;
043import com.plotsquared.core.util.task.RunnableVal;
044import com.plotsquared.core.util.task.TaskManager;
045import io.leangen.geantyref.TypeToken;
046import net.kyori.adventure.text.minimessage.Template;
047import org.checkerframework.checker.nullness.qual.NonNull;
048import org.checkerframework.checker.nullness.qual.Nullable;
049
050import java.util.Collections;
051import java.util.Iterator;
052import java.util.List;
053import java.util.stream.Collectors;
054
055@CommandDeclaration(command = "auto",
056        permission = "plots.auto",
057        category = CommandCategory.CLAIMING,
058        requiredType = RequiredType.NONE,
059        aliases = "a",
060        usage = "/plot auto [length, width]")
061public class Auto extends SubCommand {
062
063    private final PlotAreaManager plotAreaManager;
064    private final EventDispatcher eventDispatcher;
065    private final EconHandler econHandler;
066    private final ServicePipeline servicePipeline;
067
068    @Inject
069    public Auto(
070            final @NonNull PlotAreaManager plotAreaManager,
071            final @NonNull EventDispatcher eventDispatcher,
072            final @NonNull EconHandler econHandler,
073            final @NonNull ServicePipeline servicePipeline
074    ) {
075        this.plotAreaManager = plotAreaManager;
076        this.eventDispatcher = eventDispatcher;
077        this.econHandler = econHandler;
078        this.servicePipeline = servicePipeline;
079        this.servicePipeline.registerServiceType(TypeToken.get(AutoService.class), new AutoService.DefaultAutoService());
080        final AutoService.MultiPlotService multiPlotService = new AutoService.MultiPlotService();
081        this.servicePipeline.registerServiceImplementation(AutoService.class, multiPlotService,
082                Collections.singletonList(multiPlotService)
083        );
084        final AutoService.SinglePlotService singlePlotService = new AutoService.SinglePlotService();
085        this.servicePipeline.registerServiceImplementation(AutoService.class, singlePlotService,
086                Collections.singletonList(singlePlotService)
087        );
088    }
089
090    public static boolean checkAllowedPlots(
091            PlotPlayer<?> player, PlotArea plotarea,
092            @Nullable Integer allowedPlots, int sizeX, int sizeZ
093    ) {
094        if (allowedPlots == null) {
095            allowedPlots = player.getAllowedPlots();
096        }
097        int currentPlots;
098        if (Settings.Limit.GLOBAL) {
099            currentPlots = player.getPlotCount();
100        } else {
101            currentPlots = player.getPlotCount(plotarea.getWorldName());
102        }
103        int diff = allowedPlots - currentPlots;
104        if (diff - sizeX * sizeZ < 0) {
105            try (final MetaDataAccess<Integer> metaDataAccess = player.accessPersistentMetaData(
106                    PlayerMetaDataKeys.PERSISTENT_GRANTED_PLOTS)) {
107                if (metaDataAccess.isPresent()) {
108                    int grantedPlots = metaDataAccess.get().orElse(0);
109                    if (diff < 0 && grantedPlots < sizeX * sizeZ) {
110                        player.sendMessage(
111                                TranslatableCaption.of("permission.cant_claim_more_plots"),
112                                Template.of("amount", String.valueOf(diff + grantedPlots))
113                        );
114                        return false;
115                    } else if (diff >= 0 && grantedPlots + diff < sizeX * sizeZ) {
116                        player.sendMessage(
117                                TranslatableCaption.of("permission.cant_claim_more_plots"),
118                                Template.of("amount", String.valueOf(diff + grantedPlots))
119                        );
120                        return false;
121                    } else {
122                        int left = grantedPlots + diff < 0 ? 0 : diff - sizeX * sizeZ;
123                        if (left == 0) {
124                            metaDataAccess.remove();
125                        } else {
126                            metaDataAccess.set(left);
127                        }
128                        player.sendMessage(
129                                TranslatableCaption.of("economy.removed_granted_plot"),
130                                Template.of("usedGrants", String.valueOf(grantedPlots - left)),
131                                Template.of("remainingGrants", String.valueOf(left))
132                        );
133                    }
134                } else {
135                    player.sendMessage(
136                            TranslatableCaption.of("permission.cant_claim_more_plots"),
137                            Template.of("amount", String.valueOf(player.getAllowedPlots())
138                            )
139                    );
140                    return false;
141                }
142            }
143        }
144        return true;
145    }
146
147    private void claimSingle(
148            final @NonNull PlotPlayer<?> player, final @NonNull Plot plot,
149            final @NonNull PlotArea plotArea, final @Nullable String schematic
150    ) {
151        try (final MetaDataAccess<Boolean> metaDataAccess =
152                     player.accessTemporaryMetaData(PlayerMetaDataKeys.TEMPORARY_AUTO)) {
153            metaDataAccess.set(true);
154        }
155        plot.setOwnerAbs(player.getUUID());
156
157        final RunnableVal<Plot> runnableVal = new RunnableVal<>() {
158            {
159                this.value = plot;
160            }
161
162            @Override
163            public void run(final Plot plot) {
164                try {
165                    TaskManager.getPlatformImplementation().sync(
166                            new AutoClaimFinishTask(player, plot, plotArea, schematic,
167                                    PlotSquared.get().getEventDispatcher()
168                            ));
169                } catch (final Exception e) {
170                    e.printStackTrace();
171                }
172            }
173        };
174
175        DBFunc.createPlotSafe(plot, runnableVal, () -> claimSingle(player, plot, plotArea, schematic));
176
177    }
178
179    @Override
180    public boolean onCommand(final PlotPlayer<?> player, String[] args) {
181        PlotArea plotarea = player.getApplicablePlotArea();
182        if (plotarea == null) {
183            final PermissionHandler permissionHandler = PlotSquared.platform().permissionHandler();
184            if (permissionHandler.hasCapability(
185                    PermissionHandler.PermissionHandlerCapability.PER_WORLD_PERMISSIONS)) {
186                for (final PlotArea area : this.plotAreaManager.getAllPlotAreas()) {
187                    if (player.hasPermission(area.getWorldName(), "plots.auto")) {
188                        if (plotarea != null) {
189                            plotarea = null;
190                            break;
191                        }
192                        plotarea = area;
193                    }
194                }
195            }
196            if (this.plotAreaManager.getAllPlotAreas().length == 1) {
197                plotarea = this.plotAreaManager.getAllPlotAreas()[0];
198            }
199            if (plotarea == null) {
200                player.sendMessage(TranslatableCaption.of("errors.not_in_plot_world"));
201                return false;
202            }
203        }
204        int sizeX = 1;
205        int sizeZ = 1;
206        String schematic = null;
207        boolean mega = false;
208        if (args.length > 0) {
209            try {
210                String[] split = args[0].split("[,;]");
211                if (split.length == 2) {
212                    sizeX = Integer.parseInt(split[0]);
213                    sizeZ = Integer.parseInt(split[1]);
214                } else {
215                    player.sendMessage(
216                            TranslatableCaption.of("commandconfig.command_syntax"),
217                            Template.of("value", getUsage())
218                    );
219                    return true;
220                }
221                if (sizeX < 1 || sizeZ < 1) {
222                    player.sendMessage(TranslatableCaption.of("error.plot_size_negative"));
223                    return true;
224                }
225                if (args.length > 1) {
226                    schematic = args[1];
227                }
228                mega = true;
229            } catch (NumberFormatException ignored) {
230                sizeX = 1;
231                sizeZ = 1;
232                schematic = args[0];
233            }
234        }
235        PlayerAutoPlotEvent event = this.eventDispatcher
236                .callAuto(player, plotarea, schematic, sizeX, sizeZ);
237        if (event.getEventResult() == Result.DENY) {
238            player.sendMessage(
239                    TranslatableCaption.of("events.event_denied"),
240                    Template.of("value", "Auto claim")
241            );
242            return true;
243        }
244        boolean force = event.getEventResult() == Result.FORCE;
245        sizeX = event.getSizeX();
246        sizeZ = event.getSizeZ();
247        schematic = event.getSchematic();
248        if (!force && mega && !player.hasPermission(Permission.PERMISSION_AUTO_MEGA)) {
249            player.sendMessage(
250                    TranslatableCaption.of("permission.no_permission"),
251                    Template.of("node", String.valueOf(Permission.PERMISSION_AUTO_MEGA))
252            );
253            return false;
254        }
255        if (!force && sizeX * sizeZ > Settings.Claim.MAX_AUTO_AREA) {
256            player.sendMessage(
257                    TranslatableCaption.of("permission.cant_claim_more_plots_num"),
258                    Template.of("amount", String.valueOf(Settings.Claim.MAX_AUTO_AREA))
259            );
260            return false;
261        }
262        final int allowed_plots = player.getAllowedPlots();
263        try (final MetaDataAccess<Boolean> metaDataAccess =
264                     player.accessTemporaryMetaData(PlayerMetaDataKeys.TEMPORARY_AUTO)) {
265            if (!force && (metaDataAccess.get().orElse(false) || !checkAllowedPlots(player,
266                    plotarea, allowed_plots, sizeX, sizeZ
267            ))) {
268                return false;
269            }
270        }
271
272        if (schematic != null && !schematic.isEmpty()) {
273            if (!plotarea.hasSchematic(schematic)) {
274                player.sendMessage(
275                        TranslatableCaption.of("schematics.schematic_invalid_named"),
276                        Template.of("schemname", schematic),
277                        Template.of("reason", "non-existent")
278                );
279                return true;
280            }
281            if (!force && !player.hasPermission(
282                    Permission.PERMISSION_CLAIM_SCHEMATIC.format(schematic)
283            ) && !player.hasPermission("plots.admin.command.schematic")) {
284                player.sendMessage(
285                        TranslatableCaption.of("permission.no_permission"),
286                        Template.of("node", "plots.claim.%s0")
287                );
288                return true;
289            }
290        }
291        if (this.econHandler != null && plotarea.useEconomy()) {
292            PlotExpression costExp = plotarea.getPrices().get("claim");
293            double cost = costExp.evaluate(Settings.Limit.GLOBAL ?
294                    player.getPlotCount() :
295                    player.getPlotCount(plotarea.getWorldName()));
296            cost = (sizeX * sizeZ) * cost;
297            if (cost > 0d) {
298                if (!this.econHandler.isSupported()) {
299                    player.sendMessage(TranslatableCaption.of("economy.vault_or_consumer_null"));
300                    return false;
301                }
302                if (!force && this.econHandler.getMoney(player) < cost) {
303                    player.sendMessage(
304                            TranslatableCaption.of("economy.cannot_afford_plot"),
305                            Template.of("money", this.econHandler.format(cost)),
306                            Template.of("balance", this.econHandler.format(this.econHandler.getMoney(player)))
307                    );
308                    return false;
309                }
310                this.econHandler.withdrawMoney(player, cost);
311                player.sendMessage(
312                        TranslatableCaption.of("economy.removed_balance"),
313                        Template.of("money", this.econHandler.format(cost))
314                );
315            }
316        }
317
318        List<Plot> plots = this.servicePipeline
319                .pump(new AutoService.AutoQuery(player, null, sizeX, sizeZ, plotarea))
320                .through(AutoService.class)
321                .getResult();
322
323        plots = this.eventDispatcher.callAutoPlotsChosen(player, plots).getPlots();
324
325        if (plots.isEmpty()) {
326            player.sendMessage(TranslatableCaption.of("errors.no_free_plots"));
327            return false;
328        } else if (plots.size() == 1) {
329            this.claimSingle(player, plots.get(0), plotarea, schematic);
330        } else {
331            final Iterator<Plot> plotIterator = plots.iterator();
332            while (plotIterator.hasNext()) {
333                Plot plot = plotIterator.next();
334                if (!plot.canClaim(player)) {
335                    continue;
336                }
337                plot.claim(player, !plotIterator.hasNext(), null, true, true);
338                eventDispatcher.callPostAuto(player, plot);
339            }
340            final PlotAutoMergeEvent mergeEvent = this.eventDispatcher.callAutoMerge(
341                    plots.get(0),
342                    plots.stream().map(Plot::getId).collect(Collectors.toList())
343            );
344            if (!force && mergeEvent.getEventResult() == Result.DENY) {
345                player.sendMessage(
346                        TranslatableCaption.of("events.event_denied"),
347                        Template.of("value", "Auto merge")
348                );
349                return false;
350            }
351            return plotarea.mergePlots(mergeEvent.getPlots(), true);
352        }
353        return true;
354    }
355
356}