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