package openmods.block;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemBlock;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.util.IIcon;
import net.minecraft.util.MovingObjectPosition;
import net.minecraft.util.Vec3;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World;
import net.minecraftforge.common.util.ForgeDirection;
import openmods.api.IActivateAwareTile;
import openmods.api.IAddAwareTile;
import openmods.api.IBreakAwareTile;
import openmods.api.ICustomBreakDrops;
import openmods.api.ICustomHarvestDrops;
import openmods.api.ICustomPickItem;
import openmods.api.IHasGui;
import openmods.api.IIconProvider;
import openmods.api.INeighbourAwareTile;
import openmods.api.INeighbourTeAwareTile;
import openmods.api.IPlaceAwareTile;
import openmods.api.IPlacerAwareTile;
import openmods.api.ISurfaceAttachment;
import openmods.config.game.IRegisterableBlock;
import openmods.geometry.BlockSpaceTransform;
import openmods.geometry.Orientation;
import openmods.inventory.IInventoryProvider;
import openmods.tileentity.OpenTileEntity;
import openmods.utils.BlockNotifyFlags;
import openmods.utils.BlockUtils;
public abstract class OpenBlock extends Block implements IRegisterableBlock {
public static final int OPEN_MODS_TE_GUI = -1;
private static final int EVENT_ADDED = -1;
private enum TileEntityCapability {
ICON_PROVIDER(IIconProvider.class),
GUI_PROVIDER(IHasGui.class),
ACTIVATE_LISTENER(IActivateAwareTile.class),
SURFACE_ATTACHEMENT(ISurfaceAttachment.class),
BREAK_LISTENER(IBreakAwareTile.class),
PLACER_LISTENER(IPlacerAwareTile.class),
PLACE_LISTENER(IPlaceAwareTile.class),
ADD_LISTENER(IAddAwareTile.class),
CUSTOM_PICK_ITEM(ICustomPickItem.class),
CUSTOM_BREAK_DROPS(ICustomBreakDrops.class),
CUSTOM_HARVEST_DROPS(ICustomHarvestDrops.class),
INVENTORY(IInventory.class),
INVENTORY_PROVIDER(IInventoryProvider.class),
NEIGBOUR_LISTENER(INeighbourAwareTile.class),
NEIGBOUR_TE_LISTENER(INeighbourTeAwareTile.class);
public final Class<?> intf;
private TileEntityCapability(Class<?> intf) {
this.intf = intf;
}
}
public enum BlockPlacementMode {
ENTITY_ANGLE,
SURFACE
}
public enum RenderMode {
TESR_ONLY,
BLOCK_ONLY,
BOTH
}
private final Set<TileEntityCapability> teCapabilities = EnumSet.noneOf(TileEntityCapability.class);
/**
* The tile entity class associated with this block
*/
private Class<? extends TileEntity> teClass = null;
protected BlockRotationMode blockRotationMode = BlockRotationMode.NONE;
protected BlockPlacementMode blockPlacementMode = BlockPlacementMode.ENTITY_ANGLE;
protected Orientation inventoryRenderOrientation;
protected RenderMode renderMode = RenderMode.BLOCK_ONLY;
public IIcon[] textures = new IIcon[6];
public boolean hasCapability(TileEntityCapability capability) {
return teCapabilities.contains(capability);
}
public boolean hasCapabilities(TileEntityCapability capability1, TileEntityCapability capability2) {
return hasCapability(capability1) || hasCapability(capability2);
}
public boolean hasCapabilities(TileEntityCapability... capabilities) {
for (TileEntityCapability capability : capabilities)
if (teCapabilities.contains(capability)) return true;
return false;
}
protected OpenBlock(Material material) {
super(material);
setHardness(1.0F);
// I dont think vanilla actually uses this..
isBlockContainer = false;
}
protected void setPlacementMode(BlockPlacementMode mode) {
this.blockPlacementMode = mode;
}
protected void setRotationMode(BlockRotationMode mode) {
this.blockRotationMode = mode;
}
public BlockRotationMode getRotationMode() {
return this.blockRotationMode;
}
protected BlockPlacementMode getPlacementMode() {
return this.blockPlacementMode;
}
protected void setInventoryRenderOrientation(Orientation orientation) {
inventoryRenderOrientation = orientation;
}
protected void setRenderMode(RenderMode renderMode) {
this.renderMode = renderMode;
}
public Orientation getOrientation(int metadata) {
final BlockRotationMode rotationMode = getRotationMode();
return rotationMode.fromValue(metadata & rotationMode.mask);
}
@SideOnly(Side.CLIENT)
public Orientation getInventoryRenderOrientation() {
return inventoryRenderOrientation != null? inventoryRenderOrientation : getRotationMode().getInventoryRenderOrientation();
}
@SideOnly(Side.CLIENT)
public int getInventoryRenderMetadata(int itemMetadata) {
final BlockRotationMode rotationMode = getRotationMode();
final Orientation renderOrientation = inventoryRenderOrientation != null? inventoryRenderOrientation : rotationMode.getInventoryRenderOrientation();
return rotationMode.toValue(renderOrientation);
}
public void setBlockBounds(AxisAlignedBB aabb) {
this.maxX = aabb.maxX;
this.maxY = aabb.maxY;
this.maxZ = aabb.maxZ;
this.minX = aabb.minX;
this.minY = aabb.minY;
this.minZ = aabb.minZ;
}
public boolean shouldDropFromTeAfterBreak() {
return true;
}
public boolean shouldOverrideHarvestWithTeLogic() {
return hasCapability(TileEntityCapability.CUSTOM_HARVEST_DROPS);
}
public void setBoundsBasedOnOrientation(Orientation orientation) {}
public static OpenBlock getOpenBlock(IBlockAccess world, int x, int y, int z) {
if (world == null) return null;
Block block = world.getBlock(x, y, z);
if (block instanceof OpenBlock) return (OpenBlock)block;
return null;
}
@Override
public TileEntity createTileEntity(World world, int metadata) {
final TileEntity te = createTileEntity();
if (te != null) {
te.blockType = this;
if (te instanceof OpenTileEntity) {
((OpenTileEntity)te).setup();
}
}
return te;
}
public TileEntity createTileEntityForRender() {
final TileEntity te = createTileEntity();
Preconditions.checkNotNull(te, "Trying to get rendering TE for '%s', but it's not configured", this);
te.blockType = this;
te.blockMetadata = 0;
return te;
}
protected TileEntity createTileEntity() {
if (teClass == null) return null;
try {
return teClass.newInstance();
} catch (Exception ex) {
throw new RuntimeException("Failed to create TE with class " + teClass, ex);
}
}
public Class<? extends TileEntity> getTileClass() {
return teClass;
}
protected boolean suppressPickBlock() {
return false;
}
@Override
@SuppressWarnings("deprecation")
public ItemStack getPickBlock(MovingObjectPosition target, World world, int x, int y, int z) {
if (hasCapability(TileEntityCapability.CUSTOM_PICK_ITEM)) {
TileEntity te = world.getTileEntity(x, y, z);
if (te instanceof ICustomPickItem) return ((ICustomPickItem)te).getPickBlock();
}
return suppressPickBlock()? null : super.getPickBlock(target, world, x, y, z);
}
private static List<ItemStack> getTileBreakDrops(TileEntity te) {
List<ItemStack> breakDrops = Lists.newArrayList();
BlockUtils.getTileInventoryDrops(te, breakDrops);
if (te instanceof ICustomBreakDrops) ((ICustomBreakDrops)te).addDrops(breakDrops);
return breakDrops;
}
@Override
public void breakBlock(World world, int x, int y, int z, Block block, int meta) {
if (shouldDropFromTeAfterBreak()) {
final TileEntity te = world.getTileEntity(x, y, z);
if (te != null) {
if (te instanceof IBreakAwareTile) ((IBreakAwareTile)te).onBlockBroken();
for (ItemStack stack : getTileBreakDrops(te))
BlockUtils.dropItemStackInWorld(world, x, y, z, stack);
world.removeTileEntity(x, y, z);
}
}
super.breakBlock(world, x, y, z, block, meta);
}
protected ArrayList<ItemStack> getDropsWithTileEntity(World world, EntityPlayer player, int x, int y, int z) {
if (hasCapability(TileEntityCapability.CUSTOM_HARVEST_DROPS)) {
final TileEntity te = world.getTileEntity(x, y, z);
if (te instanceof ICustomHarvestDrops) {
final ICustomHarvestDrops dropper = (ICustomHarvestDrops)te;
final ArrayList<ItemStack> drops;
if (!dropper.suppressNormalHarvestDrops()) {
final int metadata = world.getBlockMetadata(x, y, z);
int fortune = player != null? EnchantmentHelper.getFortuneModifier(player) : 0;
drops = super.getDrops(world, x, y, z, metadata, fortune);
} else {
drops = Lists.newArrayList();
}
dropper.addHarvestDrops(player, drops);
return drops;
}
}
return null;
}
@Override
public boolean removedByPlayer(World world, EntityPlayer player, int x, int y, int z, boolean willHarvest) {
if (willHarvest && shouldOverrideHarvestWithTeLogic()) {
List<ItemStack> drops = getDropsWithTileEntity(world, player, x, y, z);
if (drops != null) BlockDropsStore.instance.storeDrops(world, x, y, z, drops);
}
return super.removedByPlayer(world, player, x, y, z, willHarvest);
}
@Override
public ArrayList<ItemStack> getDrops(World world, int x, int y, int z, int metadata, int fortune) {
ArrayList<ItemStack> result = BlockDropsStore.instance.harvestDrops(world, x, y, z);
// Case A - drops stored earlier (by this.removedByPlayer) and TE is already dead
if (result != null) return result;
// Case B - drops removed in other way (explosion) but TE may be still alive
result = getDropsWithTileEntity(world, null, x, y, z);
if (result != null) return result;
// Case C - TE is dead, just drop vanilla stuff
return super.getDrops(world, x, y, z, metadata, fortune);
}
@Override
public void setupBlock(String modId, String blockName, Class<? extends TileEntity> tileEntity, Class<? extends ItemBlock> itemClass) {
if (tileEntity != null) {
this.teClass = tileEntity;
isBlockContainer = true;
for (TileEntityCapability capability : TileEntityCapability.values())
if (capability.intf.isAssignableFrom(teClass)) teCapabilities.add(capability);
}
}
@Override
public boolean hasTileEntity(int metadata) {
return teClass != null;
}
public final static boolean isNeighborBlockSolid(IBlockAccess world, int x, int y, int z, ForgeDirection side) {
x += side.offsetX;
y += side.offsetY;
z += side.offsetZ;
return world.isSideSolid(x, y, z, side.getOpposite(), false);
}
public final static boolean areNeighborBlocksSolid(World world, int x, int y, int z, ForgeDirection... sides) {
for (ForgeDirection side : sides) {
if (isNeighborBlockSolid(world, x, y, z, side)) { return true; }
}
return false;
}
@Override
public void onNeighborBlockChange(World world, int x, int y, int z, Block neighbour) {
if (hasCapabilities(TileEntityCapability.NEIGBOUR_LISTENER, TileEntityCapability.SURFACE_ATTACHEMENT)) {
final TileEntity te = world.getTileEntity(x, y, z);
if (te instanceof INeighbourAwareTile) ((INeighbourAwareTile)te).onNeighbourChanged(neighbour);
if (te instanceof ISurfaceAttachment) {
final ForgeDirection direction = ((ISurfaceAttachment)te).getSurfaceDirection();
breakBlockIfSideNotSolid(world, x, y, z, direction);
}
}
}
@Override
public void onNeighborChange(IBlockAccess world, int x, int y, int z, int tileX, int tileY, int tileZ) {
if (hasCapability(TileEntityCapability.NEIGBOUR_TE_LISTENER)) {
final TileEntity te = world.getTileEntity(x, y, z);
if (te instanceof INeighbourTeAwareTile)
((INeighbourTeAwareTile)te).onNeighbourTeChanged(tileX, tileY, tileZ);
}
}
protected void breakBlockIfSideNotSolid(World world, int x, int y, int z, ForgeDirection direction) {
if (!isNeighborBlockSolid(world, x, y, z, direction)) {
world.func_147480_a(x, y, z, true);
}
}
@Override
public void onBlockAdded(World world, int x, int y, int z) {
super.onBlockAdded(world, x, y, z);
if (hasCapability(TileEntityCapability.ADD_LISTENER)) {
world.addBlockEvent(x, y, z, this, EVENT_ADDED, 0);
}
}
@Override
public boolean onBlockActivated(World world, int x, int y, int z, EntityPlayer player, int side, float hitX, float hitY, float hitZ) {
if (hasCapabilities(TileEntityCapability.GUI_PROVIDER, TileEntityCapability.ACTIVATE_LISTENER)) {
final TileEntity te = world.getTileEntity(x, y, z);
if (te instanceof IHasGui && ((IHasGui)te).canOpenGui(player) && !player.isSneaking()) {
if (!world.isRemote) openGui(player, world, x, y, z);
return true;
}
if (te instanceof IActivateAwareTile) return ((IActivateAwareTile)te).onBlockActivated(player, side, hitX, hitY, hitZ);
}
return false;
}
@Override
public boolean renderAsNormalBlock() {
return isOpaqueCube();
}
@Override
public boolean onBlockEventReceived(World world, int x, int y, int z, int eventId, int eventParam) {
if (eventId < 0 && !world.isRemote) {
switch (eventId) {
case EVENT_ADDED: {
if (hasCapability(TileEntityCapability.ADD_LISTENER)) {
final IAddAwareTile te = getTileEntity(world, x, y, z, IAddAwareTile.class);
if (te != null) te.onAdded();
}
}
break;
}
return false;
}
if (isBlockContainer) {
super.onBlockEventReceived(world, x, y, z, eventId, eventParam);
TileEntity te = world.getTileEntity(x, y, z);
return te != null? te.receiveClientEvent(eventId, eventParam) : false;
} else {
return super.onBlockEventReceived(world, x, y, z, eventId, eventParam);
}
}
protected void setupDimensionsFromCenter(float x, float y, float z, float width, float height, float depth) {
setupDimensions(x - width, y, z - depth, x + width, y + height, z + depth);
}
protected void setupDimensions(float minX, float minY, float minZ, float maxX, float maxY, float maxZ) {
this.minX = minX;
this.minY = minY;
this.minZ = minZ;
this.maxX = maxX;
this.maxY = maxY;
this.maxZ = maxZ;
}
@Override
public AxisAlignedBB getSelectedBoundingBoxFromPool(World world, int x, int y, int z) {
setBlockBoundsBasedOnState(world, x, y, z);
return super.getSelectedBoundingBoxFromPool(world, x, y, z);
}
@Override
public AxisAlignedBB getCollisionBoundingBoxFromPool(World world, int x, int y, int z) {
setBlockBoundsBasedOnState(world, x, y, z);
return super.getCollisionBoundingBoxFromPool(world, x, y, z);
}
@SuppressWarnings("unchecked")
public static <U> U getTileEntity(IBlockAccess world, int x, int y, int z, Class<? extends U> cls) {
final TileEntity te = world.getTileEntity(x, y, z);
return (cls.isInstance(te))? (U)te : null;
}
@SuppressWarnings("unchecked")
public <U extends TileEntity> U getTileEntity(IBlockAccess world, int x, int y, int z) {
Preconditions.checkNotNull(teClass, "This block has no tile entity");
final TileEntity te = world.getTileEntity(x, y, z);
return (teClass.isInstance(te))? (U)te : null;
}
public boolean canPlaceBlock(World world, EntityPlayer player, ItemStack stack, int x, int y, int z, ForgeDirection sideDir, Orientation blockOrientation, float hitX, float hitY, float hitZ, int newMeta) {
return getRotationMode().isPlacementValid(blockOrientation);
}
@Override
public void onBlockPlacedBy(World world, int x, int y, int z, EntityLivingBase placer, ItemStack stack) {
super.onBlockPlacedBy(world, x, y, z, placer, stack);
if (hasCapability(TileEntityCapability.PLACER_LISTENER)) {
final TileEntity te = world.getTileEntity(x, y, z);
if (te instanceof IPlacerAwareTile) ((IPlacerAwareTile)te).onBlockPlacedBy(placer, stack);
}
}
/***
* An extended block placement function which includes ALL the details
* you'll ever need.
* This is called if your ItemBlock extends ItemOpenBlock
*/
public void afterBlockPlaced(World world, EntityPlayer player, ItemStack stack, int x, int y, int z, ForgeDirection side, Orientation blockOrientation, float hitX, float hitY, float hitZ, int itemMeta) {
int blockMeta = getRotationMode().toValue(blockOrientation);
// silently set meta, since we want to notify TE before neighbors
world.setBlockMetadataWithNotify(x, y, z, blockMeta, BlockNotifyFlags.NONE);
notifyTileEntity(world, player, stack, x, y, z, side, blockOrientation, hitX, hitY, hitZ);
world.markBlockForUpdate(x, y, z);
if (!world.isRemote) world.notifyBlockChange(x, y, z, this);
}
protected void notifyTileEntity(World world, EntityPlayer player, ItemStack stack, int x, int y, int z, ForgeDirection side, Orientation blockOrientation, float hitX, float hitY, float hitZ) {
if (hasCapability(TileEntityCapability.PLACE_LISTENER)) {
final TileEntity te = world.getTileEntity(x, y, z);
if (te instanceof IPlaceAwareTile) ((IPlaceAwareTile)te).onBlockPlacedBy(player, side, stack, hitX, hitY, hitZ);
}
}
protected void setRotationMeta(World world, int x, int y, int z, Orientation blockOrientation) {
int blockMeta = getRotationMode().toValue(blockOrientation);
world.setBlockMetadataWithNotify(x, y, z, blockMeta, BlockNotifyFlags.ALL);
}
public Orientation calculatePlacementSide(EntityPlayer player, ForgeDirection side) {
if (blockPlacementMode == BlockPlacementMode.SURFACE) {
return getRotationMode().getPlacementOrientationFromSurface(side);
} else {
return getRotationMode().getPlacementOrientationFromEntity(player);
}
}
@Override
public final boolean canPlaceBlockOnSide(World world, int x, int y, int z, int side) {
return canPlaceBlockOnSide(world, x, y, z, ForgeDirection.getOrientation(side).getOpposite());
}
public boolean canPlaceBlockOnSide(World world, int x, int y, int z, ForgeDirection side) {
return canPlaceBlockAt(world, x, y, z); // default to vanilla rules
}
protected boolean isOnTopOfSolidBlock(World world, int x, int y, int z, ForgeDirection side) {
return side == ForgeDirection.DOWN
&& isNeighborBlockSolid(world, x, y, z, ForgeDirection.DOWN);
}
public void setTexture(ForgeDirection direction, IIcon icon) {
textures[direction.ordinal()] = icon;
}
public void setTextures(IIcon icon, ForgeDirection... directions) {
for (ForgeDirection direction : directions)
textures[direction.ordinal()] = icon;
}
protected IIcon getUnrotatedTexture(ForgeDirection direction) {
if (direction != ForgeDirection.UNKNOWN) {
final int directionId = direction.ordinal();
if (textures[directionId] != null) return textures[directionId];
}
return blockIcon;
}
/**
* This method should be overriden if needed. We're getting the texture for
* the UNROTATED block for a particular side (direction). Feel free to look
* up data in the TileEntity to grab additional information here
*/
public IIcon getUnrotatedTexture(ForgeDirection direction, IBlockAccess world, int x, int y, int z) {
return getUnrotatedTexture(direction);
}
/**
* Get the texture, but rotate the block around the metadata rotation first
*/
@Override
@SideOnly(Side.CLIENT)
public IIcon getIcon(IBlockAccess world, int x, int y, int z, int side) {
final ForgeDirection direction = rotateSideByMetadata(side, world.getBlockMetadata(x, y, z));
IIcon iconOverride = null;
if (hasCapability(TileEntityCapability.ICON_PROVIDER)) {
IIconProvider provider = getTileEntity(world, x, y, z, IIconProvider.class);
if (provider != null) iconOverride = provider.getIcon(direction);
}
return iconOverride != null? iconOverride : getUnrotatedTexture(direction, world, x, y, z);
}
/***
* This is called by the blockrenderer when rendering an item into the
* inventory.
* We'll return the block, rotated as we wish, but without any additional
* texture
* changes that are caused by the blocks current state
*/
@Override
@SideOnly(Side.CLIENT)
public final IIcon getIcon(int side, int metadata) {
ForgeDirection newRotation = rotateSideByMetadata(side, metadata);
return getUnrotatedTexture(newRotation);
}
public ForgeDirection rotateSideByMetadata(int side, int metadata) {
final ForgeDirection dir = ForgeDirection.getOrientation(side);
return rotateSideByMetadata(dir, metadata);
}
public ForgeDirection rotateSideByMetadata(ForgeDirection side, int metadata) {
final Orientation rotation = getOrientation(metadata);
return rotation.globalToLocalDirection(side);
}
public Vec3 rotateVectorByMetadata(Vec3 vec, int metadata) {
return rotateVectorByMetadata(vec.xCoord, vec.yCoord, vec.zCoord, metadata);
}
public Vec3 rotateVectorByMetadata(double x, double y, double z, int metadata) {
final Orientation rotation = getOrientation(metadata);
return rotateVectorByDirection(rotation, x, y, z);
}
public Vec3 rotateVectorByDirection(Orientation orientation, double x, double y, double z) {
return BlockSpaceTransform.instance.mapWorldToBlock(orientation, x, y, z);
}
public void setDefaultTexture(IIcon icon) {
this.blockIcon = icon;
}
protected abstract Object getModInstance();
public void openGui(EntityPlayer player, World world, int x, int y, int z) {
player.openGui(getModInstance(), OPEN_MODS_TE_GUI, world, x, y, z);
}
public final boolean shouldRenderBlock() {
return renderMode != RenderMode.TESR_ONLY;
}
public final boolean shouldRenderTesrInInventory() {
return renderMode != RenderMode.BLOCK_ONLY;
}
public boolean canRotateWithTool() {
return getRotationMode() != BlockRotationMode.NONE;
}
public RotationHelper createRotationHelper(World world, int x, int y, int z) {
return new RotationHelper(getRotationMode(), world, x, y, z);
}
@Override
public boolean rotateBlock(World worldObj, int x, int y, int z, ForgeDirection axis) {
if (!canRotateWithTool()) return false;
if (!createRotationHelper(worldObj, x, y, z).rotateWithTool(axis)) return false;
if (teCapabilities.contains(TileEntityCapability.SURFACE_ATTACHEMENT)) {
final ISurfaceAttachment te = getTileEntity(worldObj, x, y, z, ISurfaceAttachment.class);
if (te == null) return false;
breakBlockIfSideNotSolid(worldObj, x, y, z, te.getSurfaceDirection());
}
return true;
}
@Override
public ForgeDirection[] getValidRotations(World worldObj, int x, int y, int z) {
if (!canRotateWithTool()) return RotationAxis.NO_AXIS;
return getRotationMode().rotationAxes;
}
}