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