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.PlotSquared;
023import com.plotsquared.core.configuration.caption.StaticCaption;
024import com.plotsquared.core.configuration.caption.TranslatableCaption;
025import com.plotsquared.core.location.Location;
026import com.plotsquared.core.player.PlotPlayer;
027import com.plotsquared.core.plot.Plot;
028import com.plotsquared.core.plot.world.PlotAreaManager;
029import com.plotsquared.core.queue.GlobalBlockQueue;
030import com.plotsquared.core.queue.QueueCoordinator;
031import com.plotsquared.core.util.RegionManager;
032import com.plotsquared.core.util.RegionUtil;
033import com.plotsquared.core.util.WorldUtil;
034import com.plotsquared.core.util.query.PlotQuery;
035import com.plotsquared.core.util.task.RunnableVal;
036import com.plotsquared.core.util.task.RunnableVal2;
037import com.plotsquared.core.util.task.TaskManager;
038import com.plotsquared.core.util.task.TaskTime;
039import com.sk89q.worldedit.math.BlockVector2;
040import com.sk89q.worldedit.regions.CuboidRegion;
041import org.apache.logging.log4j.LogManager;
042import org.apache.logging.log4j.Logger;
043import org.checkerframework.checker.nullness.qual.NonNull;
044
045import java.util.HashSet;
046import java.util.Iterator;
047import java.util.List;
048import java.util.Set;
049
050@CommandDeclaration(command = "trim",
051        permission = "plots.admin",
052        usage = "/plot trim <world> [regenerate]",
053        requiredType = RequiredType.CONSOLE,
054        category = CommandCategory.ADMINISTRATION)
055public class Trim extends SubCommand {
056
057    private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + Trim.class.getSimpleName());
058    private static volatile boolean TASK = false;
059
060    private final PlotAreaManager plotAreaManager;
061    private final WorldUtil worldUtil;
062    private final GlobalBlockQueue blockQueue;
063    private final RegionManager regionManager;
064
065    @Inject
066    public Trim(
067            final @NonNull PlotAreaManager plotAreaManager,
068            final @NonNull WorldUtil worldUtil,
069            final @NonNull GlobalBlockQueue blockQueue,
070            final @NonNull RegionManager regionManager
071    ) {
072        this.plotAreaManager = plotAreaManager;
073        this.worldUtil = worldUtil;
074        this.blockQueue = blockQueue;
075        this.regionManager = regionManager;
076    }
077
078    /**
079     * Runs the result task with the parameters (viable, nonViable).
080     *
081     * @param world  The world
082     * @param result (viable = .mcr to trim, nonViable = .mcr keep)
083     * @return success or not
084     */
085    public static boolean getTrimRegions(
086            String world,
087            final RunnableVal2<Set<BlockVector2>, Set<BlockVector2>> result
088    ) {
089        if (result == null) {
090            return false;
091        }
092        TranslatableCaption.of("trim.trim_starting");
093        final List<Plot> plots = PlotQuery.newQuery().inWorld(world).asList();
094        if (PlotSquared.platform().expireManager() != null) {
095            plots.removeAll(PlotSquared.platform().expireManager().getPendingExpired());
096        }
097        result.value1 = new HashSet<>(PlotSquared.platform().worldUtil().getChunkChunks(world));
098        result.value2 = new HashSet<>();
099        StaticCaption.of(" - MCA #: " + result.value1.size());
100        StaticCaption.of(" - CHUNKS: " + (result.value1.size() * 1024) + " (max)");
101        StaticCaption.of(" - TIME ESTIMATE: 12 Parsecs");
102        TaskManager.getPlatformImplementation().objectTask(plots, new RunnableVal<>() {
103            @Override
104            public void run(Plot plot) {
105                Location pos1 = plot.getCorners()[0];
106                Location pos2 = plot.getCorners()[1];
107                int ccx1 = pos1.getX() >> 9;
108                int ccz1 = pos1.getZ() >> 9;
109                int ccx2 = pos2.getX() >> 9;
110                int ccz2 = pos2.getZ() >> 9;
111                for (int x = ccx1; x <= ccx2; x++) {
112                    for (int z = ccz1; z <= ccz2; z++) {
113                        BlockVector2 loc = BlockVector2.at(x, z);
114                        if (result.value1.remove(loc)) {
115                            result.value2.add(loc);
116                        }
117                    }
118                }
119            }
120        }).thenAccept(ignore ->
121                TaskManager.getPlatformImplementation().taskLater(result, TaskTime.ticks(1L)));
122        return true;
123    }
124
125    @Override
126    public boolean onCommand(final PlotPlayer<?> player, String[] args) {
127        if (args.length == 0) {
128            sendUsage(player);
129            return false;
130        }
131        final String world = args[0];
132        if (!this.worldUtil.isWorld(world) || !this.plotAreaManager.hasPlotArea(world)) {
133            player.sendMessage(TranslatableCaption.of("errors.not_valid_world"));
134            return false;
135        }
136        if (Trim.TASK) {
137            player.sendMessage(TranslatableCaption.of("trim.trim_in_progress"));
138            return false;
139        }
140        Trim.TASK = true;
141        final boolean regen = args.length == 2 && Boolean.parseBoolean(args[1]);
142        getTrimRegions(world, new RunnableVal2<>() {
143            @Override
144            public void run(Set<BlockVector2> viable, final Set<BlockVector2> nonViable) {
145                Runnable regenTask;
146                if (regen) {
147                    LOGGER.info("Starting regen task");
148                    LOGGER.info(" - This is a VERY slow command");
149                    LOGGER.info(" - It will say 'Trim done!' when complete");
150                    regenTask = new Runnable() {
151                        @Override
152                        public void run() {
153                            if (nonViable.isEmpty()) {
154                                Trim.TASK = false;
155                                player.sendMessage(TranslatableCaption.of("trim.trim_done"));
156                                LOGGER.info("Trim done!");
157                                return;
158                            }
159                            Iterator<BlockVector2> iterator = nonViable.iterator();
160                            BlockVector2 mcr = iterator.next();
161                            iterator.remove();
162                            int cbx = mcr.getX() << 5;
163                            int cbz = mcr.getZ() << 5;
164                            // get all 1024 chunks
165                            HashSet<BlockVector2> chunks = new HashSet<>();
166                            for (int x = cbx; x < cbx + 32; x++) {
167                                for (int z = cbz; z < cbz + 32; z++) {
168                                    BlockVector2 loc = BlockVector2.at(x, z);
169                                    chunks.add(loc);
170                                }
171                            }
172                            int bx = cbx << 4;
173                            int bz = cbz << 4;
174                            CuboidRegion region =
175                                    RegionUtil.createRegion(bx, bx + 511, 0, 0, bz, bz + 511);
176                            for (Plot plot : PlotQuery.newQuery().inWorld(world)) {
177                                Location bot = plot.getBottomAbs();
178                                Location top = plot.getExtendedTopAbs();
179                                CuboidRegion plotReg = RegionUtil
180                                        .createRegion(bot.getX(), top.getX(), 0, 0, bot.getZ(), top.getZ());
181                                if (!RegionUtil.intersects(region, plotReg)) {
182                                    continue;
183                                }
184                                for (int x = plotReg.getMinimumPoint().getX() >> 4;
185                                     x <= plotReg.getMaximumPoint().getX() >> 4; x++) {
186                                    for (int z = plotReg.getMinimumPoint().getZ() >> 4;
187                                         z <= plotReg.getMaximumPoint().getZ() >> 4; z++) {
188                                        BlockVector2 loc = BlockVector2.at(x, z);
189                                        chunks.remove(loc);
190                                    }
191                                }
192                            }
193                            final QueueCoordinator queue = blockQueue.getNewQueue(worldUtil.getWeWorld(world));
194                            TaskManager.getPlatformImplementation().objectTask(chunks, new RunnableVal<>() {
195                                @Override
196                                public void run(BlockVector2 value) {
197                                    queue.regenChunk(value.getX(), value.getZ());
198                                }
199                            }).thenAccept(ignore -> TaskManager.getPlatformImplementation()
200                                    .taskLater(this, TaskTime.ticks(1L)));
201                        }
202                    };
203                } else {
204                    regenTask = () -> {
205                        Trim.TASK = false;
206                        player.sendMessage(TranslatableCaption.of("trim.trim_done"));
207                        LOGGER.info("Trim done!");
208                    };
209                }
210                regionManager.deleteRegionFiles(world, viable, regenTask);
211
212            }
213        });
214        return true;
215    }
216
217}