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(hybridPlotWorld.getWorldName(), new CuboidRegion(pos1.getBlockVector3(), pos2.getBlockVector3()), biome);
145        }
146    }
147
148    private void createSchemAbs(
149            final @NonNull QueueCoordinator queue,
150            final @NonNull Location pos1,
151            final @NonNull Location pos2,
152            boolean isRoad
153    ) {
154        int size = hybridPlotWorld.SIZE;
155        int minY;
156        if ((isRoad && Settings.Schematics.PASTE_ROAD_ON_TOP) || (!isRoad && Settings.Schematics.PASTE_ON_TOP)) {
157            minY = hybridPlotWorld.SCHEM_Y;
158        } else {
159            minY = hybridPlotWorld.getMinBuildHeight();
160        }
161        BaseBlock airBlock = BlockTypes.AIR.getDefaultState().toBaseBlock();
162        for (int x = pos1.getX(); x <= pos2.getX(); x++) {
163            short absX = (short) ((x - hybridPlotWorld.ROAD_OFFSET_X) % size);
164            if (absX < 0) {
165                absX += size;
166            }
167            for (int z = pos1.getZ(); z <= pos2.getZ(); z++) {
168                short absZ = (short) ((z - hybridPlotWorld.ROAD_OFFSET_Z) % size);
169                if (absZ < 0) {
170                    absZ += size;
171                }
172                BaseBlock[] blocks = hybridPlotWorld.G_SCH.get(MathMan.pair(absX, absZ));
173                if (blocks != null) {
174                    for (int y = 0; y < blocks.length; y++) {
175                        if (blocks[y] != null) {
176                            queue.setBlock(x, minY + y, z, blocks[y]);
177                        } else if (!isRoad) {
178                            // This is necessary, otherwise any blocks not specified in the schematic will remain after a clear
179                            //  Do not set air for road as this may cause cavernous roads when debugroadregen is used
180                            queue.setBlock(x, minY + y, z, airBlock);
181                        }
182                    }
183                }
184                BiomeType biome = hybridPlotWorld.G_SCH_B.get(MathMan.pair(absX, absZ));
185                if (biome != null) {
186                    queue.setBiome(x, z, biome);
187                } else {
188                    queue.setBiome(x, z, hybridPlotWorld.getPlotBiome());
189                }
190            }
191        }
192    }
193
194    @Override
195    public boolean createRoadSouth(final @NonNull Plot plot, @Nullable QueueCoordinator queue) {
196        boolean enqueue = false;
197        if (queue == null) {
198            enqueue = true;
199            queue = hybridPlotWorld.getQueue();
200        }
201        super.createRoadSouth(plot, queue);
202        PlotId id = plot.getId();
203        PlotId id2 = PlotId.of(id.getX(), id.getY() + 1);
204        Location bot = getPlotBottomLocAbs(id2);
205        Location top = getPlotTopLocAbs(id);
206        Location pos1 = Location.at(hybridPlotWorld.getWorldName(), bot.getX() - 1, hybridPlotWorld.getMinGenHeight(), top.getZ() + 1);
207        Location pos2 = Location.at(hybridPlotWorld.getWorldName(), top.getX() + 1, hybridPlotWorld.getMaxGenHeight(), bot.getZ());
208        this.resetBiome(hybridPlotWorld, pos1, pos2);
209        if (!hybridPlotWorld.ROAD_SCHEMATIC_ENABLED) {
210            return true;
211        }
212        createSchemAbs(queue, pos1, pos2, true);
213        return !enqueue || queue.enqueue();
214    }
215
216    @Override
217    public boolean createRoadSouthEast(final @NonNull Plot plot, @Nullable QueueCoordinator queue) {
218        boolean enqueue = false;
219        if (queue == null) {
220            enqueue = true;
221            queue = hybridPlotWorld.getQueue();
222        }
223        super.createRoadSouthEast(plot, queue);
224        PlotId id = plot.getId();
225        PlotId id2 = PlotId.of(id.getX() + 1, id.getY() + 1);
226        Location pos1 = getPlotTopLocAbs(id).add(1, 0, 1);
227        Location pos2 = getPlotBottomLocAbs(id2);
228        createSchemAbs(queue, pos1, pos2, true);
229        if (hybridPlotWorld.ROAD_SCHEMATIC_ENABLED) {
230            createSchemAbs(queue, pos1, pos2, true);
231        }
232        return !enqueue || queue.enqueue();
233    }
234
235    @Override
236    public boolean clearPlot(
237            final @NonNull Plot plot,
238            final @Nullable Runnable whenDone,
239            @Nullable PlotPlayer<?> actor,
240            @Nullable QueueCoordinator queue
241    ) {
242        if (this.regionManager.notifyClear(this)) {
243            //If this returns false, the clear didn't work
244            if (this.regionManager.handleClear(plot, whenDone, this, actor)) {
245                return true;
246            }
247        }
248        final Location pos1 = plot.getBottomAbs();
249        final Location pos2 = plot.getExtendedTopAbs();
250        // If augmented
251        final boolean canRegen =
252                (hybridPlotWorld.getType() == PlotAreaType.AUGMENTED) && (hybridPlotWorld.getTerrain() != PlotAreaTerrainType.NONE) && REGENERATIVE_CLEAR;
253        // The component blocks
254        final Pattern plotfloor = hybridPlotWorld.TOP_BLOCK.toPattern();
255        final Pattern filling = hybridPlotWorld.MAIN_BLOCK.toPattern();
256
257        final Pattern bedrock;
258        if (hybridPlotWorld.PLOT_BEDROCK) {
259            bedrock = BlockTypes.BEDROCK.getDefaultState();
260        } else {
261            bedrock = hybridPlotWorld.MAIN_BLOCK.toPattern();
262        }
263
264        final BiomeType biome = hybridPlotWorld.getPlotBiome();
265        boolean enqueue = false;
266        if (queue == null) {
267            enqueue = true;
268            queue = hybridPlotWorld.getQueue();
269        }
270        if (actor != null && Settings.QUEUE.NOTIFY_PROGRESS) {
271            queue.addProgressSubscriber(subscriberFactory.createWithActor(actor));
272        }
273        if (whenDone != null) {
274            queue.setCompleteTask(whenDone);
275        }
276        if (!canRegen) {
277            queue.setCuboid(
278                    pos1.withY(hybridPlotWorld.getMinGenHeight()),
279                    pos2.withY(hybridPlotWorld.getMinGenHeight()),
280                    hybridPlotWorld.PLOT_BEDROCK ? bedrock : filling
281            );
282            // Each component has a different layer
283            queue.setCuboid(
284                    pos1.withY(hybridPlotWorld.getMinGenHeight() + 1),
285                    pos2.withY(hybridPlotWorld.PLOT_HEIGHT - 1),
286                    filling
287            );
288            queue.setCuboid(pos1.withY(hybridPlotWorld.PLOT_HEIGHT), pos2.withY(hybridPlotWorld.PLOT_HEIGHT), plotfloor);
289            queue.setCuboid(
290                    pos1.withY(hybridPlotWorld.PLOT_HEIGHT + 1),
291                    pos2.withY(hybridPlotWorld.getMaxGenHeight()),
292                    BlockTypes.AIR.getDefaultState()
293            );
294            queue.setBiomeCuboid(pos1, pos2, biome);
295        } else {
296            queue.setRegenRegion(new CuboidRegion(pos1.getBlockVector3(), pos2.getBlockVector3()));
297        }
298        pastePlotSchematic(queue, pos1, pos2);
299        return !enqueue || queue.enqueue();
300    }
301
302    public void pastePlotSchematic(
303            final @NonNull QueueCoordinator queue,
304            final @NonNull Location bottom,
305            final @NonNull Location top
306    ) {
307        if (!hybridPlotWorld.PLOT_SCHEMATIC) {
308            return;
309        }
310        createSchemAbs(queue, bottom, top, false);
311    }
312
313    /**
314     * Retrieves the location of where a sign should be for a plot.
315     *
316     * @param plot The plot
317     * @return The location where a sign should be
318     */
319    @Override
320    public Location getSignLoc(final @NonNull Plot plot) {
321        return hybridPlotWorld.getSignLocation(plot);
322    }
323
324    public HybridPlotWorld getHybridPlotWorld() {
325        return this.hybridPlotWorld;
326    }
327
328}