<repo_name>Describa14/BetterStorageToo<file_sep>/src/main/java/io/github/tehstoneman/betterstorage/proxy/IProxy.java
package io.github.tehstoneman.betterstorage.proxy;

import net.minecraft.world.World;

public interface IProxy
{
	public World getClientWorld();
}
<file_sep>/src/main/java/io/github/tehstoneman/betterstorage/common/capabilities/CapabilityCrate.java
package io.github.tehstoneman.betterstorage.common.capabilities;

import io.github.tehstoneman.betterstorage.ModInfo;
import io.github.tehstoneman.betterstorage.api.ICrateStorage;
import io.github.tehstoneman.betterstorage.common.world.storage.CrateStorage;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.INBT;
import net.minecraft.util.Direction;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.CapabilityInject;
import net.minecraftforge.common.capabilities.CapabilityManager;
import net.minecraftforge.common.capabilities.ICapabilitySerializable;
import net.minecraftforge.common.util.LazyOptional;

public class CapabilityCrate
{
	@CapabilityInject( ICrateStorage.class )
	public static final Capability< ICrateStorage >	CRATE_PILE_CAPABILITY	= null;

	public static ResourceLocation					CAPABILITY_RESOURCE		= new ResourceLocation( ModInfo.MOD_ID, "crate_pile" );

	public static void register()
	{
		CapabilityManager.INSTANCE.register( ICrateStorage.class, new Capability.IStorage< ICrateStorage >()
		{
			@Override
			public INBT writeNBT( Capability< ICrateStorage > capability, ICrateStorage instance, Direction side )
			{
				return instance.serializeNBT();
			}

			@Override
			public void readNBT( Capability< ICrateStorage > capability, ICrateStorage instance, Direction side, INBT nbt )
			{
				if( !( instance instanceof CrateStorage ) )
					throw new IllegalArgumentException( "Can not deserialize to an instance that isn't the default implementation" );
				instance.deserializeNBT( (CompoundNBT)nbt );
			}
		}, CrateStorage::new );
	}

	public static class Provider implements ICapabilitySerializable< CompoundNBT >
	{
		private final Capability< ICrateStorage >	capability;
		private final LazyOptional					capabilityHandler;

		public Provider( Capability< ICrateStorage > capability )
		{
			this.capability = capability;
			capabilityHandler = LazyOptional.of( () -> this.capability );
		}

		@Override
		public <T> LazyOptional< T > getCapability( Capability< T > capability, Direction side )
		{
			return CRATE_PILE_CAPABILITY.orEmpty( capability, capabilityHandler );
			// return LazyOptional.empty();
		}

		@Override
		public CompoundNBT serializeNBT()
		{
			return (CompoundNBT)capability.writeNBT( capability.getDefaultInstance(), null );
		}

		@Override
		public void deserializeNBT( CompoundNBT nbt )
		{
			capability.readNBT( capability.getDefaultInstance(), null, nbt );
		}
	}
}
<file_sep>/src/main/java/io/github/tehstoneman/betterstorage/common/block/BlockReinforcedChest.java
package io.github.tehstoneman.betterstorage.common.block;

import java.util.List;

import javax.annotation.Nullable;

import io.github.tehstoneman.betterstorage.api.ConnectedType;
import io.github.tehstoneman.betterstorage.api.lock.ILock;
import io.github.tehstoneman.betterstorage.api.lock.LockInteraction;
import io.github.tehstoneman.betterstorage.common.enchantment.EnchantmentBetterStorage;
import io.github.tehstoneman.betterstorage.common.tileentity.TileEntityContainer;
import io.github.tehstoneman.betterstorage.common.tileentity.TileEntityReinforcedChest;
import io.github.tehstoneman.betterstorage.common.world.storage.HexKeyConfig;
import net.minecraft.block.Block;
import net.minecraft.block.BlockRenderType;
import net.minecraft.block.BlockState;
import net.minecraft.block.IWaterLoggable;
import net.minecraft.block.SoundType;
import net.minecraft.block.material.Material;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.item.ItemEntity;
import net.minecraft.entity.passive.CatEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.ServerPlayerEntity;
import net.minecraft.fluid.Fluids;
import net.minecraft.fluid.IFluidState;
import net.minecraft.inventory.container.INamedContainerProvider;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.item.ItemStack;
import net.minecraft.state.BooleanProperty;
import net.minecraft.state.DirectionProperty;
import net.minecraft.state.StateContainer;
import net.minecraft.state.properties.BlockStateProperties;
import net.minecraft.stats.Stat;
import net.minecraft.stats.Stats;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.ActionResultType;
import net.minecraft.util.Direction;
import net.minecraft.util.Hand;
import net.minecraft.util.Mirror;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.Rotation;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.world.Explosion;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.IWorld;
import net.minecraft.world.IWorldReader;
import net.minecraft.world.World;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fml.network.NetworkHooks;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;

public class BlockReinforcedChest extends BlockConnectableContainer implements IWaterLoggable
{
	public static final BooleanProperty		WATERLOGGED		= BlockStateProperties.WATERLOGGED;
	protected static final VoxelShape		SHAPE_NORTH		= Block.makeCuboidShape( 1.0D, 0.0D, 0.0D, 15.0D, 14.0D, 15.0D );
	protected static final VoxelShape		SHAPE_SOUTH		= Block.makeCuboidShape( 1.0D, 0.0D, 1.0D, 15.0D, 14.0D, 16.0D );
	protected static final VoxelShape		SHAPE_WEST		= Block.makeCuboidShape( 0.0D, 0.0D, 1.0D, 15.0D, 14.0D, 15.0D );
	protected static final VoxelShape		SHAPE_EAST		= Block.makeCuboidShape( 1.0D, 0.0D, 1.0D, 16.0D, 14.0D, 15.0D );
	protected static final VoxelShape		SHAPE_SINGLE	= Block.makeCuboidShape( 1.0D, 0.0D, 1.0D, 15.0D, 14.0D, 15.0D );
	public static final DirectionProperty	FACING			= BlockStateProperties.HORIZONTAL_FACING;

	public BlockReinforcedChest()
	{
		this( Block.Properties.create( Material.WOOD ).hardnessAndResistance( 5.0F, 6.0F ).sound( SoundType.WOOD ) );
	}

	public BlockReinforcedChest( Properties properties )
	{
		super( properties );

		//@formatter:off
		setDefaultState( stateContainer.getBaseState().with( FACING, Direction.NORTH )
													  .with( WATERLOGGED, Boolean.valueOf( false ) ) );
		//@formatter:on
	}

	@Override
	protected void fillStateContainer( StateContainer.Builder< Block, BlockState > builder )
	{
		super.fillStateContainer( builder );
		builder.add( FACING, WATERLOGGED );
	}

	/*
	 * ======================
	 * TileEntity / Rendering
	 * ======================
	 */

	@Override
	public BlockRenderType getRenderType( BlockState state )
	{
		// return BlockRenderType.MODEL;
		return BlockRenderType.ENTITYBLOCK_ANIMATED;
	}

	@Override
	public TileEntity createTileEntity( BlockState state, IBlockReader world )
	{
		return new TileEntityReinforcedChest();
	}

	/*
	 * =========
	 * Placement
	 * =========
	 */

	@Override
	public VoxelShape getShape( BlockState state, IBlockReader worldIn, BlockPos pos, ISelectionContext context )
	{
		if( state.get( TYPE ) == ConnectedType.SINGLE )
			return SHAPE_SINGLE;
		switch( getDirectionToAttached( state ) )
		{
		case NORTH:
		default:
			return SHAPE_NORTH;
		case SOUTH:
			return SHAPE_SOUTH;
		case WEST:
			return SHAPE_WEST;
		case EAST:
			return SHAPE_EAST;
		}
	}

	@Override
	public BlockState getStateForPlacement( BlockItemUseContext context )
	{
		ConnectedType connectedType = ConnectedType.SINGLE;
		Direction direction = context.getPlacementHorizontalFacing().getOpposite();
		final IFluidState fluidState = context.getWorld().getFluidState( context.getPos() );
		final boolean sneaking = context.func_225518_g_();

		final Direction direction1 = context.getFace();
		if( direction1.getAxis().isHorizontal() && sneaking )
		{
			final Direction direction2 = getDirectionToAttach( context, direction1.getOpposite() );
			if( direction2 != null && direction2.getAxis() != direction1.getAxis() )
			{
				direction = direction2;
				connectedType = direction2.rotateYCCW() == direction1.getOpposite() ? ConnectedType.MASTER : ConnectedType.SLAVE;
			}
		}

		if( connectedType == ConnectedType.SINGLE && !sneaking )
			if( direction == getDirectionToAttach( context, direction.rotateY() ) )
				connectedType = ConnectedType.SLAVE;
			else if( direction == getDirectionToAttach( context, direction.rotateYCCW() ) )
				connectedType = ConnectedType.MASTER;

		return getDefaultState().with( FACING, direction ).with( TYPE, connectedType ).with( WATERLOGGED,
				Boolean.valueOf( fluidState.getFluid() == Fluids.WATER ) );
	}

	@Override
	public BlockState updatePostPlacement( BlockState thisState, Direction facing, BlockState facingState, IWorld world, BlockPos thisPos,
			BlockPos facingPos )
	{
		if( thisState.get( WATERLOGGED ) )
			world.getPendingFluidTicks().scheduleTick( thisPos, Fluids.WATER, Fluids.WATER.getTickRate( world ) );

		if( facingState.getBlock() == this && facing.getAxis().isHorizontal() )
		{
			final ConnectedType facingType = facingState.get( TYPE );

			if( thisState.get( TYPE ) == ConnectedType.SINGLE && facingType != ConnectedType.SINGLE
					&& thisState.get( FACING ) == facingState.get( FACING ) && getDirectionToAttached( facingState ) == facing.getOpposite() )
			{
				final ConnectedType newType = facingType.opposite();
				final TileEntityReinforcedChest thisChest = getChestAt( (World)world, thisPos );
				final TileEntityReinforcedChest facingChest = getChestAt( (World)world, facingPos );
				if( newType == ConnectedType.SLAVE )
				{
					facingChest.setConfig( thisChest.getConfig() );
					thisChest.setConfig( new HexKeyConfig() );
				}
				return thisState.with( TYPE, newType );
			}
		}
		else if( getDirectionToAttached( thisState ) == facing )
			return thisState.with( TYPE, ConnectedType.SINGLE );

		return super.updatePostPlacement( thisState, facing, facingState, world, thisPos, facingPos );
	}

	/**
	 * Returns facing pointing to a chest to form a double chest with, null otherwise
	 *
	 * @param context
	 *            Context
	 * @param facing
	 *            The offset direction
	 * @return Facing direction
	 */
	@Nullable
	private Direction getDirectionToAttach( BlockItemUseContext context, Direction facing )
	{
		final BlockState blockState = context.getWorld().getBlockState( context.getPos().offset( facing ) );
		return blockState.getBlock() == this && blockState.get( TYPE ) == ConnectedType.SINGLE ? blockState.get( FACING ) : null;
	}

	/**
	 * Returns a facing pointing from the given state to its attached double chest
	 *
	 * @param state
	 *            The BlockState of the block to attach to
	 * @return The direction of the attached block
	 */
	public static Direction getDirectionToAttached( BlockState state )
	{
		final Direction direction = state.get( FACING );
		return state.get( TYPE ) == ConnectedType.SLAVE ? direction.rotateY() : direction.rotateYCCW();
	}

	@Override
	public void onBlockPlacedBy( World worldIn, BlockPos pos, BlockState state, LivingEntity placer, ItemStack stack )
	{
		if( stack.hasDisplayName() )
		{
			final TileEntity tileentity = worldIn.getTileEntity( pos );
			if( tileentity instanceof TileEntityReinforcedChest )
				( (TileEntityReinforcedChest)tileentity ).setCustomName( stack.getDisplayName() );
		}
	}

	@Override
	public void onReplaced( BlockState state, World world, BlockPos pos, BlockState newState, boolean isMoving )
	{
		if( state.getBlock() != newState.getBlock() )
		{
			final TileEntity tileentity = world.getTileEntity( pos );
			if( tileentity instanceof TileEntityContainer )
			{
				if( state.get( TYPE ) == ConnectedType.MASTER )
				{
					final TileEntityReinforcedChest thisChest = getChestAt( world, pos );
					final TileEntityReinforcedChest facingChest = getChestAt( world, pos.offset( getDirectionToAttached( state ) ) );

					facingChest.setConfig( thisChest.getConfig() );
					thisChest.setConfig( new HexKeyConfig() );
				}
				( (TileEntityContainer)tileentity ).dropInventoryItems();
				world.updateComparatorOutputLevel( pos, this );
			}

			super.onReplaced( state, world, pos, newState, isMoving );
		}
	}

	/*
	 * ===========
	 * Adjustments
	 * ===========
	 */

	@Override
	public BlockState rotate( BlockState state, Rotation rot )
	{
		return state.with( FACING, rot.rotate( state.get( FACING ) ) );
	}

	@Override
	public BlockState mirror( BlockState state, Mirror mirrorIn )
	{
		return state.rotate( mirrorIn.toRotation( state.get( FACING ) ) );
	}

	@Override
	public boolean hasComparatorInputOverride( BlockState state )
	{
		return true;
	}

	@Override
	public int getComparatorInputOverride( BlockState blockState, World worldIn, BlockPos pos )
	{
		return calcRedstoneFromInventory(
				( (TileEntityReinforcedChest)worldIn.getTileEntity( pos ) ).getCapability( CapabilityItemHandler.ITEM_HANDLER_CAPABILITY ) );
	}

	public static int calcRedstoneFromInventory( @Nullable LazyOptional< IItemHandler > lazyOptional )
	{
		if( !lazyOptional.isPresent() )
			return 0;
		else
		{
			final IItemHandler inventory = lazyOptional.orElseGet( null );
			int i = 0;
			float f = 0.0F;

			for( int j = 0; j < inventory.getSlots(); ++j )
			{
				final ItemStack itemstack = inventory.getStackInSlot( j );
				if( !itemstack.isEmpty() )
				{
					f += (float)itemstack.getCount() / itemstack.getMaxStackSize();
					++i;
				}
			}

			f = f / inventory.getSlots();
			return MathHelper.floor( f * 14.0F ) + ( i > 0 ? 1 : 0 );
		}
	}

	@Override
	public boolean canProvidePower( BlockState state )
	{
		return true;
	}

	@Override
	public int getWeakPower( BlockState blockState, IBlockReader blockAccess, BlockPos pos, Direction side )
	{
		final TileEntity tileEntity = blockAccess.getTileEntity( pos );
		if( tileEntity instanceof TileEntityReinforcedChest )
		{
			final TileEntityReinforcedChest chest = (TileEntityReinforcedChest)tileEntity;
			return chest.isPowered() ? MathHelper.clamp( chest.getPlayersUsing(), 0, 15 ) : 0;
		}
		return 0;
	}

	@Override
	public int getStrongPower( BlockState blockState, IBlockReader blockAccess, BlockPos pos, Direction side )
	{
		return side == Direction.UP ? blockState.getWeakPower( blockAccess, pos, side ) : 0;
	}

	/*
	 * ===========
	 * Interaction
	 * ===========
	 */

	@Override
	public ActionResultType onBlockActivated( BlockState state, World worldIn, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit )
	{
		if( worldIn.isRemote )
			return ActionResultType.SUCCESS;
		else
		{
			final TileEntityReinforcedChest tileChest = getChestAt( worldIn, pos );
			if( tileChest != null && tileChest.isLocked() )
			{
				if( !tileChest.unlockWith( player.getHeldItem( hand ) ) )
				{
					final ItemStack lock = tileChest.getLock();
					( (ILock)lock.getItem() ).applyEffects( lock, tileChest, player, LockInteraction.OPEN );
					return ActionResultType.PASS;
				}
				if( player.isCrouching() )
				{
					worldIn.addEntity( new ItemEntity( worldIn, pos.getX(), pos.getY(), pos.getZ(), tileChest.getLock().copy() ) );
					tileChest.setLock( ItemStack.EMPTY );
					return ActionResultType.SUCCESS;
				}
			}
			final INamedContainerProvider chest = getContainer( state, worldIn, pos );
			if( chest != null )
			{
				NetworkHooks.openGui( (ServerPlayerEntity)player, chest, pos );
				player.addStat( getOpenStat() );
			}

			return ActionResultType.SUCCESS;
		}
	}

	@Nullable
	public static TileEntityReinforcedChest getChestAt( World world, BlockPos pos )
	{
		final TileEntity tileEntity = world.getTileEntity( pos );
		if( tileEntity instanceof TileEntityReinforcedChest )
			return (TileEntityReinforcedChest)tileEntity;
		return null;
	}

	protected Stat< ResourceLocation > getOpenStat()
	{
		return Stats.CUSTOM.get( Stats.OPEN_CHEST );
	}

	@Override
	@Nullable
	public INamedContainerProvider getContainer( BlockState state, World worldIn, BlockPos pos )
	{
		return this.getContainer( state, worldIn, pos, false );
	}

	/**
	 * Gets the chest inventory at the given location, returning null if there is no chest at that location or optionally
	 * if the chest is blocked. Handles large chests.
	 *
	 * @param state
	 *            The current state
	 * @param worldIn
	 *            The world
	 * @param pos
	 *            The position to check
	 * @param allowBlockedChest
	 *            If false, then if the chest is blocked then <code>null</code> will be returned. If true,
	 *            then the chest can still be blocked (used by hoppers).
	 * @return The chest at the position, or null if none.
	 */
	@Nullable
	public TileEntityReinforcedChest getContainer( BlockState state, World worldIn, BlockPos pos, boolean allowBlockedChest )
	{
		final TileEntity tileentity = worldIn.getTileEntity( pos );
		if( !( tileentity instanceof TileEntityReinforcedChest ) )
			return null;
		else if( !allowBlockedChest && isBlocked( worldIn, pos ) )
			return null;
		else
		{
			final TileEntityReinforcedChest ilockablecontainer = (TileEntityReinforcedChest)tileentity;
			final ConnectedType chesttype = state.get( TYPE );
			if( chesttype == ConnectedType.SINGLE )
				return ilockablecontainer;
			else
			{
				final BlockPos blockpos = pos.offset( getDirectionToAttached( state ) );
				final BlockState iblockstate = worldIn.getBlockState( blockpos );
				if( iblockstate.getBlock() == this )
				{
					final ConnectedType chesttype1 = iblockstate.get( TYPE );
					if( chesttype1 != ConnectedType.SINGLE && chesttype != chesttype1 && iblockstate.get( FACING ) == state.get( FACING ) )
						if( !allowBlockedChest && isBlocked( worldIn, blockpos ) )
							return null;
				}

				return ilockablecontainer;
			}
		}
	}

	private static boolean isBlocked( IWorld world, BlockPos pos )
	{
		return isBelowSolidBlock( world, pos ) || isCatSittingOn( world, pos );
	}

	private static boolean isBelowSolidBlock( IBlockReader reader, BlockPos worldIn )
	{
		final BlockPos blockpos = worldIn.up();
		return reader.getBlockState( blockpos ).isNormalCube( reader, blockpos );
	}

	private static boolean isCatSittingOn( IWorld world, BlockPos pos )
	{
		final List< CatEntity > list = world.getEntitiesWithinAABB( CatEntity.class,
				new AxisAlignedBB( pos.getX(), pos.getY() + 1, pos.getZ(), pos.getX() + 1, pos.getY() + 2, pos.getZ() + 1 ) );
		if( !list.isEmpty() )
			for( final CatEntity catentity : list )
				if( catentity.isSitting() )
					return true;

		return false;
	}

	/*
	 * =====
	 * Fluid
	 * =====
	 */

	@Override
	public IFluidState getFluidState( BlockState blockState )
	{
		return blockState.get( WATERLOGGED ) ? Fluids.WATER.getStillFluidState( false ) : super.getFluidState( blockState );

	}

	@Override
	public float getExplosionResistance( BlockState state, IWorldReader world, BlockPos pos, @Nullable Entity exploder, Explosion explosion )
	{
		final TileEntityReinforcedChest chest = getChestAt( (World)world, pos );
		if( chest != null && chest.isLocked() )
		{
			final int resist = EnchantmentHelper.getEnchantmentLevel( EnchantmentBetterStorage.PERSISTANCE.get(), chest.getLock() ) + 1;
			return super.getExplosionResistance( state, world, pos, exploder, explosion ) * resist * 2;
		}
		return super.getExplosionResistance( state, world, pos, exploder, explosion );
	}
}
<file_sep>/src/main/java/io/github/tehstoneman/betterstorage/client/gui/GuiCrate.java
package io.github.tehstoneman.betterstorage.client.gui;

import com.mojang.blaze3d.platform.GlStateManager;

import io.github.tehstoneman.betterstorage.BetterStorage;
import io.github.tehstoneman.betterstorage.ModInfo;
import io.github.tehstoneman.betterstorage.client.renderer.Resources;
import io.github.tehstoneman.betterstorage.common.inventory.ContainerCrate;
import net.minecraft.client.gui.screen.inventory.ContainerScreen;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TranslationTextComponent;

public class GuiCrate extends ContainerScreen< ContainerCrate >
{
	private final int	rows;
	private final int	offsetY;

	public GuiCrate( ContainerCrate container, PlayerInventory playerInventory, ITextComponent title )
	{
		super( container, playerInventory, title );

		rows = container.getRows();
		ySize = 114 + rows * 18;
		offsetY = 17 + rows * 18;
	}

	@Override
	public void render( int mouseX, int mouseY, float partialTicks )
	{
		renderBackground();
		super.render( mouseX, mouseY, partialTicks );

		if( mouseX >= guiLeft + 115 && mouseX < guiLeft + 169 && mouseY >= guiTop + 7 && mouseY < guiTop + 13 )
		{
			final ITextComponent toolTip = new TranslationTextComponent( ModInfo.CONTAINER_CAPACITY, container.getVolume() );
			this.renderTooltip( toolTip.getFormattedText(), mouseX, mouseY );
		}
		else
			renderHoveredToolTip( mouseX, mouseY );
	}

	@Override
	protected void drawGuiContainerForegroundLayer( int par1, int par2 )
	{
		font.drawString( title.getFormattedText(), 8, 6, 0x404040 );
		font.drawString( playerInventory.getDisplayName().getFormattedText(), 8, offsetY + 2, 0x404040 );
	}

	@Override
	protected void drawGuiContainerBackgroundLayer( float partialTicks, int mouseX, int mouseY )
	{
		minecraft.getTextureManager().bindTexture( Resources.CONTAINER_CRATE );
		GlStateManager.color4f( 1.0F, 1.0F, 1.0F, 1.0F );

		blit( guiLeft, guiTop, 0, 0, xSize, offsetY );
		blit( guiLeft, guiTop + offsetY, 0, 126, xSize, 96 );

		final int volume = container.getVolume();
		if( volume > 0.0 )
			blit( guiLeft + 115, guiTop + 7, 176, 0, (int)( 54 * ( volume / 100.0 ) ), 6 );
	}
}
<file_sep>/src/main/java/io/github/tehstoneman/betterstorage/client/renderer/tileentity/model/LargeChestModel.java
package io.github.tehstoneman.betterstorage.client.renderer.tileentity.model;

import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.model.ModelRenderer;

public class LargeChestModel extends ChestModel
{
	public LargeChestModel()
	{
		super( RenderType::getEntityCutout );

		chestBody = new ModelRenderer( 128, 64, 0, 19 );
		chestBody.addBox( 1.0F, 0.0F, 1.0F, 30.0F, 10.0F, 14.0F, 0.0F );

		chestLid = new ModelRenderer( 128, 64, 0, 0 );
		chestLid.addBox( 1.0F, 0.0F, 0.0F, 30.0F, 5.0F, 14.0F, 0.0F );
		chestLid.rotationPointY = 9.0F;
		chestLid.rotationPointZ = 1.0F;

		chestLatch = new ModelRenderer( 128, 64, 0, 0 );
		chestLatch.addBox( 15.0F, -1.0F, 15.0F, 2.0F, 4.0F, 1.0F, 0.0F );
		chestLatch.rotationPointY = 8.0F;
	}
}
<file_sep>/src/main/java/io/github/tehstoneman/betterstorage/common/item/locking/KeyColor.java
package io.github.tehstoneman.betterstorage.common.item.locking;

import java.awt.Color;

import io.github.tehstoneman.betterstorage.api.lock.KeyLockItem;
import net.minecraft.client.renderer.color.IItemColor;
import net.minecraft.item.ItemStack;

public class KeyColor implements IItemColor
{
	@Override
	public int getColor( ItemStack stack, int tintIndex )
	{
		switch( tintIndex )
		{
		case 0:
			return KeyLockItem.getKeyColor2( stack );
		case 1:
			return KeyLockItem.getKeyColor1( stack );
		case 2:
			return Color.WHITE.getRGB();
		}
		return Color.WHITE.getRGB();
	}
}
<file_sep>/src/main/java/io/github/tehstoneman/betterstorage/addon/jei/BetterStoragePlugin.java
package io.github.tehstoneman.betterstorage.addon.jei;

import io.github.tehstoneman.betterstorage.ModInfo;
import io.github.tehstoneman.betterstorage.addon.jei.crafting.CopyKeyRecipeExtension;
import mezz.jei.api.IModPlugin;
import mezz.jei.api.JeiPlugin;
import mezz.jei.api.recipe.category.extensions.IExtendableRecipeCategory;
import mezz.jei.api.recipe.category.extensions.vanilla.crafting.ICraftingCategoryExtension;
import mezz.jei.api.registration.IRecipeRegistration;
import mezz.jei.api.registration.IVanillaCategoryExtensionRegistration;
import mezz.jei.plugins.vanilla.crafting.CraftingCategoryExtension;
import net.minecraft.item.crafting.ICraftingRecipe;
import net.minecraft.util.ResourceLocation;

@JeiPlugin
public class BetterStoragePlugin implements IModPlugin
{
	@Override
	public ResourceLocation getPluginUid()
	{
		return new ResourceLocation( ModInfo.MOD_ID, "jei_plugin" );
	}

	/*@Override
	public void registerVanillaCategoryExtensions( IVanillaCategoryExtensionRegistration registration )
	{
		final IExtendableRecipeCategory< ICraftingRecipe, ICraftingCategoryExtension > craftingCategory = registration.getCraftingCategory();
		craftingCategory.addCategoryExtension( ICraftingRecipe.class, CopyKeyRecipeExtension::new );
	}*/

	@Override
	public void registerRecipes( IRecipeRegistration registration )
	{}
}
<file_sep>/src/main/java/io/github/tehstoneman/betterstorage/common/world/storage/HexKeyConfig.java
package io.github.tehstoneman.betterstorage.common.world.storage;

import javax.annotation.Nonnull;

import io.github.tehstoneman.betterstorage.api.IHexKeyConfig;
import net.minecraft.item.BlockItem;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT;
import net.minecraftforge.common.util.INBTSerializable;
import net.minecraftforge.items.ItemStackHandler;

/**
 * Holds the configuration for adjustable storage containers
 *
 * @author TehStoneMan
 */
public class HexKeyConfig extends ItemStackHandler implements IHexKeyConfig, INBTSerializable< CompoundNBT >
{
	public static int SLOT_APPEARANCE = 0;

	public HexKeyConfig()
	{
		super( 1 );
	}

	@Override
	public int getSlotLimit( int slot )
	{
		return 1;
	}

	@Override
	public boolean isEmpty()
	{
		return getStackInSlot( SLOT_APPEARANCE ).isEmpty();
	}

    @Override
    public boolean isItemValid(int slot, @Nonnull ItemStack stack)
    {
        return stack.isEmpty() || (slot == SLOT_APPEARANCE && stack.getItem() instanceof BlockItem );
    }
}
<file_sep>/src/main/java/io/github/tehstoneman/betterstorage/common/tileentity/TileEntityCrate.java
package io.github.tehstoneman.betterstorage.common.tileentity;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.UUID;

import javax.annotation.Nullable;

import org.antlr.v4.runtime.misc.NotNull;

import com.google.common.collect.Iterables;

import io.github.tehstoneman.betterstorage.BetterStorage;
import io.github.tehstoneman.betterstorage.ModInfo;
import io.github.tehstoneman.betterstorage.common.inventory.ContainerCrate;
import io.github.tehstoneman.betterstorage.common.inventory.CrateStackHandler;
import io.github.tehstoneman.betterstorage.common.inventory.Region;
import io.github.tehstoneman.betterstorage.common.world.CrateStackCollection;
import io.github.tehstoneman.betterstorage.config.BetterStorageConfig;
import io.github.tehstoneman.betterstorage.network.UpdateCrateMessage;
import io.github.tehstoneman.betterstorage.util.BlockUtils;
import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.entity.player.ServerPlayerEntity;
import net.minecraft.inventory.container.Container;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.network.NetworkManager;
import net.minecraft.network.play.server.SUpdateTileEntityPacket;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityType;
import net.minecraft.util.Direction;
import net.minecraft.util.NonNullList;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TranslationTextComponent;
import net.minecraft.world.IBlockReader;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fml.network.PacketDistributor;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;

public class TileEntityCrate extends TileEntityContainer
{
	private static final int	maxCratePileSize	= 125;

	public static final int		slotsPerCrate		= 18;
	public static final int		MAX_CRATES			= 125;
	public static final int		MAX_PER_SIDE		= 5;

	// private BlockPos mainPos;
	private UUID				pileID;
	protected ITextComponent	customName;

	private int					numCrates			= 1;
	private int					capacity			= slotsPerCrate;

	private String				customTitle;

	public TileEntityCrate( TileEntityType< ? > tileEntityTypeIn )
	{
		super( tileEntityTypeIn );
	}

	public TileEntityCrate()
	{
		this( BetterStorageTileEntityTypes.CRATE.get() );
	}

	@Override
	public <T> LazyOptional< T > getCapability( Capability< T > capability, Direction facing )
	{
		if( capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY )
		{
			if( facing != null && !BetterStorageConfig.COMMON.crateAllowAutomation.get() )
				return LazyOptional.empty();
			final LazyOptional< IItemHandler > crateHandler = LazyOptional.of( () -> getCrateStackHandler() );
			return CapabilityItemHandler.ITEM_HANDLER_CAPABILITY.orEmpty( capability, crateHandler );
		}
		else
			return super.getCapability( capability, facing );
	}

	public boolean hasID()
	{
		return pileID != null;
	}

	public CrateStackHandler getCrateStackHandler()
	{
		if( world.isRemote )
			return new CrateStackHandler( getCapacity() );

		final CrateStackCollection collection = CrateStackCollection.getCollection( world );
		final CrateStackHandler handler = collection.getOrCreateCratePile( getPileID() );
		handler.sendUpdatesTo( this );

		return handler;
	}

	public UUID getPileID()
	{
		if( pileID == null )
		{
			final CrateStackCollection collection = CrateStackCollection.getCollection( world );
			final CrateStackHandler handler = collection.createCratePile();
			setPileID( handler.getPileID() );
		}
		return pileID;
	}

	public void setPileID( @NotNull UUID pileID )
	{
		this.pileID = pileID;
		markDirty();
	}

	public void updateConnections()
	{
		if( !getWorld().isRemote )
		{
			final CrateStackHandler handler = getCrateStackHandler();
			pileID = handler.getPileID();
			numCrates = handler.getNumCrates();
			checkPileConnections( pileID );
			markDirty();
			notifyRegionUpdate( handler.getRegion(), pileID );
		}
	}

	private void checkPileConnections( UUID pileID )
	{
		final CrateStackHandler handler = getCrateStackHandler();
		final CrateStackCollection collection = CrateStackCollection.getCollection( world );

		// Remove empty pile
		if( handler.getNumCrates() <= 0 )
		{
			collection.removeCratePile( pileID );
			final BlockState state = getBlockState();
			getWorld().notifyBlockUpdate( pos, state, state, 3 );
			return;
		}

		// If there's more than one crate set, they need to split.
		final List< HashSet< TileEntityCrate > > crateSets = getCrateSets( pos, pileID );
		if( crateSets.size() > 1 )
			// The first crate set will keep the original pile data.
			// All other sets will get new pile data objects.
			for( final HashSet< TileEntityCrate > set : Iterables.skip( crateSets, 1 ) )
			{
				// final HashSet< TileEntityCrate > set = crateSets.get( i );
				final CrateStackHandler newHandler = collection.createCratePile();
				// int numCrates = set.size();

				// Add the base crates from the set.
				for( final TileEntityCrate crate : set )
				{
					final NonNullList< ItemStack > overflow = handler.removeCrate( crate );
					newHandler.addCrate( crate );
					if( !overflow.isEmpty() )
						for( final ItemStack stack : overflow )
							if( !stack.isEmpty() )
								newHandler.addItems( stack );
				}
				notifyRegionUpdate( newHandler.getRegion(), newHandler.getPileID() );
			}
		// Trim the original map to the size it actually is.
		// This is needed because the crates may not be removed in
		// order, from outside to inside.
		handler.trimRegion( getWorld() );
		notifyRegionUpdate( handler.getRegion(), getPileID() );
	}

	private List< HashSet< TileEntityCrate > > getCrateSets( BlockPos pos, UUID pileID )
	{
		final List< HashSet< TileEntityCrate > > crateSets = new ArrayList<>();
		int checkedCrates = 0;

		for( final Direction dir : Direction.values() )
		{
			// Continue if this neighbor block is not part of the crate pile.
			final TileEntityCrate tileCrate = getCrateAt( world, pos.offset( dir ) );
			if( tileCrate != null )
				if( !isInSet( tileCrate, crateSets ) )
				{
					// Create a new set of crates and fill it with all connecting crates.
					final HashSet< TileEntityCrate > set = new HashSet<>();
					set.add( tileCrate );
					for( final Direction ndir : Direction.values() )
						checkConnections( tileCrate.getPos().offset( ndir ), pileID, set );
					crateSets.add( set );
					checkedCrates += set.size();
				}
		}

		return crateSets;
	}

	private boolean isInSet( TileEntityCrate tileCrate, List< HashSet< TileEntityCrate > > crateSets )
	{
		for( final HashSet< TileEntityCrate > set : crateSets )
			if( set.contains( tileCrate ) )
				return true;
		return false;
	}

	private void checkConnections( BlockPos pos, UUID pileID, HashSet< TileEntityCrate > set )
	{
		final TileEntityCrate tileCrate = getCrateAt( world, pos );
		if( tileCrate == null || tileCrate == this || !pileID.equals( tileCrate.getPileID() ) || set.contains( tileCrate ) )
			return;
		set.add( tileCrate );
		for( final Direction ndir : Direction.values() )
			checkConnections( pos.offset( ndir ), pileID, set );
	}

	public void notifyRegionUpdate( Region region, UUID pileID )
	{
		if( region == null || region.isEmpty() )
			return;
		for( final BlockPos blockPos : BlockUtils.getAllInBox( region ) )
		{
			final TileEntity te = getWorld().getTileEntity( blockPos );
			if( te instanceof TileEntityCrate && ( (TileEntityCrate)te ).pileID.equals( pileID ) )
			{
				te.markDirty();
				final BlockState state = getWorld().getBlockState( blockPos );
				getWorld().notifyBlockUpdate( blockPos, state, state, 3 );
			}
		}
	}

	public void setCustomTitle( String displayName )
	{
		customTitle = displayName;
	}

	public int getNumCrates()
	{
		if( getWorld().isRemote )
			return numCrates;
		return getCrateStackHandler().getNumCrates();
	}

	public void setNumCrates( int numCrates )
	{
		this.numCrates = numCrates;
	}

	public int getCapacity()
	{
		if( getWorld().isRemote )
			return capacity;
		return getCrateStackHandler().getCapacity();
	}

	public void setCapacity( int capacity )
	{
		this.capacity = capacity;
	}

	// Comparator related

	@Override
	public void markDirty()
	{
		super.markDirty();
		if( !world.isRemote )
			CrateStackCollection.getCollection( getWorld() ).markDirty();
	}

	// TileEntity synchronization

	@Override
	public SUpdateTileEntityPacket getUpdatePacket()
	{
		final CompoundNBT nbt = new CompoundNBT();

		if( pileID != null )
			nbt.putUniqueId( "PileID", pileID );
		nbt.putInt( "NumCrates", getNumCrates() );
		nbt.putInt( "Capacity", getCapacity() );
		return new SUpdateTileEntityPacket( getPos(), 1, nbt );
	}

	@Override
	public void onDataPacket( NetworkManager network, SUpdateTileEntityPacket packet )
	{
		final CompoundNBT nbt = packet.getNbtCompound();

		if( nbt.hasUniqueId( "PileID" ) )
			pileID = nbt.getUniqueId( "PileID" );
		numCrates = nbt.getInt( "NumCrates" );
		capacity = nbt.getInt( "Capacity" );
	}

	@Override
	public CompoundNBT getUpdateTag()
	{
		final CompoundNBT nbt = super.getUpdateTag();

		if( pileID != null )
			nbt.putUniqueId( "PileID", pileID );
		nbt.putInt( "NumCrates", getNumCrates() );
		nbt.putInt( "Capacity", getCapacity() );

		return nbt;
	}

	@Override
	public void handleUpdateTag( CompoundNBT nbt )
	{
		super.handleUpdateTag( nbt );

		if( nbt.hasUniqueId( "PileID" ) )
			pileID = nbt.getUniqueId( "PileID" );
		numCrates = nbt.getInt( "NumCrates" );
		capacity = nbt.getInt( "Capacity" );
	}

	// Reading from / writing to NBT

	@Override
	public CompoundNBT write( CompoundNBT nbt )
	{
		super.write( nbt );

		if( pileID != null )
			nbt.putUniqueId( "PileID", pileID );

		return nbt;
	}

	@Override
	public void read( CompoundNBT nbt )
	{
		super.read( nbt );

		if( nbt.hasUniqueId( "PileID" ) )
			pileID = nbt.getUniqueId( "PileID" );
	}

	@Override
	public Container createMenu( int windowID, PlayerInventory playerInventory, PlayerEntity player )
	{
		BetterStorage.NETWORK.send( PacketDistributor.PLAYER.with( () -> (ServerPlayerEntity)player ),
				new UpdateCrateMessage( pos, getNumCrates(), getCapacity() ) );
		return new ContainerCrate( windowID, playerInventory, world, pos );
	}

	@Override
	public ITextComponent getName()
	{
		return new TranslationTextComponent( ModInfo.CONTAINER_CRATE_NAME );
	}

	@Nullable
	static public TileEntityCrate getCrateAt( IBlockReader world, BlockPos pos )
	{
		final TileEntity tileEntity = world.getTileEntity( pos );
		if( tileEntity instanceof TileEntityCrate )
			return (TileEntityCrate)tileEntity;
		return null;
	}

	public boolean tryAddCrate( @Nullable TileEntityCrate crate )
	{
		// Rule 1 - New crate must exist
		if( crate == null )
			return false;

		// Rule 2 - PileID's must be different
		if( crate.getPileID().equals( pileID ) )
			return true;

		// Rule 3 - New region must remain within size limits
		final CrateStackHandler handler = getCrateStackHandler();
		final Region region = handler.getRegion().clone();
		if( crate.getNumCrates() > 1 )
			region.expandToContain( crate.getCrateStackHandler().getRegion() );
		else
			region.expandToContain( crate );
		if( region.width() > MAX_PER_SIDE || region.height() > MAX_PER_SIDE || region.depth() > MAX_PER_SIDE )
			return false;

		// All rules passed - Add crate to pile
		for( final BlockPos blockPos : BlockUtils.getAllInBox( region.posMin, region.posMax ) )
		{
			final TileEntityCrate newCrate = getCrateAt( world, blockPos );
			if( newCrate != null && !newCrate.getPileID().equals( pileID ) )
				handler.addCrate( newCrate );
		}
		notifyRegionUpdate( region, pileID );
		// checkPileConnections( pileID );
		return true;
	}

	public NonNullList< ItemStack > removeCrate()
	{
		final NonNullList< ItemStack > overflow = getCrateStackHandler().removeCrate( this );
		checkPileConnections( pileID );
		return overflow;
	}
}
<file_sep>/src/main/java/io/github/tehstoneman/betterstorage/init/InitModCapabilities.java
package io.github.tehstoneman.betterstorage.init;

import io.github.tehstoneman.betterstorage.ModInfo;
import io.github.tehstoneman.betterstorage.common.capabilities.CapabilityConfig;
import io.github.tehstoneman.betterstorage.common.capabilities.CapabilityCrate;
import net.minecraft.world.World;
import net.minecraftforge.event.AttachCapabilitiesEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod.EventBusSubscriber;
import net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;

@EventBusSubscriber( modid = ModInfo.MOD_ID, bus = Bus.MOD )
public class InitModCapabilities
{
	@SubscribeEvent
	public static void registerCapabilities( FMLCommonSetupEvent event )
	{
		CapabilityConfig.register();
		CapabilityCrate.register();
	}

	@SubscribeEvent
	public static void attachWorldCapabilities( final AttachCapabilitiesEvent< World > event )
	{
		event.addCapability( CapabilityCrate.CAPABILITY_RESOURCE, new CapabilityCrate.Provider( CapabilityCrate.CRATE_PILE_CAPABILITY ) );
	}
}
<file_sep>/src/main/java/io/github/tehstoneman/betterstorage/common/item/HexKeyItem.java
package io.github.tehstoneman.betterstorage.common.item;

import io.github.tehstoneman.betterstorage.BetterStorage;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.item.Item;
import net.minecraft.item.ItemUseContext;
import net.minecraft.util.ActionResultType;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;

public class HexKeyItem extends ItemBetterStorage
{
	public HexKeyItem()
	{
		super( new Item.Properties().maxStackSize( 1 ) );
	}
}
<file_sep>/src/main/java/io/github/tehstoneman/betterstorage/client/gui/ConfigContainerGui.java
package io.github.tehstoneman.betterstorage.client.gui;

import com.mojang.blaze3d.systems.RenderSystem;

import io.github.tehstoneman.betterstorage.client.renderer.Resources;
import io.github.tehstoneman.betterstorage.common.inventory.ConfigContainer;
import io.github.tehstoneman.betterstorage.common.inventory.ConfigContainer.ConfigSlot;
import net.minecraft.client.gui.screen.inventory.ContainerScreen;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.util.text.ITextComponent;

public class ConfigContainerGui extends ContainerScreen< ConfigContainer >
{
	public ConfigContainerGui( ConfigContainer screenContainer, PlayerInventory inv, ITextComponent titleIn )
	{
		super( screenContainer, inv, titleIn );
		ySize = 132;
	}

	@Override
	public void render( int mouseX, int mouseY, float partialTicks )
	{
		renderBackground();
		super.render( mouseX, mouseY, partialTicks );
		renderHoveredToolTip( mouseX, mouseY );
	}

	@Override
	protected void drawGuiContainerForegroundLayer( int par1, int par2 )
	{
		font.drawString( title.getFormattedText(), 8, 6, 0x404040 );
		font.drawString( playerInventory.getDisplayName().getFormattedText(), 8 + ( xSize - 176 ) / 2, ySize - 94, 0x404040 );
	}

	@Override
	protected void drawGuiContainerBackgroundLayer( float partialTicks, int mouseX, int mouseY )
	{
		minecraft.getTextureManager().bindTexture( Resources.CONTAINER_CONFIG );
		RenderSystem.color4f( 1.0F, 1.0F, 1.0F, 1.0F );

		blit( guiLeft, guiTop, 0, 0, xSize, ySize );
		for( int i = 0; i < container.indexHotbar; i++ )
		{
			final ConfigSlot slot = (ConfigSlot)container.getSlot( i );
			if( !slot.getHasStack() )
				blit( guiLeft + slot.xPos, guiTop + slot.yPos, slot.getIconX() + 1, slot.getIconY() + 1, 16, 16 );
		}
		// blit( guiLeft, guiTop + 35, 0, 125, xSize, 97 );
	}
}
<file_sep>/src/main/java/io/github/tehstoneman/betterstorage/client/gui/GuiKeyring.java
package io.github.tehstoneman.betterstorage.client.gui;

import com.mojang.blaze3d.platform.GlStateManager;

import io.github.tehstoneman.betterstorage.client.renderer.Resources;
import io.github.tehstoneman.betterstorage.common.inventory.ContainerKeyring;
import net.minecraft.client.gui.screen.inventory.ContainerScreen;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.util.text.ITextComponent;

public class GuiKeyring extends ContainerScreen< ContainerKeyring >
{
	public GuiKeyring( ContainerKeyring container, PlayerInventory playerInventory, ITextComponent title )
	{
		super( container, playerInventory, title );
		ySize = 131;
	}

	@Override
	public void render( int mouseX, int mouseY, float partialTicks )
	{
		renderBackground();
		super.render( mouseX, mouseY, partialTicks );
		renderHoveredToolTip( mouseX, mouseY );
	}

	@Override
	protected void drawGuiContainerForegroundLayer( int mouseX, int mouseY )
	{
		font.drawString( title.getFormattedText(), 8, 6, 0x404040 );
		font.drawString( playerInventory.getDisplayName().getFormattedText(), 8 + ( xSize - 176 ) / 2, ySize - 94, 0x404040 );
	}

	@Override
	protected void drawGuiContainerBackgroundLayer( float partialTicks, int x, int y )
	{
		minecraft.getTextureManager().bindTexture( Resources.CONTAINER_GENERIC );
		GlStateManager.color4f( 1.0F, 1.0F, 1.0F, 1.0F );

		blit( guiLeft, guiTop, 0, 0, xSize, 35 );
		blit( guiLeft, guiTop + 35, 0, 125, xSize, 97 );
	}
}<file_sep>/src/main/java/io/github/tehstoneman/betterstorage/client/renderer/tileentity/model/ModelTankFluid.java
package io.github.tehstoneman.betterstorage.client.renderer.tileentity.model;

import com.mojang.blaze3d.matrix.MatrixStack;
import com.mojang.blaze3d.vertex.IVertexBuilder;

import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.model.Model;
import net.minecraft.client.renderer.model.ModelRenderer;

public class ModelTankFluid extends Model
{
	protected ModelRenderer fluid;

	public ModelTankFluid()
	{
		super( RenderType::getEntityCutout );
	}

	public void setLevel( float amount, boolean fillTop, boolean fillBottom, boolean isLigterThanAir )
	{
		final float height = ( ( fillBottom ? 2.0f : 0.0f ) + 12.0F + ( fillTop ? 2.0f : 0.0f ) ) * amount;
		final float base = fillBottom ? 0.0f : 2.0f;
		fluid = new ModelRenderer( 64, 32, 0, 0 ).addBox( 1.1F, isLigterThanAir ? 16.0f - ( base + height ) : base, 1.1F, 13.8F, height, 13.8F );
	}

	@Override
	public void render( MatrixStack matrixStack, IVertexBuilder buffer, int packedLight, int packedOverlay, float red, float green, float blue,
			float alpha )
	{
		fluid.render( matrixStack, buffer, packedLight, packedOverlay, red, green, blue, alpha );
	}
}
<file_sep>/src/main/java/io/github/tehstoneman/betterstorage/common/item/cardboard/CardboardColor.java
package io.github.tehstoneman.betterstorage.common.item.cardboard;

import io.github.tehstoneman.betterstorage.common.tileentity.TileEntityCardboardBox;
import net.minecraft.block.BlockState;
import net.minecraft.client.renderer.color.IBlockColor;
import net.minecraft.client.renderer.color.IItemColor;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.ILightReader;

public class CardboardColor implements IItemColor, IBlockColor
{
	@Override
	public int getColor( ItemStack stack, int tintIndex )
	{
		if( stack.hasTag() )
		{
			final CompoundNBT tag = stack.getTag();
			if( tag.contains( "Color" ) )
				return tag.getInt( "Color" );

			// For backwards compatibility
			if( tag.contains( "color" ) )
				return tag.getInt( "color" );
		}
		return 0xA08060;
	}

	@Override
	public int getColor( BlockState state, ILightReader reader, BlockPos pos, int tint )
	{
		if( reader != null )
		{
			final TileEntity tileEntity = reader.getTileEntity( pos );
			if( tileEntity instanceof TileEntityCardboardBox )
				return ( (TileEntityCardboardBox)tileEntity ).getColor();
		}
		return 0xA08060;
	}
}
<file_sep>/src/main/java/io/github/tehstoneman/betterstorage/client/renderer/tileentity/ObjRenderer.java
package io.github.tehstoneman.betterstorage.client.renderer.tileentity;

import com.mojang.blaze3d.matrix.MatrixStack;
import com.mojang.blaze3d.vertex.IVertexBuilder;

import io.github.tehstoneman.betterstorage.BetterStorage;
import io.github.tehstoneman.betterstorage.common.block.BetterStorageBlocks;
import io.github.tehstoneman.betterstorage.common.block.BlockConnectableContainer;
import io.github.tehstoneman.betterstorage.common.block.BlockReinforcedChest;
import net.minecraft.block.BlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BlockRendererDispatcher;
import net.minecraft.client.renderer.IRenderTypeBuffer;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.Vector3f;
import net.minecraft.client.renderer.model.IBakedModel;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraftforge.client.model.data.EmptyModelData;

public class ObjRenderer
{
	public static void render( TileEntity tileEntity, float partialTicks, MatrixStack matrixStack, IRenderTypeBuffer renderBuffer, int combinedLight,
			int combinedOverlay )
	{
		matrixStack.push();

		final BlockState tileState = tileEntity.getBlockState();
		final BlockState state = BetterStorageBlocks.REINFORCED_CHEST.get().getDefaultState().with( BlockConnectableContainer.TYPE,
				tileState.get( BlockConnectableContainer.TYPE ) );
		final BlockRendererDispatcher dispatcher = Minecraft.getInstance().getBlockRendererDispatcher();
		final IBakedModel model = dispatcher.getModelForState( state );

		// BetterStorage.LOGGER.info( "ObjRenderer ==== {} ====", model );
		final float f = tileState.get( BlockReinforcedChest.FACING ).getHorizontalAngle();
		matrixStack.translate( 0.5D, 0.5D, 0.5D );
		matrixStack.rotate( Vector3f.YP.rotationDegrees( -f ) );
		matrixStack.translate( -0.5D, -0.5D, -0.5D );

		// You can use one of the various BlockRenderer render methods depending on how much control you want to retain over the model's appearance
		// To render the model as if it were a block placed in the world (lighting, shading, etc) you can use
		// the renderModel() with World (ILightRenderer) as the first argument.
		final World world = tileEntity.getWorld();
		if( world == null )
			return;
		final BlockPos blockPos = tileEntity.getPos();

		// To render the model with more control over lighting, colour, etc, use the renderModel() shown below.

		final MatrixStack.Entry currentMatrix = matrixStack.getLast();

		final float red = 1.0f;
		final float green = 1.0f;
		final float blue = 1.0f;

		final IVertexBuilder vertexBuffer = renderBuffer.getBuffer( RenderType.getSolid() );
		dispatcher.getBlockModelRenderer().renderModel( currentMatrix, vertexBuffer, state, model, red, green, blue, combinedLight, combinedOverlay,
				EmptyModelData.INSTANCE );
		dispatcher.getBlockModelRenderer().renderModel( world, model, state, blockPos, matrixStack, vertexBuffer, true, BetterStorage.RANDOM, 42,
				combinedOverlay, EmptyModelData.INSTANCE );

		matrixStack.pop();
	}
}
<file_sep>/src/main/java/io/github/tehstoneman/betterstorage/common/item/locking/ItemKeyring.java
package io.github.tehstoneman.betterstorage.common.item.locking;

import java.util.List;

import javax.annotation.Nullable;

import io.github.tehstoneman.betterstorage.BetterStorage;
import io.github.tehstoneman.betterstorage.ModInfo;
import io.github.tehstoneman.betterstorage.api.lock.IKey;
import io.github.tehstoneman.betterstorage.common.inventory.ContainerKeyring;
import io.github.tehstoneman.betterstorage.common.inventory.KeyringCapabilityProvider;
import io.github.tehstoneman.betterstorage.common.item.BetterStorageItems;
import io.github.tehstoneman.betterstorage.common.item.ItemBetterStorage;
import net.minecraft.client.util.ITooltipFlag;
import net.minecraft.enchantment.Enchantment;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.entity.player.ServerPlayerEntity;
import net.minecraft.inventory.container.Container;
import net.minecraft.inventory.container.INamedContainerProvider;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.util.ActionResult;
import net.minecraft.util.ActionResultType;
import net.minecraft.util.Hand;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TranslationTextComponent;
import net.minecraft.world.World;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.fml.network.NetworkHooks;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;

public class ItemKeyring extends ItemBetterStorage implements IKey, INamedContainerProvider
{
	public ItemKeyring()
	{
		super( new Item.Properties() );
		addPropertyOverride( new ResourceLocation( "full" ), ( itemStack, world, entityPlayer ) ->
		{
			return getFilledCapacity( itemStack );
		} );
	}

	@Override
	public ActionResult< ItemStack > onItemRightClick( World world, PlayerEntity player, Hand hand )
	{
		final ItemStack stack = player.getHeldItem( hand );

		if( !player.isCrouching() )
			return new ActionResult( ActionResultType.PASS, stack );

		if( !world.isRemote )
			NetworkHooks.openGui( (ServerPlayerEntity)player, BetterStorageItems.KEYRING.get(),
					buf -> buf.writeItemStack( player.getHeldItem( hand ) ).writeInt( player.inventory.currentItem ) );
		return new ActionResult( ActionResultType.SUCCESS, stack );
	}

	/*
	 * ====
	 * IKey
	 * ====
	 */

	@Override
	public boolean unlock( ItemStack keyring, ItemStack lock, boolean useAbility )
	{
		// Loop through all the keys in the keyring,
		// returns if any of the keys fit in the lock.

		final IItemHandler inventory = keyring.getCapability( CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, null ).orElse( null );
		if( inventory != null )
			for( int i = 0; i < inventory.getSlots(); i++ )
			{
				final ItemStack key = inventory.getStackInSlot( i );
				if( !key.isEmpty() )
				{
					final IKey keyType = (IKey)key.getItem();
					if( keyType.unlock( key, lock, false ) )
						return true;
				}
			}

		return false;
	}

	@Override
	public boolean canApplyEnchantment( ItemStack key, Enchantment enchantment )
	{
		return false;
	}

	@Override
	public ICapabilityProvider initCapabilities( ItemStack stack, @Nullable CompoundNBT nbt )
	{
		return new KeyringCapabilityProvider( stack );
	}

	@Override
	public Container createMenu( int windowID, PlayerInventory playerInventory, PlayerEntity player )
	{
		final ItemStack itemStack = player.getHeldItemMainhand();
		final int index = player.inventory.currentItem;
		return new ContainerKeyring( windowID, playerInventory, itemStack, index );
	}

	@Override
	public ITextComponent getDisplayName()
	{
		return new TranslationTextComponent( ModInfo.CONTAINER_KEYRING_NAME );
	}

	public float getFilledCapacity( ItemStack itemStack )
	{
		if( itemStack.hasTag() )
		{
			final CompoundNBT tag = itemStack.getTag();
			if( tag.contains( "Occupied" ) )
			{
				final int occupied = tag.getInt( "Occupied" );
				return occupied / 9.0f;
			}
		}
		return 0.0F;
	}

	@Override
	public void addInformation( ItemStack stack, @Nullable World worldIn, List< ITextComponent > tooltip, ITooltipFlag flagIn )
	{
		super.addInformation( stack, worldIn, tooltip, flagIn );
		if( flagIn.isAdvanced() && stack.hasTag() )
		{
			final CompoundNBT tag = stack.getTag();
			if( tag.contains( "Occupied" ) )
				tooltip.add( new TranslationTextComponent( "Keys : " + tag.getInt( "Occupied" ) ) );
		}
	}
}
<file_sep>/src/main/java/io/github/tehstoneman/betterstorage/common/inventory/BetterStorageContainerTypes.java
package io.github.tehstoneman.betterstorage.common.inventory;

import io.github.tehstoneman.betterstorage.BetterStorage;
import io.github.tehstoneman.betterstorage.ModInfo;
import io.github.tehstoneman.betterstorage.common.item.BetterStorageItems;
import net.minecraft.inventory.container.ContainerType;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.BlockPos;
import net.minecraftforge.common.extensions.IForgeContainerType;
import net.minecraftforge.fml.RegistryObject;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;

public final class BetterStorageContainerTypes
{
	public static final DeferredRegister< ContainerType< ? > >					REGISTERY			= new DeferredRegister<>(
			ForgeRegistries.CONTAINERS, ModInfo.MOD_ID );

	//@formatter:off
	public static RegistryObject< ContainerType< ContainerCardboardBox > >		CARDBOARD_BOX		= REGISTERY.register( "cardboard_box",
			() -> IForgeContainerType.create( ( windowID, inv, data ) ->
										{
											final BlockPos pos = data.readBlockPos();
											return new ContainerCardboardBox( windowID, inv, BetterStorage.PROXY.getClientWorld(), pos );
										} ) );
	public static RegistryObject< ContainerType< ContainerCrate > >				CRATE				= REGISTERY.register( "crate",
			() -> IForgeContainerType.create( ( windowID, inv, data ) ->
										{
											final BlockPos pos = data.readBlockPos();
											return new ContainerCrate( windowID, inv, BetterStorage.PROXY.getClientWorld(), pos );
										} ) );
	public static RegistryObject< ContainerType< ContainerKeyring > >			KEYRING				= REGISTERY.register( "keyring",
			() -> IForgeContainerType.create( ( windowID, inv, data ) ->
										{
											final ItemStack keyring = data.readItemStack();
											final int protectedIndex = data.readInt();
											return new ContainerKeyring( windowID, inv, keyring, protectedIndex );
										} ) );
	public static RegistryObject< ContainerType< ContainerLocker > >			LOCKER				= REGISTERY.register( "locker",
			() -> IForgeContainerType.create( ( windowID, inv, data ) ->
										{
											final BlockPos pos = data.readBlockPos();
											return new ContainerLocker( windowID, inv, BetterStorage.PROXY.getClientWorld(), pos );
										} ) );
	public static RegistryObject< ContainerType< ReinforcedChestContainer > >	REINFORCED_CHEST	= REGISTERY.register( "reinforced_chest",
			() -> IForgeContainerType.create( ( windowID, inv, data ) ->
										{
											final BlockPos pos = data.readBlockPos();
											return new ReinforcedChestContainer( windowID, inv, BetterStorage.PROXY.getClientWorld(), pos );
										} ) );
	public static RegistryObject< ContainerType< ContainerReinforcedLocker > >	REINFORCED_LOCKER	= REGISTERY.register( "reinforced_locker",
			() -> IForgeContainerType.create( ( windowID, inv, data ) ->
										{
											final BlockPos pos = data.readBlockPos();
											return new ContainerReinforcedLocker( windowID, inv, BetterStorage.PROXY.getClientWorld(), pos );
										} ) );
	public static RegistryObject< ContainerType< ConfigContainer > >			CONFIG	= REGISTERY.register( "config_container",
			() -> IForgeContainerType.create( ( windowID, inv, data ) ->
										{
											final BlockPos pos = data.readBlockPos();
											return new ConfigContainer( windowID, inv, BetterStorage.PROXY.getClientWorld(), pos );
										} ) );
	//@formatter:on
}
<file_sep>/src/main/java/io/github/tehstoneman/betterstorage/client/renderer/tileentity/model/ChestModel.java
package io.github.tehstoneman.betterstorage.client.renderer.tileentity.model;

import java.util.function.Function;

import com.mojang.blaze3d.matrix.MatrixStack;
import com.mojang.blaze3d.vertex.IVertexBuilder;

import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.model.Model;
import net.minecraft.client.renderer.model.ModelRenderer;
import net.minecraft.util.ResourceLocation;

public class ChestModel extends Model
{
	protected ModelRenderer	chestBody;
	protected ModelRenderer	chestLid;
	protected ModelRenderer	chestLatch;

	public ChestModel()
	{
		super( RenderType::getEntityCutout );

		chestBody = new ModelRenderer( 64, 64, 0, 19 );
		chestBody.addBox( 1.0F, 0.0F, 1.0F, 14.0F, 10.0F, 14.0F, 0.0F );

		chestLid = new ModelRenderer( 64, 64, 0, 0 );
		chestLid.addBox( 1.0F, 0.0F, 0.0F, 14.0F, 5.0F, 14.0F, 0.0F );
		chestLid.rotationPointY = 9.0F;
		chestLid.rotationPointZ = 1.0F;

		chestLatch = new ModelRenderer( 64, 64, 0, 0 );
		chestLatch.addBox( 7.0F, -1.0F, 15.0F, 2.0F, 4.0F, 1.0F, 0.0F );
		chestLatch.rotationPointY = 8.0F;
	}

	public ChestModel( Function< ResourceLocation, RenderType > renderTypeIn )
	{
		super( renderTypeIn );
	}

	@Override
	public void render( MatrixStack matrixStackIn, IVertexBuilder bufferIn, int packedLightIn, int packedOverlayIn, float red, float green,
			float blue, float alpha )
	{
		chestBody.render( matrixStackIn, bufferIn, packedLightIn, packedOverlayIn, red, green, blue, alpha );
		chestLid.render( matrixStackIn, bufferIn, packedLightIn, packedOverlayIn, red, green, blue, alpha );
		chestLatch.render( matrixStackIn, bufferIn, packedLightIn, packedOverlayIn, red, green, blue, alpha );
	}

	public void rotateLid( float angle )
	{
		final float r = -angle * ( (float)Math.PI / 2F );
		chestLid.rotateAngleX = r;
		chestLatch.rotateAngleX = r;
	}
}
<file_sep>/src/main/java/io/github/tehstoneman/betterstorage/common/world/storage/CrateStorage.java
package io.github.tehstoneman.betterstorage.common.world.storage;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Logger;

import io.github.tehstoneman.betterstorage.ModInfo;
import io.github.tehstoneman.betterstorage.api.ICrateStorage;
import io.github.tehstoneman.betterstorage.common.inventory.CrateStackHandler;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.world.World;

/** Holds all CratePile objects for one world / dimension. */
public class CrateStorage implements ICrateStorage
{
	private final Map< UUID, CrateStackHandler > pileDataMap = new HashMap<>();

	public CrateStorage()
	{}

	/** Get the stack handler for the associated ID */
	@Override
	public CrateStackHandler getCratePile( UUID pileID )
	{
		if( pileID != null && pileDataMap.containsKey( pileID ) )
			return pileDataMap.get( pileID );
		return createCratePile();
	}

	@Override
	public CrateStackHandler getOrCreateCratePile( UUID pileID )
	{
		if( !pileDataMap.containsKey( pileID ) )
		{
			final CrateStackHandler cratePile = new CrateStackHandler( 18 );
			pileDataMap.put( pileID, cratePile );
			// cratePile.setPileID( pileID );
		}
		return pileDataMap.get( pileID );
	}

	@Override
	public CrateStackHandler createCratePile()
	{
		UUID pileID = UUID.randomUUID();
		while( pileDataMap.containsKey( pileID ) )
			pileID = UUID.randomUUID();
		return addCrateToPile( pileID );
	}

	@Override
	public CrateStackHandler addCrateToPile( UUID pileID )
	{
		final CrateStackHandler cratePile = new CrateStackHandler( 18 );
		pileDataMap.put( pileID, cratePile );
		cratePile.setPileID( pileID );
		// markDirty();
		return cratePile;
	}

	@Override
	public void removeCratePile( UUID pileID )
	{
		pileDataMap.remove( pileID );
		// markDirty();
	}

	@Override
	public CompoundNBT serializeNBT()
	{
		Logger.getLogger( ModInfo.MOD_ID ).info( "Saving crate storage" );
		final CompoundNBT compound = new CompoundNBT();
		if( !pileDataMap.isEmpty() )
			for( final Map.Entry< UUID, CrateStackHandler > entry : pileDataMap.entrySet() )
				compound.put( entry.getKey().toString(), entry.getValue().serializeNBT() );
		return compound;
	}

	@Override
	public void deserializeNBT( CompoundNBT compound )
	{
		Logger.getLogger( ModInfo.MOD_ID ).info( "Loading crate storage" );
		if( !compound.isEmpty() )
			for( final String key : compound.keySet() )
			{
				final CrateStackHandler crateStackHandler = new CrateStackHandler( 18 );
				crateStackHandler.deserializeNBT( compound.getCompound( key ) );
				pileDataMap.put( UUID.fromString( key ), crateStackHandler );
			}
	}
}
<file_sep>/src/main/java/io/github/tehstoneman/betterstorage/common/item/BlockItemBetterStorage.java
package io.github.tehstoneman.betterstorage.common.item;

import io.github.tehstoneman.betterstorage.BetterStorage;
import net.minecraft.block.Block;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;

public class BlockItemBetterStorage extends BlockItem
{
	public BlockItemBetterStorage( Block blockIn )
	{
		this( blockIn, new Item.Properties() );
	}

	public BlockItemBetterStorage( Block blockIn, Properties properties )
	{
		super( blockIn, properties.group( BetterStorage.ITEM_GROUP ) );
	}
}
<file_sep>/src/main/java/io/github/tehstoneman/betterstorage/common/block/BlockContainerBetterStorage.java
package io.github.tehstoneman.betterstorage.common.block;

import io.github.tehstoneman.betterstorage.BetterStorage;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;

public abstract class BlockContainerBetterStorage extends Block
{
	protected BlockContainerBetterStorage( Properties properties )
	{
		super( properties );
	}

	/*
	 * ======================
	 * TileEntity / Rendering
	 * ======================
	 */

	@Override
	public boolean hasTileEntity( BlockState state )
	{
		return true;
	}

	/*
	 * ===========
	 * Interaction
	 * ===========
	 */

	@Override
	public boolean eventReceived( BlockState state, World worldIn, BlockPos pos, int id, int param )
	{
		final TileEntity te = worldIn.getTileEntity( pos );
		return te != null ? te.receiveClientEvent( id, param ) : false;
	}
}
<file_sep>/src/main/java/io/github/tehstoneman/betterstorage/common/tileentity/BetterStorageTileEntityTypes.java
package io.github.tehstoneman.betterstorage.common.tileentity;

import io.github.tehstoneman.betterstorage.ModInfo;
import io.github.tehstoneman.betterstorage.common.block.BetterStorageBlocks;
import net.minecraft.tileentity.TileEntityType;
import net.minecraftforge.fml.RegistryObject;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;

public final class BetterStorageTileEntityTypes
{
	public static final DeferredRegister< TileEntityType< ? > >						REGISTERY			= new DeferredRegister<>(
			ForgeRegistries.TILE_ENTITIES, ModInfo.MOD_ID );

	public static RegistryObject< TileEntityType< TileEntityCardboardBox > >		CARDBOARD_BOX		= REGISTERY.register( "cardboard_box",
			() -> TileEntityType.Builder.create( TileEntityCardboardBox::new, BetterStorageBlocks.CARDBOARD_BOX.get() ).build( null ) );
	public static RegistryObject< TileEntityType< TileEntityCrate > >				CRATE				= REGISTERY.register( "crate",
			() -> TileEntityType.Builder.create( TileEntityCrate::new, BetterStorageBlocks.CRATE.get() ).build( null ) );
	public static RegistryObject< TileEntityType< TileEntityLockableDoor > >		LOCKABLE_DOOR		= REGISTERY.register( "lockable_door",
			() -> TileEntityType.Builder.create( TileEntityLockableDoor::new, BetterStorageBlocks.LOCKABLE_DOOR.get() ).build( null ) );
	public static RegistryObject< TileEntityType< TileEntityLocker > >				LOCKER				= REGISTERY.register( "locker",
			() -> TileEntityType.Builder.create( TileEntityLocker::new, BetterStorageBlocks.LOCKER.get() ).build( null ) );
	public static RegistryObject< TileEntityType< TileEntityReinforcedChest > >		REINFORCED_CHEST	= REGISTERY.register( "reinforced_chest",
			() -> TileEntityType.Builder.create( TileEntityReinforcedChest::new, BetterStorageBlocks.REINFORCED_CHEST.get() ).build( null ) );
	public static RegistryObject< TileEntityType< TileEntityReinforcedLocker > >	REINFORCED_LOCKER	= REGISTERY.register( "reinforced_locker",
			() -> TileEntityType.Builder.create( TileEntityReinforcedLocker::new, BetterStorageBlocks.REINFORCED_LOCKER.get() ).build( null ) );
	public static RegistryObject< TileEntityType< TileEntityTank > >				GLASS_TANK			= REGISTERY.register( "glass_tank",
			() -> TileEntityType.Builder.create( TileEntityTank::new, BetterStorageBlocks.GLASS_TANK.get() ).build( null ) );
}
