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.database.DBFunc;
025import com.plotsquared.core.events.PlayerClaimPlotEvent;
026import com.plotsquared.core.events.PlotMergeEvent;
027import com.plotsquared.core.events.Result;
028import com.plotsquared.core.location.Direction;
029import com.plotsquared.core.permissions.Permission;
030import com.plotsquared.core.player.MetaDataAccess;
031import com.plotsquared.core.player.PlayerMetaDataKeys;
032import com.plotsquared.core.player.PlotPlayer;
033import com.plotsquared.core.plot.Plot;
034import com.plotsquared.core.plot.PlotArea;
035import com.plotsquared.core.util.EconHandler;
036import com.plotsquared.core.util.EventDispatcher;
037import com.plotsquared.core.util.PlotExpression;
038import com.plotsquared.core.util.task.TaskManager;
039import net.kyori.adventure.text.Component;
040import net.kyori.adventure.text.minimessage.tag.Tag;
041import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
042import org.apache.logging.log4j.LogManager;
043import org.apache.logging.log4j.Logger;
044import org.checkerframework.checker.nullness.qual.NonNull;
045
046@CommandDeclaration(
047        command = "claim",
048        aliases = "c",
049        category = CommandCategory.CLAIMING,
050        requiredType = RequiredType.PLAYER, permission = "plots.claim",
051        usage = "/plot claim")
052public class Claim extends SubCommand {
053
054    private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + Claim.class.getSimpleName());
055
056    private final EventDispatcher eventDispatcher;
057    private final EconHandler econHandler;
058
059    @Inject
060    public Claim(
061            final @NonNull EventDispatcher eventDispatcher,
062            final @NonNull EconHandler econHandler
063    ) {
064        this.eventDispatcher = eventDispatcher;
065        this.econHandler = econHandler;
066    }
067
068    @Override
069    public boolean onCommand(final PlotPlayer<?> player, String[] args) {
070        String schematic = null;
071        if (args.length >= 1) {
072            schematic = args[0];
073        }
074        Plot plot = player.getCurrentPlot();
075        if (plot == null) {
076            player.sendMessage(TranslatableCaption.of("errors.not_in_plot"));
077            return false;
078        }
079        final PlayerClaimPlotEvent event = this.eventDispatcher.callClaim(player, plot, schematic);
080        schematic = event.getSchematic();
081        if (event.getEventResult() == Result.DENY) {
082            player.sendMessage(
083                    TranslatableCaption.of("events.event_denied"),
084                    TagResolver.resolver("value", Tag.inserting(Component.text("Claim")))
085            );
086            return true;
087        }
088        boolean force = event.getEventResult() == Result.FORCE;
089        int currentPlots = Settings.Limit.GLOBAL ?
090                player.getPlotCount() :
091                player.getPlotCount(plot.getWorldName());
092
093        final PlotArea area = plot.getArea();
094
095        try (final MetaDataAccess<Integer> metaDataAccess = player.accessPersistentMetaData(PlayerMetaDataKeys.PERSISTENT_GRANTED_PLOTS)) {
096            int grants = 0;
097            if (currentPlots >= player.getAllowedPlots() && !force) {
098                if (metaDataAccess.isPresent()) {
099                    grants = metaDataAccess.get().orElse(0);
100                    if (grants <= 0) {
101                        player.sendMessage(
102                                TranslatableCaption.of("permission.cant_claim_more_plots"),
103                                TagResolver.resolver("amount", Tag.inserting(Component.text(grants)))
104                        );
105                        metaDataAccess.remove();
106                    }
107                } else {
108                    player.sendMessage(
109                            TranslatableCaption.of("permission.cant_claim_more_plots"),
110                            TagResolver.resolver("amount", Tag.inserting(Component.text(player.getAllowedPlots())))
111                    );
112                    return false;
113                }
114            }
115
116            if (!plot.canClaim(player)) {
117                player.sendMessage(TranslatableCaption.of("working.plot_is_claimed"));
118                return false;
119            }
120            if (schematic != null && !schematic.isEmpty()) {
121                if (area.isSchematicClaimSpecify()) {
122                    if (!area.hasSchematic(schematic)) {
123                        player.sendMessage(
124                                TranslatableCaption.of("schematics.schematic_invalid_named"),
125                                TagResolver.builder()
126                                        .tag("schemname", Tag.inserting(Component.text(schematic)))
127                                        .tag("reason", Tag.inserting(Component.text("non-existent")))
128                                        .build()
129                        );
130                    }
131                    if (!player.hasPermission(Permission.PERMISSION_CLAIM_SCHEMATIC
132                            .format(schematic)) && !player.hasPermission(
133                            "plots.admin.command.schematic"
134                    ) && !force) {
135                        player.sendMessage(
136                                TranslatableCaption.of("permission.no_schematic_permission"),
137                                TagResolver.resolver("value", Tag.inserting(Component.text(schematic)))
138                        );
139                    }
140                }
141            }
142            if (this.econHandler.isEnabled(area) && !force && !player.hasPermission(Permission.PERMISSION_ADMIN_BYPASS_ECON)) {
143                PlotExpression costExr = area.getPrices().get("claim");
144                double cost = costExr.evaluate(currentPlots);
145                if (cost > 0d) {
146                    if (!this.econHandler.isSupported()) {
147                        player.sendMessage(TranslatableCaption.of("economy.vault_or_consumer_null"));
148                        return false;
149                    }
150                    if (this.econHandler.getMoney(player) < cost) {
151                        player.sendMessage(
152                                TranslatableCaption.of("economy.cannot_afford_plot"),
153                                TagResolver.builder()
154                                        .tag("money", Tag.inserting(Component.text(this.econHandler.format(cost))))
155                                        .tag(
156                                                "balance",
157                                                Tag.inserting(Component.text(this.econHandler.format(this.econHandler.getMoney(
158                                                        player))))
159                                        )
160                                        .build()
161                        );
162                        return false;
163                    }
164                    this.econHandler.withdrawMoney(player, cost);
165                    player.sendMessage(
166                            TranslatableCaption.of("economy.removed_balance"),
167                            TagResolver.builder()
168                                    .tag("money", Tag.inserting(Component.text(this.econHandler.format(cost))))
169                                    .tag(
170                                            "balance",
171                                            Tag.inserting(Component.text(this.econHandler.format(this.econHandler.getMoney(
172                                                    player))))
173                                    )
174                                    .build()
175                    );
176                }
177            }
178            if (grants > 0) {
179                if (grants == 1) {
180                    metaDataAccess.remove();
181                } else {
182                    metaDataAccess.set(grants - 1);
183                }
184                player.sendMessage(
185                        TranslatableCaption.of("economy.removed_granted_plot"),
186                        TagResolver.builder()
187                                .tag("used_grants", Tag.inserting(Component.text(grants - 1)))
188                                .tag("remaining_grants", Tag.inserting(Component.text(grants)))
189                                .build()
190                );
191            }
192        }
193        if (!player.hasPermission(Permission.PERMISSION_ADMIN_BYPASS_BORDER)) {
194            int border = area.getBorder(false);
195            if (border != Integer.MAX_VALUE && plot.getDistanceFromOrigin() > border && !force) {
196                player.sendMessage(TranslatableCaption.of("border.denied"));
197                return false;
198            }
199        }
200        plot.setOwnerAbs(player.getUUID());
201        final String finalSchematic = schematic;
202        DBFunc.createPlotSafe(plot, () -> {
203            try {
204                TaskManager.getPlatformImplementation().sync(() -> {
205                    if (!plot.claim(player, true, finalSchematic, false, false)) {
206                        LOGGER.info("Failed to claim plot {}", plot.getId().toCommaSeparatedString());
207                        player.sendMessage(TranslatableCaption.of("working.plot_not_claimed"));
208                        plot.setOwnerAbs(null);
209                    } else if (area.isAutoMerge()) {
210                        PlotMergeEvent mergeEvent = Claim.this.eventDispatcher
211                                .callMerge(plot, Direction.ALL, Integer.MAX_VALUE, player);
212                        if (mergeEvent.getEventResult() == Result.DENY) {
213                            player.sendMessage(
214                                    TranslatableCaption.of("events.event_denied"),
215                                    TagResolver.resolver("value", Tag.inserting(Component.text("Auto merge on claim")))
216                            );
217                        } else {
218                            if (plot.getPlotModificationManager().autoMerge(
219                                    mergeEvent.getDir(),
220                                    mergeEvent.getMax(),
221                                    player.getUUID(),
222                                    player,
223                                    true
224                            )) {
225                                eventDispatcher.callPostMerge(player, plot);
226                            }
227                        }
228                    }
229                    return null;
230                });
231            } catch (final Exception e) {
232                e.printStackTrace();
233            }
234        }, () -> {
235            LOGGER.info("Failed to add plot to database: {}", plot.getId().toCommaSeparatedString());
236            player.sendMessage(TranslatableCaption.of("working.plot_not_claimed"));
237            plot.setOwnerAbs(null);
238        });
239        return true;
240    }
241
242}