package slimeknights.tconstruct.shared.block; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import net.minecraft.block.Block; import net.minecraft.block.ITileEntityProvider; import net.minecraft.block.material.Material; import net.minecraft.block.properties.IProperty; import net.minecraft.block.state.BlockStateContainer; import net.minecraft.block.state.IBlockState; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.BlockRenderLayer; import net.minecraft.util.EnumFacing; import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.RayTraceResult; import net.minecraft.util.math.Vec3d; import net.minecraft.world.IBlockAccess; import net.minecraft.world.World; import net.minecraftforge.common.property.ExtendedBlockState; import net.minecraftforge.common.property.IExtendedBlockState; import net.minecraftforge.common.property.IUnlistedProperty; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; import java.util.List; import javax.annotation.Nonnull; import slimeknights.mantle.block.BlockInventory; import slimeknights.mantle.property.PropertyString; import slimeknights.mantle.property.PropertyUnlistedDirection; import slimeknights.mantle.tileentity.TileInventory; import slimeknights.tconstruct.library.utils.TagUtil; import slimeknights.tconstruct.shared.tileentity.TileTable; public class BlockTable extends BlockInventory implements ITileEntityProvider { public static final PropertyString TEXTURE = new PropertyString("texture"); public static final PropertyTableItem INVENTORY = new PropertyTableItem(); public static final PropertyUnlistedDirection FACING = new PropertyUnlistedDirection("facing", EnumFacing.Plane.HORIZONTAL); public BlockTable(Material materialIn) { super(materialIn); } @Override public boolean isOpaqueCube(IBlockState state) { return false; } @Override public boolean isFullCube(IBlockState state) { return false; } @Nonnull @Override @SideOnly(Side.CLIENT) public BlockRenderLayer getBlockLayer() { return BlockRenderLayer.CUTOUT; } @Override public boolean shouldSideBeRendered(IBlockState blockState, @Nonnull IBlockAccess blockAccess, @Nonnull BlockPos pos, EnumFacing side) { return true; } @Override public boolean hasTileEntity(IBlockState state) { return true; } @Nonnull @Override public TileEntity createNewTileEntity(@Nonnull World worldIn, int meta) { // table without inventory by default return new TileTable("tile.table", 0, 0); } @Override public boolean openGui(EntityPlayer player, World world, BlockPos pos) { // no gui by default return false; } @Nonnull @Override protected BlockStateContainer createBlockState() { return new ExtendedBlockState(this, new IProperty[0], new IUnlistedProperty[]{TEXTURE, INVENTORY, FACING}); } @Nonnull @Override public IBlockState getExtendedState(@Nonnull IBlockState state, IBlockAccess world, BlockPos pos) { IExtendedBlockState extendedState = (IExtendedBlockState) state; TileEntity te = world.getTileEntity(pos); if(te != null && te instanceof TileTable) { TileTable table = (TileTable) te; return table.writeExtendedBlockState(extendedState); } return super.getExtendedState(state, world, pos); } @Override public void onBlockPlacedBy(World world, BlockPos pos, IBlockState state, EntityLivingBase placer, ItemStack stack) { super.onBlockPlacedBy(world, pos, state, placer, stack); NBTTagCompound tag = TagUtil.getTagSafe(stack); TileEntity te = world.getTileEntity(pos); if(te != null && te instanceof TileTable) { TileTable table = (TileTable) te; NBTTagCompound feetTag = tag.getCompoundTag(TileTable.FEET_TAG); if(feetTag == null) { feetTag = new NBTTagCompound(); } table.updateTextureBlock(feetTag); table.setFacing(placer.getHorizontalFacing().getOpposite()); // check if we also have an inventory if(tag.hasKey("inventory")) { table.readInventoryFromNBT(tag.getCompoundTag("inventory")); } // check if we have a custom name if (stack.hasDisplayName()) { table.setCustomName(stack.getDisplayName()); } } } @Override public boolean removedByPlayer(@Nonnull IBlockState state, @Nonnull World world, @Nonnull BlockPos pos, @Nonnull EntityPlayer player, boolean willHarvest) { // we pull up a few calls to this point in time because we still have the TE here // the execution otherwise is equivalent to vanilla order this.onBlockDestroyedByPlayer(world, pos, state); if(willHarvest) { this.harvestBlock(world, player, pos, state, world.getTileEntity(pos), player.getHeldItemMainhand()); } // clear the inventory if we kept it on the item // otherwise we'd dupe it since it'd also spill when we set the block to air if(keepInventory(state)) { TileEntity te = world.getTileEntity(pos); if(te instanceof TileInventory) { ((TileInventory) te).clear(); } } world.setBlockToAir(pos); // return false to prevent the above called functions to be called again // side effect of this is that no xp will be dropped. but it shoudln't anyway from a table :P return false; } protected boolean keepInventory(IBlockState state) { return false; } // saves the info about the TE onto the itemstack private void writeDataOntoItemstack(@Nonnull ItemStack item, @Nonnull IBlockAccess world, @Nonnull BlockPos pos, @Nonnull IBlockState state, boolean inventorySave) { // get block data from the block TileEntity te = world.getTileEntity(pos); if(te != null && te instanceof TileTable) { TileTable table = (TileTable) te; NBTTagCompound tag = TagUtil.getTagSafe(item); // texture NBTTagCompound data = table.getTextureBlock(); if (!data.hasNoTags()) { tag.setTag(TileTable.FEET_TAG, data); } // save inventory, if not empty if(inventorySave && keepInventory(state)) { if (!table.isInventoryEmpty()) { NBTTagCompound inventoryTag = new NBTTagCompound(); table.writeInventoryToNBT(inventoryTag); tag.setTag("inventory", inventoryTag); table.clear(); } } if (!tag.hasNoTags()) { item.setTagCompound(tag); } } } @Override public void dropBlockAsItemWithChance(@Nonnull World worldIn, @Nonnull BlockPos pos, @Nonnull IBlockState state, float chance, int fortune) { if(!worldIn.isRemote && !worldIn.restoringBlockSnapshots) { List<ItemStack> items = this.getDrops(worldIn, pos, state, fortune); chance = net.minecraftforge.event.ForgeEventFactory.fireBlockHarvesting(items, worldIn, pos, state, fortune, chance, false, harvesters.get()); for(ItemStack item : items) { // save the data from the block onto the item if(item.getItem() == Item.getItemFromBlock(this)) { writeDataOntoItemstack(item, worldIn, pos, state, chance >= 1f); } } // also drop inventory contents (that hasn't been written on the TE above) TileEntity te = worldIn.getTileEntity(pos); if(te instanceof TileInventory) { TileInventory tileInventory = (TileInventory) te; for(int i = 0; i < tileInventory.getSizeInventory(); i++) { ItemStack itemStack = tileInventory.getStackInSlot(i); if(itemStack != null) { items.add(itemStack); } } // clear since otherwise we might dupe tileInventory.clear(); // if the TE had a custom name, let's propagate that to the ItemStack if (tileInventory.hasCustomName()) { for(ItemStack item : items) { if(item.getItem() == Item.getItemFromBlock(this)) { item.setStackDisplayName(tileInventory.getName()); item.setRepairCost(0); // seems to be always added when renaming in anvil, need to restore it here or won't stack } } } } for(ItemStack item : items) { if(worldIn.rand.nextFloat() <= chance) { spawnAsEntity(worldIn, pos, item); } } } } @Nonnull @Override public ItemStack getPickBlock(@Nonnull IBlockState state, RayTraceResult target, @Nonnull World world, @Nonnull BlockPos pos, EntityPlayer player) { List<ItemStack> drops = getDrops(world, pos, world.getBlockState(pos), 0); if(drops.size() > 0) { ItemStack stack = drops.get(0); writeDataOntoItemstack(stack, world, pos, state, false); return stack; } return super.getPickBlock(state, target, world, pos, player); } public static ItemStack createItemstack(BlockTable table, int tableMeta, Block block, int blockMeta) { ItemStack stack = new ItemStack(table, 1, tableMeta); if(block != null) { ItemStack blockStack = new ItemStack(block, 1, blockMeta); NBTTagCompound tag = new NBTTagCompound(); NBTTagCompound subTag = new NBTTagCompound(); blockStack.writeToNBT(subTag); tag.setTag(TileTable.FEET_TAG, subTag); stack.setTagCompound(tag); } return stack; } @Override public boolean isSideSolid(@Nonnull IBlockState base_state, @Nonnull IBlockAccess world, @Nonnull BlockPos pos, @Nonnull EnumFacing side) { return side == EnumFacing.UP || super.isSideSolid(base_state, world, pos, side); } /* Bounds */ private static ImmutableList<AxisAlignedBB> BOUNDS_Table = ImmutableList.of( new AxisAlignedBB(0, 0.75, 0, 1, 1, 1), new AxisAlignedBB(0, 0, 0, 0.25, 0.75, 0.25), new AxisAlignedBB(0.75, 0, 0, 1, 0.75, 0.25), new AxisAlignedBB(0.75, 0, 0.75, 1, 0.75, 1), new AxisAlignedBB(0, 0, 0.75, 0.25, 0.75, 1) ); @Override public RayTraceResult collisionRayTrace(IBlockState blockState, @Nonnull World worldIn, @Nonnull BlockPos pos, @Nonnull Vec3d start, @Nonnull Vec3d end) { // basically the same BlockStairs does // Raytrace through all AABBs (plate, legs) and return the nearest one return raytraceMultiAABB(BOUNDS_Table, pos, start, end); } public static RayTraceResult raytraceMultiAABB(List<AxisAlignedBB> aabbs, BlockPos pos, Vec3d start, Vec3d end) { List<RayTraceResult> list = Lists.<RayTraceResult>newArrayList(); for(AxisAlignedBB axisalignedbb : aabbs) { list.add(rayTrace2(pos, start, end, axisalignedbb)); } RayTraceResult raytraceresult1 = null; double d1 = 0.0D; for(RayTraceResult raytraceresult : list) { if(raytraceresult != null) { double d0 = raytraceresult.hitVec.squareDistanceTo(end); if(d0 > d1) { raytraceresult1 = raytraceresult; d1 = d0; } } } return raytraceresult1; } // Block.raytrace private static RayTraceResult rayTrace2(BlockPos pos, Vec3d start, Vec3d end, AxisAlignedBB boundingBox) { Vec3d vec3d = start.subtract((double) pos.getX(), (double) pos.getY(), (double) pos.getZ()); Vec3d vec3d1 = end.subtract((double) pos.getX(), (double) pos.getY(), (double) pos.getZ()); RayTraceResult raytraceresult = boundingBox.calculateIntercept(vec3d, vec3d1); return raytraceresult == null ? null : new RayTraceResult(raytraceresult.hitVec.addVector((double) pos.getX(), (double) pos.getY(), (double) pos.getZ()), raytraceresult.sideHit, pos); } @Override public boolean rotateBlock(World world, BlockPos pos, EnumFacing axis) { TileEntity te = world.getTileEntity(pos); if(te instanceof TileTable) { TileTable table = (TileTable) te; table.setFacing(table.getFacing().rotateY()); IBlockState state = world.getBlockState(pos); world.notifyBlockUpdate(pos, state, state, 0); return true; } return false; } }