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.base.Preconditions;
022import com.google.inject.Inject;
023import com.plotsquared.core.PlotSquared;
024import com.plotsquared.core.configuration.Settings;
025import com.plotsquared.core.inject.factory.HybridPlotWorldFactory;
026import com.plotsquared.core.location.Location;
027import com.plotsquared.core.plot.PlotArea;
028import com.plotsquared.core.plot.PlotId;
029import com.plotsquared.core.queue.ScopedQueueCoordinator;
030import com.plotsquared.core.util.MathMan;
031import com.sk89q.worldedit.entity.BaseEntity;
032import com.sk89q.worldedit.entity.Entity;
033import com.sk89q.worldedit.extent.Extent;
034import com.sk89q.worldedit.math.BlockVector3;
035import com.sk89q.worldedit.math.Vector3;
036import com.sk89q.worldedit.regions.CuboidRegion;
037import com.sk89q.worldedit.regions.RegionOperationException;
038import com.sk89q.worldedit.world.NullWorld;
039import com.sk89q.worldedit.world.biome.BiomeType;
040import com.sk89q.worldedit.world.block.BaseBlock;
041import com.sk89q.worldedit.world.block.BlockTypes;
042import org.checkerframework.checker.nullness.qual.NonNull;
043import org.checkerframework.checker.nullness.qual.Nullable;
044
045public class HybridGen extends IndependentPlotGenerator {
046
047    private static final CuboidRegion CHUNK = new CuboidRegion(BlockVector3.ZERO, BlockVector3.at(15, 396, 15));
048    private final HybridPlotWorldFactory hybridPlotWorldFactory;
049
050    @Inject
051    public HybridGen(final @NonNull HybridPlotWorldFactory hybridPlotWorldFactory) {
052        this.hybridPlotWorldFactory = hybridPlotWorldFactory;
053    }
054
055    @Override
056    public String getName() {
057        return PlotSquared.platform().pluginName();
058    }
059
060    private void placeSchem(
061            HybridPlotWorld world,
062            ScopedQueueCoordinator result,
063            short relativeX,
064            short relativeZ,
065            int x,
066            int z,
067            boolean isRoad,
068            boolean isPopulating
069    ) {
070        int minY; // Math.min(world.PLOT_HEIGHT, world.ROAD_HEIGHT);
071        if ((isRoad && Settings.Schematics.PASTE_ROAD_ON_TOP) || (!isRoad && Settings.Schematics.PASTE_ON_TOP)) {
072            minY = world.SCHEM_Y;
073        } else {
074            minY = world.getMinBuildHeight();
075        }
076        BaseBlock[] blocks = world.G_SCH.get(MathMan.pair(relativeX, relativeZ));
077        if (blocks != null) {
078            for (int y = 0; y < blocks.length; y++) {
079                if (blocks[y] != null) {
080                    if (!isPopulating || blocks[y].hasNbtData()) {
081                        result.setBlock(x, minY + y, z, blocks[y]);
082                    }
083                }
084            }
085        }
086        BiomeType biome = world.G_SCH_B.get(MathMan.pair(relativeX, relativeZ));
087        if (biome != null) {
088            result.setBiome(x, z, biome);
089        }
090    }
091
092    @Override
093    public void generateChunk(@NonNull ScopedQueueCoordinator result, @NonNull PlotArea settings) {
094        Preconditions.checkNotNull(result, "result cannot be null");
095        Preconditions.checkNotNull(settings, "settings cannot be null");
096
097        HybridPlotWorld hybridPlotWorld = (HybridPlotWorld) settings;
098        // Biome
099        result.fillBiome(hybridPlotWorld.getPlotBiome());
100        // Bedrock
101        if (hybridPlotWorld.PLOT_BEDROCK) {
102            for (short x = 0; x < 16; x++) {
103                for (short z = 0; z < 16; z++) {
104                    result.setBlock(x, hybridPlotWorld.getMinGenHeight(), z, BlockTypes.BEDROCK.getDefaultState());
105                }
106            }
107        }
108        // Coords
109        Location min = result.getMin();
110        int bx = min.getX() - hybridPlotWorld.ROAD_OFFSET_X;
111        int bz = min.getZ() - hybridPlotWorld.ROAD_OFFSET_Z;
112        // The relative X-coordinate (within the plot) of the minimum X coordinate
113        // contained in the scoped queue
114        short relativeOffsetX;
115        if (bx < 0) {
116            relativeOffsetX = (short) (hybridPlotWorld.SIZE + (bx % hybridPlotWorld.SIZE));
117        } else {
118            relativeOffsetX = (short) (bx % hybridPlotWorld.SIZE);
119        }
120        // The relative Z-coordinate (within the plot) of the minimum Z coordinate
121        // contained in the scoped queue
122        short relativeOffsetZ;
123        if (bz < 0) {
124            relativeOffsetZ = (short) (hybridPlotWorld.SIZE + (bz % hybridPlotWorld.SIZE));
125        } else {
126            relativeOffsetZ = (short) (bz % hybridPlotWorld.SIZE);
127        }
128        // The X-coordinate of a given X coordinate, relative to the
129        // plot (Counting from the corner with the least positive
130        // coordinates)
131        short[] relativeX = new short[16];
132        boolean[] insideRoadX = new boolean[16];
133        boolean[] insideWallX = new boolean[16];
134        short offsetX = relativeOffsetX;
135        for (short i = 0; i < 16; i++) {
136            if (offsetX >= hybridPlotWorld.SIZE) {
137                offsetX -= hybridPlotWorld.SIZE;
138            }
139            relativeX[i] = offsetX;
140            if (hybridPlotWorld.ROAD_WIDTH != 0) {
141                insideRoadX[i] = offsetX < hybridPlotWorld.PATH_WIDTH_LOWER || offsetX > hybridPlotWorld.PATH_WIDTH_UPPER;
142                insideWallX[i] = offsetX == hybridPlotWorld.PATH_WIDTH_LOWER || offsetX == hybridPlotWorld.PATH_WIDTH_UPPER;
143            }
144            offsetX++;
145        }
146        // The Z-coordinate of a given Z coordinate, relative to the
147        // plot (Counting from the corner with the least positive
148        // coordinates)
149        short[] relativeZ = new short[16];
150        boolean[] insideRoadZ = new boolean[16];
151        boolean[] insideWallZ = new boolean[16];
152        short offsetZ = relativeOffsetZ;
153        for (short i = 0; i < 16; i++) {
154            if (offsetZ >= hybridPlotWorld.SIZE) {
155                offsetZ -= hybridPlotWorld.SIZE;
156            }
157            relativeZ[i] = offsetZ;
158            if (hybridPlotWorld.ROAD_WIDTH != 0) {
159                insideRoadZ[i] = offsetZ < hybridPlotWorld.PATH_WIDTH_LOWER || offsetZ > hybridPlotWorld.PATH_WIDTH_UPPER;
160                insideWallZ[i] = offsetZ == hybridPlotWorld.PATH_WIDTH_LOWER || offsetZ == hybridPlotWorld.PATH_WIDTH_UPPER;
161            }
162            offsetZ++;
163        }
164        // generation
165        int startY = hybridPlotWorld.getMinGenHeight() + (hybridPlotWorld.PLOT_BEDROCK ? 1 : 0);
166        for (short x = 0; x < 16; x++) {
167            if (insideRoadX[x]) {
168                for (short z = 0; z < 16; z++) {
169                    // Road
170                    for (int y = startY; y <= hybridPlotWorld.ROAD_HEIGHT; y++) {
171                        result.setBlock(x, y, z, hybridPlotWorld.ROAD_BLOCK.toPattern());
172                    }
173                    if (hybridPlotWorld.ROAD_SCHEMATIC_ENABLED) {
174                        placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, true, false);
175                    }
176                }
177            } else if (insideWallX[x]) {
178                for (short z = 0; z < 16; z++) {
179                    if (insideRoadZ[z]) {
180                        // road
181                        for (int y = startY; y <= hybridPlotWorld.ROAD_HEIGHT; y++) {
182                            result.setBlock(x, y, z, hybridPlotWorld.ROAD_BLOCK.toPattern());
183                        }
184                        if (hybridPlotWorld.ROAD_SCHEMATIC_ENABLED) {
185                            placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, true, false);
186                        }
187                    } else {
188                        // wall
189                        for (int y = startY; y <= hybridPlotWorld.WALL_HEIGHT; y++) {
190                            result.setBlock(x, y, z, hybridPlotWorld.WALL_FILLING.toPattern());
191                        }
192                        if (!hybridPlotWorld.ROAD_SCHEMATIC_ENABLED) {
193                            if (hybridPlotWorld.PLACE_TOP_BLOCK) {
194                                result.setBlock(x, hybridPlotWorld.WALL_HEIGHT + 1, z, hybridPlotWorld.WALL_BLOCK.toPattern());
195                            }
196                        } else {
197                            placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, true, false);
198                        }
199                    }
200                }
201            } else {
202                for (short z = 0; z < 16; z++) {
203                    if (insideRoadZ[z]) {
204                        // road
205                        for (int y = startY; y <= hybridPlotWorld.ROAD_HEIGHT; y++) {
206                            result.setBlock(x, y, z, hybridPlotWorld.ROAD_BLOCK.toPattern());
207                        }
208                        if (hybridPlotWorld.ROAD_SCHEMATIC_ENABLED) {
209                            placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, true, false);
210                        }
211                    } else if (insideWallZ[z]) {
212                        // wall
213                        for (int y = startY; y <= hybridPlotWorld.WALL_HEIGHT; y++) {
214                            result.setBlock(x, y, z, hybridPlotWorld.WALL_FILLING.toPattern());
215                        }
216                        if (!hybridPlotWorld.ROAD_SCHEMATIC_ENABLED) {
217                            if (hybridPlotWorld.PLACE_TOP_BLOCK) {
218                                result.setBlock(x, hybridPlotWorld.WALL_HEIGHT + 1, z, hybridPlotWorld.WALL_BLOCK.toPattern());
219                            }
220                        } else {
221                            placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, true, false);
222                        }
223                    } else {
224                        // plot
225                        for (int y = startY; y < hybridPlotWorld.PLOT_HEIGHT; y++) {
226                            result.setBlock(x, y, z, hybridPlotWorld.MAIN_BLOCK.toPattern());
227                        }
228                        result.setBlock(x, hybridPlotWorld.PLOT_HEIGHT, z, hybridPlotWorld.TOP_BLOCK.toPattern());
229                        if (hybridPlotWorld.PLOT_SCHEMATIC) {
230                            placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, false, false);
231                        }
232                    }
233                }
234            }
235        }
236    }
237
238    @Override
239    public boolean populateChunk(final ScopedQueueCoordinator result, final PlotArea settings) {
240        HybridPlotWorld hybridPlotWorld = (HybridPlotWorld) settings;
241        if (!hybridPlotWorld.populationNeeded()) {
242            return false;
243        }
244        // Coords
245        Location min = result.getMin();
246        int bx = min.getX() - hybridPlotWorld.ROAD_OFFSET_X;
247        int bz = min.getZ() - hybridPlotWorld.ROAD_OFFSET_Z;
248        // The relative X-coordinate (within the plot) of the minimum X coordinate
249        // contained in the scoped queue
250        short relativeOffsetX;
251        if (bx < 0) {
252            relativeOffsetX = (short) (hybridPlotWorld.SIZE + (bx % hybridPlotWorld.SIZE));
253        } else {
254            relativeOffsetX = (short) (bx % hybridPlotWorld.SIZE);
255        }
256        // The relative Z-coordinate (within the plot) of the minimum Z coordinate
257        // contained in the scoped queue
258        short relativeOffsetZ;
259        if (bz < 0) {
260            relativeOffsetZ = (short) (hybridPlotWorld.SIZE + (bz % hybridPlotWorld.SIZE));
261        } else {
262            relativeOffsetZ = (short) (bz % hybridPlotWorld.SIZE);
263        }
264        boolean allRoad = true;
265        boolean overlap = false;
266
267        // The X-coordinate of a given X coordinate, relative to the
268        // plot (Counting from the corner with the least positive
269        // coordinates)
270        short[] relativeX = new short[16];
271        boolean[] insideRoadX = new boolean[16];
272        boolean[] insideWallX = new boolean[16];
273        short offsetX = relativeOffsetX;
274        for (short i = 0; i < 16; i++) {
275            if (offsetX >= hybridPlotWorld.SIZE) {
276                offsetX -= hybridPlotWorld.SIZE;
277                overlap = true;
278            }
279            relativeX[i] = offsetX;
280            if (hybridPlotWorld.ROAD_WIDTH != 0) {
281                boolean insideRoad = offsetX < hybridPlotWorld.PATH_WIDTH_LOWER || offsetX > hybridPlotWorld.PATH_WIDTH_UPPER;
282                boolean insideWall = offsetX == hybridPlotWorld.PATH_WIDTH_LOWER || offsetX == hybridPlotWorld.PATH_WIDTH_UPPER;
283                insideRoadX[i] = insideRoad;
284                insideWallX[i] = insideWall;
285                allRoad &= insideRoad && insideWall;
286            }
287            offsetX++;
288        }
289
290        // The Z-coordinate of a given Z coordinate, relative to the
291        // plot (Counting from the corner with the least positive
292        // coordinates)
293        short[] relativeZ = new short[16];
294        boolean[] insideRoadZ = new boolean[16];
295        boolean[] insideWallZ = new boolean[16];
296        short offsetZ = relativeOffsetZ;
297        for (short i = 0; i < 16; i++) {
298            if (offsetZ >= hybridPlotWorld.SIZE) {
299                offsetZ -= hybridPlotWorld.SIZE;
300                overlap = true;
301            }
302            relativeZ[i] = offsetZ;
303            if (hybridPlotWorld.ROAD_WIDTH != 0) {
304                boolean insideRoad = offsetZ < hybridPlotWorld.PATH_WIDTH_LOWER || offsetZ > hybridPlotWorld.PATH_WIDTH_UPPER;
305                boolean insideWall = offsetZ == hybridPlotWorld.PATH_WIDTH_LOWER || offsetZ == hybridPlotWorld.PATH_WIDTH_UPPER;
306                insideRoadZ[i] = insideRoad;
307                insideWallZ[i] = insideWall;
308                allRoad &= insideRoad && insideWall;
309            }
310            offsetZ++;
311        }
312        for (short x = 0; x < 16; x++) {
313            if (insideRoadX[x] || insideWallX[x]) {
314                if (hybridPlotWorld.ROAD_SCHEMATIC_ENABLED) {
315                    for (short z = 0; z < 16; z++) {
316                        placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, true, true);
317                    }
318                }
319            } else {
320                for (short z = 0; z < 16; z++) {
321                    if (insideRoadZ[z] || insideWallZ[z]) {
322                        if (hybridPlotWorld.ROAD_SCHEMATIC_ENABLED) {
323                            placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, true, true);
324                        }
325                    } else if (hybridPlotWorld.PLOT_SCHEMATIC) {
326                        placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, false, true);
327                    }
328                }
329            }
330        }
331        if (!allRoad && hybridPlotWorld.getPlotSchematicEntities() != null && !hybridPlotWorld
332                .getPlotSchematicEntities()
333                .isEmpty()) {
334            CuboidRegion region = CHUNK.clone();
335            try {
336                region.shift(hybridPlotWorld
337                        .getPlotSchematicMinPoint()
338                        .add(relativeOffsetX, 0, relativeOffsetZ)
339                        .subtract(hybridPlotWorld.PATH_WIDTH_LOWER + 1, 0, hybridPlotWorld.PATH_WIDTH_LOWER + 1));
340                for (Entity entity : hybridPlotWorld.getPlotSchematicEntities()) {
341                    if (region.contains(entity.getLocation().toVector().toBlockPoint())) {
342                        Vector3 pos = (entity.getLocation().toVector()
343                                .subtract(region.getMinimumPoint().withY(hybridPlotWorld.getPlotSchematicMinPoint().getY()).toVector3()))
344                                .add(min.getBlockVector3().withY(hybridPlotWorld.SCHEM_Y).toVector3());
345                        result.setEntity(new PopulatingEntity(
346                                entity,
347                                new com.sk89q.worldedit.util.Location(NullWorld.getInstance(), pos)
348                        ));
349                    }
350                }
351            } catch (RegionOperationException e) {
352                throw new RuntimeException(e);
353            }
354            if (overlap) {
355                try {
356                    region.shift(BlockVector3.at(-hybridPlotWorld.SIZE, 0, -hybridPlotWorld.SIZE));
357                    for (Entity entity : hybridPlotWorld.getPlotSchematicEntities()) {
358                        if (region.contains(entity.getLocation().toVector().toBlockPoint())) {
359                            result.setEntity(entity);
360                        }
361                    }
362                } catch (RegionOperationException e) {
363                    throw new RuntimeException(e);
364                }
365            }
366        }
367        return true;
368    }
369
370    @Override
371    public PlotArea getNewPlotArea(String world, String id, PlotId min, PlotId max) {
372        return this.hybridPlotWorldFactory.create(world, id, this, min, max);
373    }
374
375    @Override
376    public void initialize(PlotArea area) {
377        // All initialization is done in the PlotArea class
378    }
379
380    /**
381     * Wrapper to allow a WorldEdit {@link Entity} to effectively have a mutable location as the location in its NBT should be changed
382     * when set to the world.
383     *
384     * @since 6.9.0
385     */
386    private static final class PopulatingEntity implements Entity {
387
388        private final Entity parent;
389        private com.sk89q.worldedit.util.Location location;
390
391        /**
392         * @since 6.9.0
393         */
394        private PopulatingEntity(Entity parent, com.sk89q.worldedit.util.Location location) {
395            this.parent = parent;
396            this.location = location;
397        }
398
399        @Nullable
400        @Override
401        public BaseEntity getState() {
402            return parent.getState();
403        }
404
405        @Override
406        public boolean remove() {
407            return parent.remove();
408        }
409
410        @Override
411        public com.sk89q.worldedit.util.Location getLocation() {
412            return location;
413        }
414
415        @Override
416        public boolean setLocation(final com.sk89q.worldedit.util.Location location) {
417            this.location = location;
418            return true;
419        }
420
421        @Override
422        public Extent getExtent() {
423            return parent.getExtent();
424        }
425
426        @Nullable
427        @Override
428        public <T> T getFacet(final Class<? extends T> cls) {
429            return parent.getFacet(cls);
430        }
431
432    }
433
434}