blob: be8ed192f4e62e74cf7b7a8f637cbeb1f2b088b3 [file] [log] [blame] [raw]
package net.glowstone.generator.objects.trees;
import io.netty.util.internal.ThreadLocalRandom;
import java.util.Arrays;
import java.util.Collection;
import java.util.Random;
import net.glowstone.generator.objects.TerrainObject;
import net.glowstone.util.BlockStateDelegate;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.BlockState;
import org.bukkit.material.Dirt;
import org.bukkit.material.types.DirtType;
/** Oak tree, and superclass for other types. */
public class GenericTree implements TerrainObject {
protected final BlockStateDelegate delegate;
protected int height;
protected int logType;
protected int leavesType;
protected Collection<Material> overridables;
/**
* Initializes this tree with a random height, preparing it to attempt to generate.
*
* @param random the PRNG
* @param delegate the BlockStateDelegate used to check for space and to fill in wood and leaves
*/
public GenericTree(Random random, BlockStateDelegate delegate) {
this.delegate = delegate;
setOverridables(
Material.AIR,
Material.LEAVES,
Material.GRASS,
Material.DIRT,
Material.LOG,
Material.LOG_2,
Material.SAPLING,
Material.VINE
);
setHeight(random.nextInt(3) + 4);
setTypes(0, 0);
}
protected final void setOverridables(Material... overridables) {
this.overridables = Arrays.asList(overridables);
}
protected final void setHeight(int height) {
this.height = height;
}
/**
* Sets the block data values for this tree's blocks.
*
* @param logType the species portion of the data value for wood blocks.
* @param leavesType the species portion of the data value for leaf blocks.
*/
protected final void setTypes(int logType, int leavesType) {
this.logType = logType;
this.leavesType = leavesType;
}
/**
* Checks whether this tree fits under the upper world limit.
* @param baseHeight the height of the base of the trunk
*
* @return true if this tree can grow without exceeding block height 255; false otherwise.
*/
public boolean canHeightFit(int baseHeight) {
return baseHeight >= 1 && baseHeight + height + 1 <= 255;
}
/**
* Checks whether this tree can grow on top of the given block.
* @param soil the block we're growing on
* @return true if this tree can grow on the type of block below it; false otherwise
*/
public boolean canPlaceOn(BlockState soil) {
return soil.getType() == Material.GRASS || soil.getType() == Material.DIRT
|| soil.getType() == Material.SOIL;
}
/**
* Checks whether this tree has enough space to grow.
*
* @param baseX the X coordinate of the base of the trunk
* @param baseY the Y coordinate of the base of the trunk
* @param baseZ the Z coordinate of the base of the trunk
* @param world the world to grow in
* @return true if this tree has space to grow; false otherwise
*/
public boolean canPlace(int baseX, int baseY, int baseZ, World world) {
for (int y = baseY; y <= baseY + 1 + height; y++) {
// Space requirement
int radius = 1; // default radius if above first block
if (y == baseY) {
radius = 0; // radius at source block y is 0 (only trunk)
} else if (y >= baseY + 1 + height - 2) {
radius = 2; // max radius starting at leaves bottom
}
// check for block collision on horizontal slices
for (int x = baseX - radius; x <= baseX + radius; x++) {
for (int z = baseZ - radius; z <= baseZ + radius; z++) {
if (y >= 0 && y < 256) {
// we can overlap some blocks around
Material type = blockTypeAt(x, y, z, world);
if (!overridables.contains(type)) {
return false;
}
} else { // height out of range
return false;
}
}
}
}
return true;
}
/**
* Attempts to grow this tree at its current location. If successful, the associated {@link
* BlockStateDelegate} is instructed to set blocks to wood and leaves.
*
* @param loc the base of the trunk
* @return true if successfully grown; false otherwise
* @deprecated use {@link #generate(World, Random, int, int, int)}
*/
@Deprecated
public boolean generate(Location loc) {
return generate(loc.getWorld(), ThreadLocalRandom.current(),
loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
}
@Override
public boolean generate(World world, Random random, int blockX, int blockY, int blockZ) {
if (cannotGenerateAt(blockX, blockY, blockZ, world)) {
return false;
}
// generate the leaves
for (int y = blockY + height - 3; y <= blockY + height; y++) {
int n = y - (blockY + height);
int radius = 1 - n / 2;
for (int x = blockX - radius; x <= blockX + radius; x++) {
for (int z = blockZ - radius; z <= blockZ + radius; z++) {
if (Math.abs(x - blockX) != radius
|| Math.abs(z - blockZ) != radius
|| random.nextBoolean() && n != 0) {
replaceIfAirOrLeaves(x, y, z, Material.LEAVES, leavesType, world);
}
}
}
}
// generate the trunk
for (int y = 0; y < height; y++) {
replaceIfAirOrLeaves(blockX,
blockY + y, blockZ, Material.LOG, logType, world);
}
// block below trunk is always dirt
Dirt dirt = new Dirt(DirtType.NORMAL);
delegate
.setTypeAndData(world, blockX, blockY - 1, blockZ,
Material.DIRT, dirt);
return true;
}
/**
* Returns whether any of {@link #canHeightFit(int)}, {@link #canPlace(int, int, int, World)} or
* {@link #canPlaceOn(BlockState)} prevent this tree from generating.
*
* @param baseX the X coordinate of the base of the trunk
* @param baseY the Y coordinate of the base of the trunk
* @param baseZ the Z coordinate of the base of the trunk
* @param world the world to grow in
* @return true if any of the checks prevent us from generating, false otherwise
*/
protected boolean cannotGenerateAt(int baseX, int baseY, int baseZ,
World world) {
return !canHeightFit(baseY)
|| !canPlaceOn(world.getBlockAt(baseX, baseY - 1, baseZ).getState())
|| !canPlace(baseX, baseY, baseZ, world);
}
/**
* Replaces the block at a location with the given new one, if it is air or leaves.
*
* @param x the x coordinate
* @param y the y coordinate
* @param z the z coordinate
* @param newMaterial the new block type
* @param data the new block data
* @param world the world we are generating in
*/
protected void replaceIfAirOrLeaves(int x, int y, int z, Material newMaterial, int data,
World world) {
Material oldMaterial = blockTypeAt(x, y, z, world);
if (oldMaterial == Material.AIR || oldMaterial == Material.LEAVES) {
delegate.setTypeAndRawData(world, x, y, z, newMaterial, data);
}
}
/**
* Returns the block type at the given coordinates.
* @param x the x coordinate
* @param y the y coordinate
* @param z the z coordinate
* @param world the world we are generating in
* @return the block type
*/
protected Material blockTypeAt(int x, int y, int z, World world) {
return delegate.getBlockState(world, x, y, z).getType();
}
}