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.generator;
020
021import com.google.common.collect.Sets;
022import com.plotsquared.core.PlotSquared;
023import com.plotsquared.core.command.Template;
024import com.plotsquared.core.configuration.Settings;
025import com.plotsquared.core.inject.factory.ProgressSubscriberFactory;
026import com.plotsquared.core.location.Location;
027import com.plotsquared.core.player.PlotPlayer;
028import com.plotsquared.core.plot.Plot;
029import com.plotsquared.core.plot.PlotAreaTerrainType;
030import com.plotsquared.core.plot.PlotAreaType;
031import com.plotsquared.core.plot.PlotId;
032import com.plotsquared.core.queue.QueueCoordinator;
033import com.plotsquared.core.util.FileBytes;
034import com.plotsquared.core.util.FileUtils;
035import com.plotsquared.core.util.MathMan;
036import com.plotsquared.core.util.RegionManager;
037import com.plotsquared.core.util.WorldUtil;
038import com.sk89q.worldedit.function.pattern.Pattern;
039import com.sk89q.worldedit.regions.CuboidRegion;
040import com.sk89q.worldedit.world.biome.BiomeType;
041import com.sk89q.worldedit.world.block.BaseBlock;
042import com.sk89q.worldedit.world.block.BlockTypes;
043import org.checkerframework.checker.nullness.qual.NonNull;
044import org.checkerframework.checker.nullness.qual.Nullable;
045
046import java.io.File;
047import java.io.IOException;
048import java.nio.file.Files;
049import java.util.HashSet;
050import java.util.Objects;
051
052public class HybridPlotManager extends ClassicPlotManager {
053
054    public static boolean REGENERATIVE_CLEAR = true;
055
056    private final HybridPlotWorld hybridPlotWorld;
057    private final RegionManager regionManager;
058    private final ProgressSubscriberFactory subscriberFactory;
059
060    public HybridPlotManager(
061            final @NonNull HybridPlotWorld hybridPlotWorld,
062            final @NonNull RegionManager regionManager,
063            @NonNull ProgressSubscriberFactory subscriberFactory
064    ) {
065        super(hybridPlotWorld, regionManager);
066        this.hybridPlotWorld = hybridPlotWorld;
067        this.regionManager = regionManager;
068        this.subscriberFactory = subscriberFactory;
069    }
070
071    @Override
072    public void exportTemplate() throws IOException {
073        HashSet<FileBytes> files = Sets.newHashSet(new FileBytes(
074                Settings.Paths.TEMPLATES + "/tmp-data.yml",
075                Template.getBytes(hybridPlotWorld)
076        ));
077        String dir =
078                Settings.Paths.SCHEMATICS + File.separator + "GEN_ROAD_SCHEMATIC" + File.separator + hybridPlotWorld.getWorldName() + File.separator;
079        try {
080            File sideRoad = FileUtils.getFile(PlotSquared.platform().getDirectory(), dir + "sideroad.schem");
081            String newDir =
082                    Settings.Paths.SCHEMATICS + File.separator + "GEN_ROAD_SCHEMATIC" + File.separator + "__TEMP_DIR__" + File.separator;
083            if (sideRoad.exists()) {
084                files.add(new FileBytes(newDir + "sideroad.schem", Files.readAllBytes(sideRoad.toPath())));
085            }
086            File intersection = FileUtils.getFile(PlotSquared.platform().getDirectory(), dir + "intersection.schem");
087            if (intersection.exists()) {
088                files.add(new FileBytes(newDir + "intersection.schem", Files.readAllBytes(intersection.toPath())));
089            }
090            File plot = FileUtils.getFile(PlotSquared.platform().getDirectory(), dir + "plot.schem");
091            if (plot.exists()) {
092                files.add(new FileBytes(newDir + "plot.schem", Files.readAllBytes(plot.toPath())));
093            }
094        } catch (IOException e) {
095            e.printStackTrace();
096        }
097        Template.zipAll(hybridPlotWorld.getWorldName(), files);
098    }
099
100    @Override
101    public boolean createRoadEast(final @NonNull Plot plot, @Nullable QueueCoordinator queue) {
102        boolean enqueue = false;
103        if (queue == null) {
104            queue = hybridPlotWorld.getQueue();
105            enqueue = true;
106        }
107        super.createRoadEast(plot, queue);
108        PlotId id = plot.getId();
109        PlotId id2 = PlotId.of(id.getX() + 1, id.getY());
110        Location bot = getPlotBottomLocAbs(id2);
111        Location top = getPlotTopLocAbs(id);
112        Location pos1 = Location.at(
113                hybridPlotWorld.getWorldName(),
114                top.getX() + 1,
115                hybridPlotWorld.getMinGenHeight(),
116                bot.getZ() - 1
117        );
118        Location pos2 = Location.at(
119                hybridPlotWorld.getWorldName(),
120                bot.getX(),
121                hybridPlotWorld.getMaxGenHeight(),
122                top.getZ() + 1
123        );
124        this.resetBiome(hybridPlotWorld, pos1, pos2);
125        if (!hybridPlotWorld.ROAD_SCHEMATIC_ENABLED) {
126            return true;
127        }
128        createSchemAbs(queue, pos1, pos2, true);
129        return !enqueue || queue.enqueue();
130    }
131
132    private void resetBiome(
133            final @NonNull HybridPlotWorld hybridPlotWorld,
134            final @NonNull Location pos1,
135            final @NonNull Location pos2
136    ) {
137        BiomeType biome = hybridPlotWorld.getPlotBiome();
138        if (!Objects.equals(PlotSquared.platform().worldUtil()
139                .getBiomeSynchronous(
140                        hybridPlotWorld.getWorldName(),
141                        (pos1.getX() + pos2.getX()) / 2,
142                        (pos1.getZ() + pos2.getZ()) / 2
143                ), biome)) {
144            WorldUtil.setBiome(
145                    hybridPlotWorld.getWorldName(),
146                    new CuboidRegion(pos1.getBlockVector3(), pos2.getBlockVector3()),
147                    biome
148            );
149        }
150    }
151
152    private void createSchemAbs(
153            final @NonNull QueueCoordinator queue,
154            final @NonNull Location pos1,
155            final @NonNull Location pos2,
156            boolean isRoad
157    ) {
158        int size = hybridPlotWorld.SIZE;
159        int minY;
160        if ((isRoad && Settings.Schematics.PASTE_ROAD_ON_TOP) || (!isRoad && Settings.Schematics.PASTE_ON_TOP)) {
161            minY = hybridPlotWorld.SCHEM_Y;
162        } else {
163            minY = hybridPlotWorld.getMinBuildHeight();
164        }
165        int schemYDiff = (isRoad ? hybridPlotWorld.getRoadYStart() : hybridPlotWorld.getPlotYStart()) - minY;
166        BaseBlock airBlock = BlockTypes.AIR.getDefaultState().toBaseBlock();
167        for (int x = pos1.getX(); x <= pos2.getX(); x++) {
168            short absX = (short) ((x - hybridPlotWorld.ROAD_OFFSET_X) % size);
169            if (absX < 0) {
170                absX += size;
171            }
172            for (int z = pos1.getZ(); z <= pos2.getZ(); z++) {
173                short absZ = (short) ((z - hybridPlotWorld.ROAD_OFFSET_Z) % size);
174                if (absZ < 0) {
175                    absZ += size;
176                }
177                BaseBlock[] blocks = hybridPlotWorld.G_SCH.get(MathMan.pair(absX, absZ));
178                if (blocks != null) {
179                    for (int y = 0; y < blocks.length; y++) {
180                        if (blocks[y] != null) {
181                            queue.setBlock(x, minY + y, z, blocks[y]);
182                        } else if (y > schemYDiff) {
183                            // This is necessary, otherwise any blocks not specified in the schematic will remain after a clear.
184                            // This should only be done where the schematic has actually "started"
185                            queue.setBlock(x, minY + y, z, airBlock);
186                        } else if (isRoad) {
187                            queue.setBlock(x, minY + y, z, hybridPlotWorld.ROAD_BLOCK.toPattern());
188                        } else {
189                            queue.setBlock(x, minY + y, z, hybridPlotWorld.MAIN_BLOCK.toPattern());
190                        }
191                    }
192                }
193                BiomeType biome = hybridPlotWorld.G_SCH_B.get(MathMan.pair(absX, absZ));
194                if (biome != null) {
195                    queue.setBiome(x, z, biome);
196                } else {
197                    queue.setBiome(x, z, hybridPlotWorld.getPlotBiome());
198                }
199            }
200        }
201    }
202
203    @Override
204    public boolean createRoadSouth(final @NonNull Plot plot, @Nullable QueueCoordinator queue) {
205        boolean enqueue = false;
206        if (queue == null) {
207            enqueue = true;
208            queue = hybridPlotWorld.getQueue();
209        }
210        super.createRoadSouth(plot, queue);
211        PlotId id = plot.getId();
212        PlotId id2 = PlotId.of(id.getX(), id.getY() + 1);
213        Location bot = getPlotBottomLocAbs(id2);
214        Location top = getPlotTopLocAbs(id);
215        Location pos1 = Location.at(
216                hybridPlotWorld.getWorldName(),
217                bot.getX() - 1,
218                hybridPlotWorld.getMinGenHeight(),
219                top.getZ() + 1
220        );
221        Location pos2 = Location.at(
222                hybridPlotWorld.getWorldName(),
223                top.getX() + 1,
224                hybridPlotWorld.getMaxGenHeight(),
225                bot.getZ()
226        );
227        this.resetBiome(hybridPlotWorld, pos1, pos2);
228        if (!hybridPlotWorld.ROAD_SCHEMATIC_ENABLED) {
229            return true;
230        }
231        createSchemAbs(queue, pos1, pos2, true);
232        return !enqueue || queue.enqueue();
233    }
234
235    @Override
236    public boolean createRoadSouthEast(final @NonNull Plot plot, @Nullable QueueCoordinator queue) {
237        boolean enqueue = false;
238        if (queue == null) {
239            enqueue = true;
240            queue = hybridPlotWorld.getQueue();
241        }
242        super.createRoadSouthEast(plot, queue);
243        PlotId id = plot.getId();
244        PlotId id2 = PlotId.of(id.getX() + 1, id.getY() + 1);
245        Location pos1 = getPlotTopLocAbs(id).add(1, 0, 1);
246        Location pos2 = getPlotBottomLocAbs(id2);
247        createSchemAbs(queue, pos1, pos2, true);
248        if (hybridPlotWorld.ROAD_SCHEMATIC_ENABLED) {
249            createSchemAbs(queue, pos1, pos2, true);
250        }
251        return !enqueue || queue.enqueue();
252    }
253
254    @Override
255    public boolean clearPlot(
256            final @NonNull Plot plot,
257            final @Nullable Runnable whenDone,
258            @Nullable PlotPlayer<?> actor,
259            @Nullable QueueCoordinator queue
260    ) {
261        if (this.regionManager.notifyClear(this)) {
262            //If this returns false, the clear didn't work
263            if (this.regionManager.handleClear(plot, whenDone, this, actor)) {
264                return true;
265            }
266        }
267        final Location pos1 = plot.getBottomAbs();
268        final Location pos2 = plot.getExtendedTopAbs();
269        // If augmented
270        final boolean canRegen =
271                (hybridPlotWorld.getType() == PlotAreaType.AUGMENTED) && (hybridPlotWorld.getTerrain() != PlotAreaTerrainType.NONE) && REGENERATIVE_CLEAR;
272        // The component blocks
273        final Pattern plotfloor = hybridPlotWorld.TOP_BLOCK.toPattern();
274        final Pattern filling = hybridPlotWorld.MAIN_BLOCK.toPattern();
275
276        final Pattern bedrock;
277        if (hybridPlotWorld.PLOT_BEDROCK) {
278            bedrock = BlockTypes.BEDROCK.getDefaultState();
279        } else {
280            bedrock = hybridPlotWorld.MAIN_BLOCK.toPattern();
281        }
282
283        final BiomeType biome = hybridPlotWorld.getPlotBiome();
284        boolean enqueue = false;
285        if (queue == null) {
286            enqueue = true;
287            queue = hybridPlotWorld.getQueue();
288        }
289        if (actor != null && Settings.QUEUE.NOTIFY_PROGRESS) {
290            queue.addProgressSubscriber(subscriberFactory.createWithActor(actor));
291        }
292        if (whenDone != null) {
293            queue.setCompleteTask(whenDone);
294        }
295        if (!canRegen) {
296            if (hybridPlotWorld.getMinBuildHeight() < hybridPlotWorld.getMinGenHeight()) {
297                queue.setCuboid(
298                        pos1.withY(hybridPlotWorld.getMinBuildHeight()),
299                        pos2.withY(hybridPlotWorld.getMinGenHeight()),
300                        BlockTypes.AIR.getDefaultState()
301                );
302            }
303            queue.setCuboid(
304                    pos1.withY(hybridPlotWorld.getMinGenHeight()),
305                    pos2.withY(hybridPlotWorld.getMinGenHeight()),
306                    hybridPlotWorld.PLOT_BEDROCK ? bedrock : filling
307            );
308            // Each component has a different layer
309            queue.setCuboid(
310                    pos1.withY(hybridPlotWorld.getMinGenHeight() + 1),
311                    pos2.withY(hybridPlotWorld.PLOT_HEIGHT - 1),
312                    filling
313            );
314            queue.setCuboid(pos1.withY(hybridPlotWorld.PLOT_HEIGHT), pos2.withY(hybridPlotWorld.PLOT_HEIGHT), plotfloor);
315            queue.setCuboid(
316                    pos1.withY(hybridPlotWorld.PLOT_HEIGHT + 1),
317                    pos2.withY(hybridPlotWorld.getMaxGenHeight()),
318                    BlockTypes.AIR.getDefaultState()
319            );
320            if (hybridPlotWorld.getMaxGenHeight() < hybridPlotWorld.getMaxBuildHeight() - 1) {
321                queue.setCuboid(
322                        pos1.withY(hybridPlotWorld.getMaxGenHeight()),
323                        pos2.withY(hybridPlotWorld.getMaxBuildHeight() - 1),
324                        BlockTypes.AIR.getDefaultState()
325                );
326            }
327            queue.setBiomeCuboid(pos1, pos2, biome);
328        } else {
329            queue.setRegenRegion(new CuboidRegion(pos1.getBlockVector3(), pos2.getBlockVector3()));
330        }
331        pastePlotSchematic(queue, pos1, pos2);
332        return !enqueue || queue.enqueue();
333    }
334
335    public void pastePlotSchematic(
336            final @NonNull QueueCoordinator queue,
337            final @NonNull Location bottom,
338            final @NonNull Location top
339    ) {
340        if (!hybridPlotWorld.PLOT_SCHEMATIC) {
341            return;
342        }
343        createSchemAbs(queue, bottom, top, false);
344    }
345
346    /**
347     * Retrieves the location of where a sign should be for a plot.
348     *
349     * @param plot The plot
350     * @return The location where a sign should be
351     */
352    @Override
353    public Location getSignLoc(final @NonNull Plot plot) {
354        return hybridPlotWorld.getSignLocation(plot);
355    }
356
357    public HybridPlotWorld getHybridPlotWorld() {
358        return this.hybridPlotWorld;
359    }
360
361}