diff --git a/src/main/java/biomesoplenty/api/block/BlockQueries.java b/src/main/java/biomesoplenty/api/block/BlockQueries.java index 7bc3005b0..a754097d5 100644 --- a/src/main/java/biomesoplenty/api/block/BlockQueries.java +++ b/src/main/java/biomesoplenty/api/block/BlockQueries.java @@ -21,6 +21,7 @@ public class BlockQueries public static IBlockPosQuery airOrLeaves; public static IBlockPosQuery surfaceBlocks; public static IBlockPosQuery groundBlocks; + public static IBlockPosQuery solid; public static IBlockPosQuery fertile; public static IBlockPosQuery fertileOrNetherrack; diff --git a/src/main/java/biomesoplenty/common/biome/overworld/BiomeGenWasteland.java b/src/main/java/biomesoplenty/common/biome/overworld/BiomeGenWasteland.java index f1c6141e7..5e0c1b94d 100644 --- a/src/main/java/biomesoplenty/common/biome/overworld/BiomeGenWasteland.java +++ b/src/main/java/biomesoplenty/common/biome/overworld/BiomeGenWasteland.java @@ -33,6 +33,7 @@ import biomesoplenty.common.world.feature.GeneratorFlora; import biomesoplenty.common.world.feature.GeneratorGrass; import biomesoplenty.common.world.feature.GeneratorLakes; import biomesoplenty.common.world.feature.GeneratorOreSingle; +import biomesoplenty.common.world.feature.GeneratorSpike; import biomesoplenty.common.world.feature.GeneratorSplatter; import biomesoplenty.common.world.feature.tree.GeneratorBasicTree; import biomesoplenty.common.world.feature.tree.GeneratorBigTree; @@ -63,11 +64,10 @@ public class BiomeGenWasteland extends BOPBiome this.spawnableCreatureList.clear(); this.spawnableWaterCreatureList.clear(); - IBlockPosQuery emptyDriedDirt = BlockQuery.buildAnd().withAirAbove().states(this.topBlock).create(); - // trees - GeneratorWeighted treeGenerator = new GeneratorWeighted(1); - treeGenerator.add("dead_tree", 1, (new GeneratorBigTree.Builder()).amountPerChunk(1.0F).minHeight(5).maxHeight(12).placeOn(emptyDriedDirt).foliageHeight(0).foliageDensity(0.5D).log(BOPWoods.DEAD).leaves(Blocks.air.getDefaultState()).create()); + GeneratorWeighted treeGenerator = new GeneratorWeighted(2.0F); + this.addGenerator("trees", GeneratorStage.TREE, treeGenerator); + treeGenerator.add("dead_tree", 1, (new GeneratorBigTree.Builder()).amountPerChunk(1.0F).minHeight(5).maxHeight(12).foliageHeight(0).foliageDensity(0.5D).log(BOPWoods.DEAD).leaves(Blocks.air.getDefaultState()).create()); // grasses GeneratorWeighted grassGenerator = new GeneratorWeighted(0.2F); @@ -81,6 +81,9 @@ public class BiomeGenWasteland extends BOPBiome this.addGenerator("lakes", GeneratorStage.SAND, (new GeneratorLakes.Builder()).amountPerChunk(0.5F).waterLakeForBiome(this).create()); this.addGenerator("poison_lakes", GeneratorStage.SAND, (new GeneratorLakes.Builder()).amountPerChunk(0.2F).waterLakeForBiome(this).liquid(BOPBlocks.poison).frozenLiquid((IBlockState)null).create()); + // spikes + this.addGenerator("spikes", GeneratorStage.PRE, (new GeneratorSpike.Builder()).amountPerChunk(0.5F).create()); + // gem this.addGenerator("malachite", GeneratorStage.SAND, (new GeneratorOreSingle.Builder()).amountPerChunk(12).with(BOPGems.MALACHITE).create()); diff --git a/src/main/java/biomesoplenty/common/init/ModBlockQueries.java b/src/main/java/biomesoplenty/common/init/ModBlockQueries.java index 7fe80ad77..e71dea4b7 100644 --- a/src/main/java/biomesoplenty/common/init/ModBlockQueries.java +++ b/src/main/java/biomesoplenty/common/init/ModBlockQueries.java @@ -12,6 +12,7 @@ import static biomesoplenty.api.block.BlockQueries.*; import net.minecraft.block.material.Material; import net.minecraft.init.Blocks; import net.minecraft.util.BlockPos; +import net.minecraft.util.EnumFacing; import net.minecraft.world.World; import net.minecraftforge.common.EnumPlantType; import biomesoplenty.api.block.BOPBlocks; @@ -61,6 +62,17 @@ public class ModBlockQueries } }; + //Match blocks with a solid top + solid = new IBlockPosQuery() + { + // Block.setBlockUnbreakable sets the hardness value to -1.0F + @Override + public boolean matches(World world, BlockPos pos) + { + return world.isSideSolid(pos, EnumFacing.UP); + } + }; + airOrLeaves = new BlockQueryMaterial(Material.air, Material.leaves); // Match blocks which count as 'the surface' - useful for finding places to put plants, trees, lilypads etc - note plants, trees, snow all excluded because they sit or grow 'on' the surface diff --git a/src/main/java/biomesoplenty/common/init/ModGenerators.java b/src/main/java/biomesoplenty/common/init/ModGenerators.java index ee015ec2b..a872e8d9c 100644 --- a/src/main/java/biomesoplenty/common/init/ModGenerators.java +++ b/src/main/java/biomesoplenty/common/init/ModGenerators.java @@ -44,5 +44,6 @@ public class ModGenerators registerGenerator("columns", GeneratorColumns.class, new GeneratorColumns.Builder()); registerGenerator("mixed_lily", GeneratorMixedLily.class, new GeneratorMixedLily.Builder()); registerGenerator("crystals", GeneratorCrystals.class, new GeneratorCrystals.Builder()); + registerGenerator("spike", GeneratorSpike.class, new GeneratorSpike.Builder()); } } diff --git a/src/main/java/biomesoplenty/common/world/feature/GeneratorCrystals.java b/src/main/java/biomesoplenty/common/world/feature/GeneratorCrystals.java index 2c0d07ebc..c09b4bc0d 100644 --- a/src/main/java/biomesoplenty/common/world/feature/GeneratorCrystals.java +++ b/src/main/java/biomesoplenty/common/world/feature/GeneratorCrystals.java @@ -52,7 +52,7 @@ public class GeneratorCrystals extends GeneratorReplacing public GeneratorCrystals(float amountPerChunk, IBlockPosQuery placeOn, IBlockPosQuery replace, IBlockState with, ScatterYMethod scatterYMethod, int generationAttempts, int maxRadius, int maxDepth) { - super(amountPerChunk, replace, replace, with, scatterYMethod); + super(amountPerChunk, placeOn, replace, with, scatterYMethod); this.generationAttempts = generationAttempts; this.maxRadius = maxRadius; this.maxDepth = maxDepth; diff --git a/src/main/java/biomesoplenty/common/world/feature/GeneratorSpike.java b/src/main/java/biomesoplenty/common/world/feature/GeneratorSpike.java new file mode 100644 index 000000000..9b2ee8ab3 --- /dev/null +++ b/src/main/java/biomesoplenty/common/world/feature/GeneratorSpike.java @@ -0,0 +1,235 @@ +/******************************************************************************* + * Copyright 2016, the Biomes O' Plenty Team + * + * This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International Public License. + * + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/. + ******************************************************************************/ +package biomesoplenty.common.world.feature; + +import java.util.Random; +import java.util.function.Predicate; + +import org.apache.commons.lang3.tuple.Pair; + +import biomesoplenty.api.biome.generation.BOPGeneratorBase; +import biomesoplenty.api.biome.generation.IGenerator.IGeneratorBuilder; +import biomesoplenty.api.block.BOPBlocks; +import biomesoplenty.api.block.BlockQueries; +import biomesoplenty.common.util.biome.GeneratorUtils; +import biomesoplenty.common.util.biome.GeneratorUtils.ScatterYMethod; +import biomesoplenty.common.util.block.BlockQuery.BlockQueryMaterial; +import biomesoplenty.common.util.block.BlockQuery.IBlockPosQuery; +import biomesoplenty.common.util.config.BOPConfig.IConfigObj; +import biomesoplenty.common.world.feature.GeneratorBigFlower.Builder; +import net.minecraft.block.material.Material; +import net.minecraft.block.state.IBlockState; +import net.minecraft.init.Blocks; +import net.minecraft.util.BlockPos; +import net.minecraft.world.World; + +public class GeneratorSpike extends GeneratorReplacing +{ + public static class Builder extends GeneratorReplacing.InnerBuilder implements IGeneratorBuilder + { + protected int minHeight; + protected int maxHeight; + protected int minRadius; + protected int maxRadius; + + public Builder minHeight(int a) {this.minHeight = a; return this.self();} + public Builder maxHeight(int a) {this.maxHeight = a; return this.self();} + public Builder minRadius(int a) {this.minRadius = a; return this.self();} + public Builder maxRadius(int a) {this.maxRadius = a; return this.self();} + + public Builder() + { + // defaults + this.amountPerChunk = 1.0F; + this.placeOn = BlockQueries.solid; + this.replace = new BlockQueryMaterial(Material.air); + this.with = BOPBlocks.dried_dirt.getDefaultState(); + this.scatterYMethod = ScatterYMethod.AT_SURFACE; + this.minHeight = 8; + this.maxHeight = 12; + this.minRadius = 3; + this.maxRadius = 3; + } + + @Override + public GeneratorSpike create() + { + return new GeneratorSpike(this.amountPerChunk, this.placeOn, this.replace, this.with, this.scatterYMethod, this.minHeight, this.maxHeight, this.minRadius, this.maxRadius); + } + } + + protected int minHeight; + protected int maxHeight; + protected int minRadius; + protected int maxRadius; + + public GeneratorSpike(float amountPerChunk, IBlockPosQuery placeOn, IBlockPosQuery replace, IBlockState with, ScatterYMethod scatterYMethod, int minHeight, int maxHeight, int minRadius, int maxRadius) + { + super(amountPerChunk, placeOn, replace, with, scatterYMethod); + this.minHeight = minHeight; + this.maxHeight = maxHeight; + this.minRadius = minRadius; + this.maxRadius = maxRadius; + } + + @Override + public boolean generate(World world, Random rand, BlockPos position) + { + int maxRadius = this.minRadius + rand.nextInt(this.maxRadius - this.minRadius + 1); + int centreHeight = this.minHeight + rand.nextInt(this.maxHeight - this.minHeight + 1); + + // check that there's room and if the blocks below are suitable + if (!this.canPlaceHere(world, position, centreHeight, maxRadius)) {return false;} + + //Distribute the height excluding the spire and the base between the radius (other than the base) + int layerHeight = centreHeight / (maxRadius - 1); + + // + // Generate the base + // + + //Fill the inner section of the circle + createCircleWithChance(world, position, this.with, maxRadius - 1, true, 1.0F); + //Randomly remove the outer edges + createCircleWithChance(world, position, this.with, maxRadius, true, 0.15F); + + // + // Generate the centre and the spire + // + + BlockPos layerStartPos = position.up(); + + //Add 2 for the spire + for (int y = 0; y <= centreHeight + 2; y++) + { + //Generate the spire + if (y > centreHeight) + { + world.setBlockState(layerStartPos.add(0, y, 0), this.with); + } + else //Generate the layers + { + //Bottom layer is 0, then 1 etc + int layer = y / layerHeight; + int layerIndex = y % layerHeight; //Get the position of y within the current layer, ignoring the base + int radius = maxRadius - 1 - layer; //The radius for this layer, with the base radius subtracted + + //Treat any layers with a radius of 0 as an extension of the spire + if (radius == 0) + { + world.setBlockState(layerStartPos.add(0, y, 0), this.with); + continue; + } + + if (layerIndex == layerHeight - 1) //Generate midpoints randomly + { + //Fill the inner section of the circle + createCircleWithChance(world, layerStartPos.add(0, y, 0), this.with, radius - 1, true, 1.0F); + createMidpointsWithChance(world, layerStartPos.add(0, y, 0), this.with, radius, 0.7F); + } + else if (layerIndex == layerHeight - 2) + { + //Fill the inner section of the circle + createCircleWithChance(world, layerStartPos.add(0, y, 0), this.with, radius - 1, true, 1.0F); + //Randomly remove the outer edges + createCircleWithChance(world, layerStartPos.add(0, y, 0), this.with, radius, true, 0.1F / (layer + 1)); + } + else + { + createCircleWithChance(world, layerStartPos.add(0, y, 0), this.with, radius, true, 1.0F); + } + } + } + + return true; + } + + public boolean canPlaceHere(World world, BlockPos pos, int height, int radius) + { + if (pos.getY() < 1 || pos.getY() + height > 255) + { + return false; + } + for (int y = pos.getY(); y <= pos.getY() + 8; ++y) + { + for (int x = pos.getX() - radius; x <= pos.getX() + radius; ++x) + { + for (int z = pos.getZ() - radius; z <= pos.getZ() + radius; ++z) + { + if (y == pos.getY() && !this.placeOn.matches(world, new BlockPos(x, y - 1, z))) + { + return false; + } + + if (!this.replace.matches(world, new BlockPos(x, y, z))) + { + return false; + } + } + } + } + return true; + } + + @Override + public void configure(IConfigObj conf) + { + this.amountPerChunk = conf.getFloat("amountPerChunk", this.amountPerChunk); + this.placeOn = conf.getBlockPosQuery("placeUnder", this.placeOn); + this.replace = conf.getBlockPosQuery("replace", this.replace); + this.with = conf.getBlockState("with", this.with); + this.scatterYMethod = conf.getEnum("scatterYMethod", this.scatterYMethod, ScatterYMethod.class); + int minHeight = conf.getInt("minHeight", this.minHeight).intValue(); + int maxHeight = conf.getInt("maxHeight", this.maxHeight).intValue(); + + Pair heights = GeneratorUtils.validateMinMaxHeight(minHeight, maxHeight); + this.minHeight = heights.getLeft(); + this.maxHeight = heights.getRight(); + + int minRadius = conf.getInt("minRadius", this.minRadius).intValue(); + int maxRadius = conf.getInt("maxRadius", this.maxRadius).intValue(); + + Pair radii = GeneratorUtils.validateMinMaxHeight(minRadius, maxRadius); + this.minRadius = radii.getLeft(); + this.maxRadius = radii.getRight(); + } + + private void createCircleWithChance(World world, BlockPos middle, IBlockState state, int maxRadius, boolean fill, float chance) + { + //This may break for larger radii however it will do for this purpose + double increment = 0.05D; + + for (int radius = maxRadius; radius >= 0; radius--) + { + for (double angle = 0.0F; angle <= Math.PI * 2; angle += increment) + { + BlockPos pos = middle.add(Math.round(radius * Math.cos(angle)), 0, Math.round(radius * Math.sin(angle))); + + setBlockWithChance(world, pos, state, chance); + } + + if (!fill) break; + } + } + + private void createMidpointsWithChance(World world, BlockPos middle, IBlockState state, int radius, float chance) + { + BlockPos midpoint; + + if (world.getBlockState((midpoint = middle.add(-radius, 0, 0)).down()) == state) setBlockWithChance(world, midpoint, state, chance); + if (world.getBlockState((midpoint = middle.add(radius, 0, 0)).down()) == state) setBlockWithChance(world, midpoint, state, chance); + if (world.getBlockState((midpoint = middle.add(0, 0, -radius)).down()) == state) setBlockWithChance(world, midpoint, state, chance); + if (world.getBlockState((midpoint = middle.add(0, 0, radius)).down()) == state) setBlockWithChance(world, midpoint, state, chance); + } + + private void setBlockWithChance(World world, BlockPos pos, IBlockState state, float chance) + { + if (world.rand.nextFloat() < chance) + world.setBlockState(pos, state); + } +} diff --git a/src/main/java/biomesoplenty/common/world/feature/tree/GeneratorBigTree.java b/src/main/java/biomesoplenty/common/world/feature/tree/GeneratorBigTree.java index 25e8933d0..d1994c503 100644 --- a/src/main/java/biomesoplenty/common/world/feature/tree/GeneratorBigTree.java +++ b/src/main/java/biomesoplenty/common/world/feature/tree/GeneratorBigTree.java @@ -493,7 +493,7 @@ public class GeneratorBigTree extends GeneratorTreeBase boolean isSoil = state.getBlock().canSustainPlant(this.world, down, EnumFacing.UP, ((BlockSapling)Blocks.sapling)); //Don't grow the tree here if the location can't sustain a sapling - if (!isSoil || !this.placeOn.matches(world, down)) + if (!isSoil && !this.placeOn.matches(world, down)) { return false; }