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