/**
Copyright (C) <2015> <coolAlias>
This file is part of coolAlias' Zelda Sword Skills Minecraft Mod; as such,
you can redistribute it and/or modify it under the terms of the GNU
General Public License as published by the Free Software Foundation,
either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package zeldaswordskills.world.gen.structure;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import net.minecraft.block.Block;
import net.minecraft.block.BlockChest;
import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState;
import net.minecraft.init.Blocks;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.BlockPos;
import net.minecraft.util.MathHelper;
import net.minecraft.util.Vec3i;
import net.minecraft.world.World;
import net.minecraft.world.gen.structure.StructureBoundingBox;
import zeldaswordskills.block.BlockChestLocked;
import zeldaswordskills.block.ZSSBlocks;
import zeldaswordskills.block.tileentity.TileEntityPedestal;
import zeldaswordskills.item.ZSSItems;
import zeldaswordskills.ref.Config;
import zeldaswordskills.util.StructureGenUtils;
/**
*
* Basic shared functionality for all structures, such as bounding box and location data
*
*/
public abstract class RoomBase
{
/** Whether this particular dungeon was below water */
public boolean submerged = false;
/** Whether this particular dungeon came into contact with lava */
public boolean inLava = false;
/** Whether this dungeon will generate on the ocean floor (not just in the Ocean biome) */
public boolean inOcean = false;
/** Mountain dungeons have the highest chance of adding fairy spawners */
public boolean inMountain = false;
/** Nether dungeons have the highest chance of fire-based equipment */
public boolean inNether = false;
/** Generates a dungeon with a locked door and completely unbreakable walls if true */
public boolean isLocked = false;
/** This structure's bounding box */
protected StructureBoundingBox bBox;
/** The structure's chunk coordinates */
public final int chunkX, chunkZ;
/** The block which must have a majority representation in the area to replace */
protected final Block blockRequired;
/** The metadata that will be used for setting this block's texture */
protected int metadata = 0;
/** Set of all blocks that it's okay for this structure to replace */
protected static final Set<Block> replaceBlocks = new HashSet<Block>();
/** Returns this structure's bounding box */
public final StructureBoundingBox getBoundingBox() {
return bBox;
}
/** Returns total area of one horizontal slice of the structure's bounding box */
public final int getArea() {
return (bBox.getXSize() * bBox.getZSize());
}
/** Returns total cubic area represented by the structure's bounding box */
public final int getVolume() {
return (bBox.getXSize() * bBox.getYSize() * bBox.getZSize());
}
/** Attempts to generate the structure at the given coordinates, returning true if successful */
public abstract boolean generate(ZSSMapGenBase mapGen, World world, Random rand, int x, int y, int z);
/** Adds the final touches: chests, dungeon core, pedestal, etc. */
protected abstract void decorateDungeon(World world, Random rand);
/** Places and sets up the Dungeon Core for this room */
protected abstract void placeDungeonCore(World world);
/**
* Use to place a chest with appropriate facing based on surrounding blocks
*/
protected void placeChest(World world, BlockPos pos, Block chest) {
IBlockState chestState = chest.getDefaultState();
if (chest instanceof BlockChest) {
chestState = ((BlockChest) chest).correctFacing(world, pos, chestState);
} else if (chest instanceof BlockChestLocked) {
chestState = ((BlockChestLocked) chest).correctFacing(world, pos, chestState);
}
world.setBlockState(pos, chestState, 2);
}
/**
* Places a pedestal in the dungeon's center with a Master Sword stuck in it
*/
protected void placePedestal(World world, int offsetY) {
StructureGenUtils.setBlockAtPosition(world, bBox, bBox.getXSize() / 2, offsetY, bBox.getZSize() / 2, ZSSBlocks.pedestal.getDefaultState());
int x = StructureGenUtils.getXWithOffset(bBox, bBox.getXSize() / 2, bBox.getZSize() / 2);
int y = StructureGenUtils.getYWithOffset(bBox, offsetY);
int z = StructureGenUtils.getZWithOffset(bBox, bBox.getXSize() / 2, bBox.getZSize() / 2);
TileEntity te = world.getTileEntity(new BlockPos(x, y, z));
if (te instanceof TileEntityPedestal) {
((TileEntityPedestal) te).setSword(new ItemStack(ZSSItems.swordMaster), null);
}
}
/**
* Basic constructor for rooms sets up the bounding box and sets the block required field
*/
public RoomBase(int chunkX, int chunkZ, int size, int maxHeight, Block blockRequired) {
this.chunkX = chunkX;
this.chunkZ = chunkZ;
this.blockRequired = blockRequired;
this.bBox = new StructureBoundingBox(1, 1, 1, Math.max(size, 3), MathHelper.clamp_int(size, 3, maxHeight), Math.max(size, 3));
}
/**
* Returns true if there aren't too many unacceptable materials within the structure's location
*/
protected boolean canGenerate(World world) {
int failedAmount = 0;
int maxFail = (bBox.getXSize() * bBox.getZSize() / 2);
for (int i = bBox.minX; i <= bBox.maxX; ++i) {
for (int j = bBox.minY; j <= bBox.maxY; ++j) {
for (int k = bBox.minZ; k <= bBox.maxZ; ++k) {
Block block = world.getBlockState(new BlockPos(i, j, k)).getBlock();
if (!canReplaceBlockAt(j, block)) {
// TODO avoid mod block check needs testing (are mod block IDs guaranteed to be > 255?)
if (block == ZSSBlocks.secretStone || (Config.avoidModBlocks() && Block.getIdFromBlock(block) > 255)) {
return false;
} else if (++failedAmount > maxFail) {
return false;
}
}
}
}
}
return true;
}
/**
* Standard room gen procedure builds basic cube, fills with liquids/air, and calls decorateDungeon
*/
protected void doStandardRoomGen(World world, Random rand) {
StructureGenUtils.fillWithBlocks(world, bBox, 0, bBox.getXSize(), 0, bBox.getYSize(), 0, bBox.getZSize(), ZSSBlocks.secretStone.getStateFromMeta(getMetadata()));
genSubmerged(world);
generateAir(world);
decorateDungeon(world, rand);
}
/**
* Returns true if the structure is considered well-hidden; i.e. not too many blocks exposed to air / water
*/
protected boolean isWellHidden(World world) {
int difficulty = Config.getMainDungeonDifficulty();
if (inOcean) {
return true;
//return world.rand.nextFloat() < (1.0F - (difficulty * 0.25F)) ||
//StructureGenUtils.getNumBlocksOfMaterialInArea(world, bBox, Material.water, 1) < (getArea() / (difficulty + 1));
}
Material material = (!inNether && StructureGenUtils.getNumBlocksOfMaterial(world, bBox, Material.water, 1) > 0 ? Material.water : Material.air);
int above = StructureGenUtils.getNumBlocksOfMaterial(world, bBox, material, 1);
int below = StructureGenUtils.getNumBlocksOfMaterial(world, bBox, material, -1);
if (inNether) {
return world.rand.nextFloat() < (0.35F - (difficulty * 0.1F)) || ((difficulty != 3 || (above + below) < 4) &&
StructureGenUtils.getNumBlocksOfMaterialInArea(world, bBox, material, 1) < (getArea() / (difficulty + 1))); // above < (5 - difficulty) &&
}
return world.rand.nextFloat() < (0.35F - (difficulty * 0.1F)) || (above + below) < (5 - difficulty);
}
/** Shortcut for canReplaceBlockAt(int y, int id) */
protected boolean canReplaceBlockAt(World world, int x, int y, int z) {
return canReplaceBlockAt(y, world.getBlockState(new BlockPos(x, y, z)).getBlock());
}
/**
* Returns true if the block at height y can be replaced by this structure,
* specifically if replaceBlocks contains the block or the block's material is
* liquid and not in the top two layers
*/
protected boolean canReplaceBlockAt(int y, Block block) {
if (block == null) { return false; }
boolean flag1 = (submerged && !inLava && block.getMaterial() == Material.water);
boolean flag2 = (inNether && block.getMaterial() == Material.lava);
return (replaceBlocks.contains(block) || flag1 || flag2 || (block.getMaterial().isLiquid() && y < (bBox.maxY - 2)));
}
/**
* Returns the secret stone metadata value of the block to place
*/
protected int getMetadata() {
return metadata + (isLocked ? 8 : 0);
}
/** Sets the room's metadata based on world biome */
protected abstract void setMetadata(World world, BlockPos pos);
/**
* Fills room with air according to submerged / ocean status
*/
protected void generateAir(World world) {
if (!inOcean) {
StructureGenUtils.fillWithBlocks(world, bBox, 1, bBox.getXSize() - 1, (submerged ? (inLava || isLocked ? 2 : 3) : 1), bBox.getYSize() - 1, 1, bBox.getZSize() - 1, Blocks.air.getDefaultState());
}
}
/**
* Generation for submerged dungeons adds liquid layers: lava 1, water 2, ocean filled
* Checks internally if this room is valid for liquid generation
*/
protected void genSubmerged(World world) {
if (submerged && bBox.getXSize() > 3) {
int fillTo = (inLava ? 2 : inOcean ? bBox.getYSize() - 1 : 3);
Block block = (inLava ? Blocks.lava : Blocks.water);
StructureGenUtils.fillWithBlocks(world, bBox, 1, bBox.getXSize() - 1, 1, fillTo, 1, bBox.getZSize() - 1, block.getDefaultState());
}
}
/**
* After a failed validation, attempts to place structure in ocean if applicable
* @param sink if true, sinks the structure by some amount into the ocean floor
* @return true if successful, in which case inOcean is set to true
*/
protected boolean placeInOcean(World world, boolean sink) {
bBox.offset(0, 4, 0); // move back up a little
Vec3i center = bBox.getCenter();
int x = center.getX();
int z = center.getZ();
boolean flag = world.getBiomeGenForCoords(new BlockPos(center)).biomeName.toLowerCase().contains("ocean");
if (flag && !inLava && world.getBlockState(new BlockPos(x, bBox.maxY, z)).getBlock().getMaterial() == Material.water) {
int count = 0;
while (bBox.minY > 16 && count < 8 && world.getBlockState(new BlockPos(x, bBox.minY, z)).getBlock().getMaterial() == Material.water) {
bBox.offset(0, -1, 0);
++count;
}
if (world.getBlockState(new BlockPos(x, bBox.minY, z)).getBlock().getMaterial() != Material.water) {
inOcean = true;
StructureGenUtils.adjustCornersForMaterial(world, bBox, Material.water, 6, false, false);
if (sink) {
int diff = Config.getMainDungeonDifficulty();
int adj = 2 - diff;
if (world.rand.nextFloat() > (diff * 0.25F)) {
if (diff == 3) {
++adj;
} else {
adj += (world.rand.nextFloat() < 0.5F ? 1 : -1);
}
}
bBox.offset(0, -(bBox.getYSize() - adj), 0);
}
return true;
}
}
return false;
}
/**
* Adjusts nether dungeons to rest on solid ground when submerged in lava
* @return true if final bottom block is not another secret dungeon block
*/
protected boolean placeInNether(World world) {
Vec3i center = bBox.getCenter();
int x = center.getX();
int z = center.getZ();
while (bBox.minY > 8 && world.getBlockState(new BlockPos(x, bBox.minY, z)).getBlock().getMaterial() == Material.lava) {
bBox.offset(0, -1, 0);
}
StructureGenUtils.adjustCornersForMaterial(world, bBox, Material.lava, 4, false, false);
return (world.getBlockState(new BlockPos(x, bBox.minY, z)).getBlock() != ZSSBlocks.secretStone);
}
protected int validations = 0;
/** Number of y layers the room may be adjusted downwards */
protected static final int NUM_VALIDATIONS = 8;
/**
* If top layer is not valid (contains liquid or not enough of correct material), adjusts
* the structure downwards until no longer the case, the structure's lower level is too
* low, or the number of attempts exceeds NUM_VALIDATIONS
*/
protected boolean validateTopLayer(World world) {
int invalidBlocks = 0; // number of blocks not matching the required block type
int area = getArea();
++validations;
for (int i = bBox.minX; i <= bBox.maxX; ++i) {
for (int k = bBox.minZ; k <= bBox.maxZ; ++k) {
if (validations > NUM_VALIDATIONS || bBox.minY < 5) {
return false;
} else {
Block block = world.getBlockState(new BlockPos(i, bBox.maxY, k)).getBlock();
if (block != null && block.getMaterial().isLiquid()) {
submerged = true;
inLava = (block == Blocks.lava);
bBox.offset(0, -1, 0);
return ((inNether && bBox.maxY < 48) || validateTopLayer(world));
} else if (block != blockRequired) {
if (++invalidBlocks > area / 2) {
bBox.offset(0, -1, 0);
return validateTopLayer(world);
}
}
}
}
}
return true;
}
/**
* Returns a new tag compound with the room's chunk coordinates and bounding box
*/
public NBTTagCompound writeToNBT() {
NBTTagCompound compound = new NBTTagCompound();
compound.setTag("BB", bBox.toNBTTagIntArray());
return compound;
}
/**
* Reads the room's bounding box from NBT (currently unused)
*/
public void readFromNBT(NBTTagCompound compound) {
bBox = new StructureBoundingBox(compound.getIntArray("BB"));
}
static {
replaceBlocks.add(Blocks.cobblestone);
replaceBlocks.add(Blocks.stone);
replaceBlocks.add(Blocks.dirt);
replaceBlocks.add(Blocks.grass);
replaceBlocks.add(Blocks.gravel);
replaceBlocks.add(Blocks.netherrack);
replaceBlocks.add(Blocks.sand);
replaceBlocks.add(Blocks.sandstone);
replaceBlocks.add(Blocks.snow);
}
}