From 9fe1d85199c4af0eafc6ae392866c8d8c833d120 Mon Sep 17 00:00:00 2001
From: simibubi <31564874+simibubi@users.noreply.github.com>
Date: Fri, 26 Jun 2020 21:38:10 +0200
Subject: [PATCH] Reworked belt processing and interfacing

- Removed the IBeltAttachment system
- Item processing on belts is now a TE behaviour
- Added TE behaviour for allowing belts (and similar) to directly insert items
- Massive refactor to the Belts' inventory
- Fixed fast moving items missing their processing step
- Saws, Basins, Crushing Wheels, Belts and Crafters now interact through the new behaviour rather than hard-coded interactions
- Fixed items (visually) disappearing on saws while queueing up
- Items on belts now back up a little further away from the end of the belt
---
 .../crafter/MechanicalCrafterTileEntity.java  |  47 +-
 .../CrushingWheelControllerTileEntity.java    |  55 ++-
 .../press/BeltPressingCallbacks.java          |  59 +++
 .../press/MechanicalPressBlock.java           | 104 +---
 .../press/MechanicalPressTileEntity.java      |  54 ++-
 .../components/saw/SawRenderer.java           |  31 +-
 .../components/saw/SawTileEntity.java         | 135 +++---
 .../processing/BasinTileEntity.java           |  14 +-
 .../relays/belt/AllBeltAttachments.java       | 202 --------
 .../contraptions/relays/belt/BeltBlock.java   |   3 -
 .../relays/belt/BeltRenderer.java             |   2 +-
 .../relays/belt/BeltTileEntity.java           |  66 +--
 .../relays/belt/transport/BeltInventory.java  | 445 ++++++++----------
 .../belt/transport/BeltMovementHandler.java   |  11 -
 .../BeltTunnelInteractionHandler.java         | 100 ++++
 .../belts/BeltAttachableLogisticalBlock.java  |  70 +--
 .../belts/observer/BeltObserverBlock.java     | 142 +-----
 .../block/extractor/ExtractorTileEntity.java  |   2 +-
 .../logistics/block/funnel/FunnelBlock.java   |  48 +-
 .../tileEntity/SmartTileEntity.java           |   8 +-
 .../tileEntity/TileEntityBehaviour.java       |  10 +-
 ...IBehaviourType.java => BehaviourType.java} |   2 +-
 .../belt/BeltProcessingBehaviour.java         |  59 +++
 .../belt/DirectBeltInputBehaviour.java        |  76 +++
 .../EdgeInteractionBehaviour.java             |   7 +-
 .../filtering/FilteringBehaviour.java         |   7 +-
 .../inventory/AutoExtractingBehaviour.java    |   7 +-
 .../inventory/ExtractingBehaviour.java        |   7 +-
 .../inventory/InsertingBehaviour.java         |   7 +-
 .../InventoryManagementBehaviour.java         |   7 +-
 .../SingleTargetAutoExtractingBehaviour.java  |  13 +-
 .../behaviour/linked/LinkBehaviour.java       |   7 +-
 .../scrollvalue/ScrollValueBehaviour.java     |   7 +-
 .../behaviour/simple/DeferralBehaviour.java   |   7 +-
 34 files changed, 753 insertions(+), 1068 deletions(-)
 create mode 100644 src/main/java/com/simibubi/create/content/contraptions/components/press/BeltPressingCallbacks.java
 delete mode 100644 src/main/java/com/simibubi/create/content/contraptions/relays/belt/AllBeltAttachments.java
 create mode 100644 src/main/java/com/simibubi/create/content/contraptions/relays/belt/transport/BeltTunnelInteractionHandler.java
 rename src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/{IBehaviourType.java => BehaviourType.java} (67%)
 create mode 100644 src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/belt/BeltProcessingBehaviour.java
 create mode 100644 src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/belt/DirectBeltInputBehaviour.java

diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/crafter/MechanicalCrafterTileEntity.java b/src/main/java/com/simibubi/create/content/contraptions/components/crafter/MechanicalCrafterTileEntity.java
index 93d44a928..8bace4b11 100644
--- a/src/main/java/com/simibubi/create/content/contraptions/components/crafter/MechanicalCrafterTileEntity.java
+++ b/src/main/java/com/simibubi/create/content/contraptions/components/crafter/MechanicalCrafterTileEntity.java
@@ -5,6 +5,7 @@ import static com.simibubi.create.content.contraptions.base.HorizontalKineticBlo
 import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map.Entry;
 
 import org.apache.commons.lang3.tuple.Pair;
 
@@ -13,8 +14,8 @@ import com.simibubi.create.AllItems;
 import com.simibubi.create.content.contraptions.base.KineticTileEntity;
 import com.simibubi.create.content.contraptions.components.crafter.ConnectedInputHandler.ConnectedInput;
 import com.simibubi.create.content.contraptions.components.crafter.RecipeGridHandler.GroupedItems;
-import com.simibubi.create.content.contraptions.relays.belt.BeltTileEntity;
 import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
+import com.simibubi.create.foundation.tileEntity.behaviour.belt.DirectBeltInputBehaviour;
 import com.simibubi.create.foundation.tileEntity.behaviour.edgeInteraction.EdgeInteractionBehaviour;
 import com.simibubi.create.foundation.tileEntity.behaviour.inventory.InsertingBehaviour;
 import com.simibubi.create.foundation.tileEntity.behaviour.inventory.InventoryManagementBehaviour.Attachments;
@@ -27,7 +28,6 @@ import net.minecraft.item.ItemStack;
 import net.minecraft.nbt.CompoundNBT;
 import net.minecraft.particles.ItemParticleData;
 import net.minecraft.particles.ParticleTypes;
-import net.minecraft.tileentity.TileEntity;
 import net.minecraft.tileentity.TileEntityType;
 import net.minecraft.util.Direction;
 import net.minecraft.util.math.BlockPos;
@@ -91,9 +91,9 @@ public class MechanicalCrafterTileEntity extends KineticTileEntity {
 		setLazyTickRate(20);
 		phase = Phase.IDLE;
 		groupedItemsBeforeCraft = new GroupedItems();
-		
+
 		// Does not get serialized due to active checking in tick
-		wasPoweredBefore = true; 
+		wasPoweredBefore = true;
 	}
 
 	@Override
@@ -337,13 +337,13 @@ public class MechanicalCrafterTileEntity extends KineticTileEntity {
 	}
 
 	protected boolean isTargetingBelt() {
+		DirectBeltInputBehaviour behaviour = getTargetingBelt();
+		return behaviour != null && behaviour.canInsertFromSide(getTargetFacing());
+	}
+
+	protected DirectBeltInputBehaviour getTargetingBelt() {
 		BlockPos targetPos = pos.offset(getTargetFacing());
-		if (!AllBlocks.BELT.has(world.getBlockState(targetPos)))
-			return false;
-		TileEntity te = world.getTileEntity(targetPos);
-		if (!(te instanceof BeltTileEntity))
-			return false;
-		return ((KineticTileEntity) te).getSpeed() != 0;
+		return TileEntityBehaviour.get(world, targetPos, DirectBeltInputBehaviour.TYPE);
 	}
 
 	public void tryInsert() {
@@ -355,22 +355,21 @@ public class MechanicalCrafterTileEntity extends KineticTileEntity {
 		boolean chagedPhase = phase != Phase.INSERTING;
 		final List<Pair<Integer, Integer>> inserted = new LinkedList<>();
 
-		groupedItems.grid.forEach((pair, stack) -> {
-			if (isTargetingBelt()) {
-				Direction facing = getTargetFacing();
-				BlockPos targetPos = pos.offset(facing);
-				BeltTileEntity te = (BeltTileEntity) world.getTileEntity(targetPos);
-				if (te.tryInsertingFromSide(facing, stack, false))
-					inserted.add(pair);
-				return;
-			}
+		DirectBeltInputBehaviour behaviour = getTargetingBelt();
+		for (Entry<Pair<Integer, Integer>, ItemStack> entry : groupedItems.grid.entrySet()) {
+			Pair<Integer, Integer> pair = entry.getKey();
+			ItemStack stack = entry.getValue();
+			Direction facing = getTargetFacing();
 
-			ItemStack remainder = inserting.insert(stack.copy(), false);
-			if (!remainder.isEmpty())
+			ItemStack remainder = behaviour == null ? inserting.insert(stack.copy(), false)
+				: behaviour.handleInsertion(stack, facing, false);
+			if (!remainder.isEmpty()) {
 				stack.setCount(remainder.getCount());
-			else
-				inserted.add(pair);
-		});
+				continue;
+			}
+			
+			inserted.add(pair);
+		}
 
 		inserted.forEach(groupedItems.grid::remove);
 		if (groupedItems.grid.isEmpty())
diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/crusher/CrushingWheelControllerTileEntity.java b/src/main/java/com/simibubi/create/content/contraptions/components/crusher/CrushingWheelControllerTileEntity.java
index ad8179594..8141fcf36 100644
--- a/src/main/java/com/simibubi/create/content/contraptions/components/crusher/CrushingWheelControllerTileEntity.java
+++ b/src/main/java/com/simibubi/create/content/contraptions/components/crusher/CrushingWheelControllerTileEntity.java
@@ -11,7 +11,9 @@ import com.simibubi.create.content.contraptions.processing.ProcessingInventory;
 import com.simibubi.create.content.contraptions.processing.ProcessingRecipe;
 import com.simibubi.create.foundation.config.AllConfigs;
 import com.simibubi.create.foundation.item.ItemHelper;
-import com.simibubi.create.foundation.tileEntity.SyncedTileEntity;
+import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
+import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
+import com.simibubi.create.foundation.tileEntity.behaviour.belt.DirectBeltInputBehaviour;
 import com.simibubi.create.foundation.utility.VecHelper;
 
 import net.minecraft.entity.Entity;
@@ -24,7 +26,6 @@ import net.minecraft.particles.BlockParticleData;
 import net.minecraft.particles.IParticleData;
 import net.minecraft.particles.ItemParticleData;
 import net.minecraft.particles.ParticleTypes;
-import net.minecraft.tileentity.ITickableTileEntity;
 import net.minecraft.tileentity.TileEntityType;
 import net.minecraft.util.Direction;
 import net.minecraft.util.math.AxisAlignedBB;
@@ -36,7 +37,7 @@ import net.minecraftforge.items.CapabilityItemHandler;
 import net.minecraftforge.items.IItemHandlerModifiable;
 import net.minecraftforge.items.wrapper.RecipeWrapper;
 
-public class CrushingWheelControllerTileEntity extends SyncedTileEntity implements ITickableTileEntity {
+public class CrushingWheelControllerTileEntity extends SmartTileEntity {
 
 	public Entity processingEntity;
 	private UUID entityUUID;
@@ -60,14 +61,20 @@ public class CrushingWheelControllerTileEntity extends SyncedTileEntity implemen
 		wrapper = new RecipeWrapper(inventory);
 	}
 
+	@Override
+	public void addBehaviours(List<TileEntityBehaviour> behaviours) {
+		behaviours.add(new DirectBeltInputBehaviour(this));
+	}
+
 	@Override
 	public void tick() {
+		super.tick();
 		if (isFrozen())
 			return;
 		if (searchForEntity) {
 			searchForEntity = false;
 			List<Entity> search = world.getEntitiesInAABBexcluding(null, new AxisAlignedBB(getPos()),
-					e -> entityUUID.equals(e.getUniqueID()));
+				e -> entityUUID.equals(e.getUniqueID()));
 			if (search.isEmpty())
 				clear();
 			else
@@ -84,9 +91,9 @@ public class CrushingWheelControllerTileEntity extends SyncedTileEntity implemen
 
 		if (!hasEntity()) {
 
-			float processingSpeed = MathHelper.clamp(
-					(speed) / (!inventory.appliedRecipe ? MathHelper.log2(inventory.getStackInSlot(0).getCount()) : 1),
-					.25f, 20);
+			float processingSpeed =
+				MathHelper.clamp((speed) / (!inventory.appliedRecipe ? MathHelper.log2(inventory.getStackInSlot(0)
+					.getCount()) : 1), .25f, 20);
 			inventory.remainingTime -= processingSpeed;
 			spawnParticles(inventory.getStackInSlot(0));
 
@@ -107,7 +114,8 @@ public class CrushingWheelControllerTileEntity extends SyncedTileEntity implemen
 						continue;
 					ItemEntity entityIn = new ItemEntity(world, outPos.x, outPos.y, outPos.z, stack);
 					entityIn.setMotion(Vec3d.ZERO);
-					entityIn.getPersistentData().put("BypassCrushingWheel", NBTUtil.writeBlockPos(pos));
+					entityIn.getPersistentData()
+						.put("BypassCrushingWheel", NBTUtil.writeBlockPos(pos));
 					world.addEntity(entityIn);
 				}
 				inventory.clear();
@@ -118,8 +126,8 @@ public class CrushingWheelControllerTileEntity extends SyncedTileEntity implemen
 			return;
 		}
 
-		if (!processingEntity.isAlive()
-				|| !processingEntity.getBoundingBox().intersects(new AxisAlignedBB(pos).grow(.5f))) {
+		if (!processingEntity.isAlive() || !processingEntity.getBoundingBox()
+			.intersects(new AxisAlignedBB(pos).grow(.5f))) {
 			clear();
 			return;
 		}
@@ -136,7 +144,7 @@ public class CrushingWheelControllerTileEntity extends SyncedTileEntity implemen
 
 		if (!(processingEntity instanceof ItemEntity)) {
 			processingEntity.attackEntityFrom(CrushingWheelTileEntity.damageSource,
-					AllConfigs.SERVER.kinetics.crushingDamage.get());
+				AllConfigs.SERVER.kinetics.crushingDamage.get());
 			if (!processingEntity.isAlive()) {
 				processingEntity.setPosition(outPos.x, outPos.y - .75f, outPos.z);
 			}
@@ -147,7 +155,8 @@ public class CrushingWheelControllerTileEntity extends SyncedTileEntity implemen
 		itemEntity.setPickupDelay(20);
 		if (processingEntity.getY() < pos.getY() + .25f) {
 			inventory.clear();
-			inventory.setStackInSlot(0, itemEntity.getItem().copy());
+			inventory.setStackInSlot(0, itemEntity.getItem()
+				.copy());
 			itemInserted(inventory.getStackInSlot(0));
 			itemEntity.remove();
 			world.notifyBlockUpdate(pos, getBlockState(), getBlockState(), 2 | 16);
@@ -161,15 +170,15 @@ public class CrushingWheelControllerTileEntity extends SyncedTileEntity implemen
 
 		IParticleData particleData = null;
 		if (stack.getItem() instanceof BlockItem)
-			particleData =
-				new BlockParticleData(ParticleTypes.BLOCK, ((BlockItem) stack.getItem()).getBlock().getDefaultState());
+			particleData = new BlockParticleData(ParticleTypes.BLOCK, ((BlockItem) stack.getItem()).getBlock()
+				.getDefaultState());
 		else
 			particleData = new ItemParticleData(ParticleTypes.ITEM, stack);
 
 		Random r = world.rand;
 		for (int i = 0; i < 4; i++)
 			world.addParticle(particleData, pos.getX() + r.nextFloat(), pos.getY() + r.nextFloat(),
-					pos.getZ() + r.nextFloat(), 0, 0, 0);
+				pos.getZ() + r.nextFloat(), 0, 0, 0);
 	}
 
 	private void applyRecipe() {
@@ -177,10 +186,12 @@ public class CrushingWheelControllerTileEntity extends SyncedTileEntity implemen
 
 		List<ItemStack> list = new ArrayList<>();
 		if (recipe.isPresent()) {
-			int rolls = inventory.getStackInSlot(0).getCount();
+			int rolls = inventory.getStackInSlot(0)
+				.getCount();
 			inventory.clear();
 			for (int roll = 0; roll < rolls; roll++) {
-				List<ItemStack> rolledResults = recipe.get().rollResults();
+				List<ItemStack> rolledResults = recipe.get()
+					.rollResults();
 				for (int i = 0; i < rolledResults.size(); i++) {
 					ItemStack stack = rolledResults.get(i);
 					ItemHelper.addToList(stack, list);
@@ -195,10 +206,11 @@ public class CrushingWheelControllerTileEntity extends SyncedTileEntity implemen
 	}
 
 	public Optional<ProcessingRecipe<RecipeWrapper>> findRecipe() {
-		Optional<ProcessingRecipe<RecipeWrapper>> crushingRecipe =
-			world.getRecipeManager().getRecipe(AllRecipeTypes.CRUSHING.getType(), wrapper, world);
+		Optional<ProcessingRecipe<RecipeWrapper>> crushingRecipe = world.getRecipeManager()
+			.getRecipe(AllRecipeTypes.CRUSHING.getType(), wrapper, world);
 		if (!crushingRecipe.isPresent())
-			crushingRecipe = world.getRecipeManager().getRecipe(AllRecipeTypes.MILLING.getType(), wrapper, world);
+			crushingRecipe = world.getRecipeManager()
+				.getRecipe(AllRecipeTypes.MILLING.getType(), wrapper, world);
 		return crushingRecipe;
 	}
 
@@ -231,7 +243,8 @@ public class CrushingWheelControllerTileEntity extends SyncedTileEntity implemen
 
 	private void itemInserted(ItemStack stack) {
 		Optional<ProcessingRecipe<RecipeWrapper>> recipe = findRecipe();
-		inventory.remainingTime = recipe.isPresent() ? recipe.get().getProcessingDuration() : 100;
+		inventory.remainingTime = recipe.isPresent() ? recipe.get()
+			.getProcessingDuration() : 100;
 		inventory.appliedRecipe = false;
 	}
 
diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/press/BeltPressingCallbacks.java b/src/main/java/com/simibubi/create/content/contraptions/components/press/BeltPressingCallbacks.java
new file mode 100644
index 000000000..da9f3f539
--- /dev/null
+++ b/src/main/java/com/simibubi/create/content/contraptions/components/press/BeltPressingCallbacks.java
@@ -0,0 +1,59 @@
+package com.simibubi.create.content.contraptions.components.press;
+
+import static com.simibubi.create.foundation.tileEntity.behaviour.belt.BeltProcessingBehaviour.ProcessingResult.HOLD;
+import static com.simibubi.create.foundation.tileEntity.behaviour.belt.BeltProcessingBehaviour.ProcessingResult.PASS;
+
+import java.util.List;
+import java.util.Optional;
+
+import com.simibubi.create.content.contraptions.components.press.MechanicalPressTileEntity.Mode;
+import com.simibubi.create.content.contraptions.relays.belt.transport.BeltInventory;
+import com.simibubi.create.content.contraptions.relays.belt.transport.TransportedItemStack;
+import com.simibubi.create.foundation.item.ItemHelper;
+import com.simibubi.create.foundation.tileEntity.behaviour.belt.BeltProcessingBehaviour.ProcessingResult;
+
+import net.minecraft.item.ItemStack;
+
+public class BeltPressingCallbacks {
+
+	static ProcessingResult onItemReceived(TransportedItemStack transported, BeltInventory beltInventory,
+		MechanicalPressTileEntity press) {
+		if (press.getSpeed() == 0 || press.running)
+			return PASS;
+		if (!press.getRecipe(transported.stack)
+			.isPresent())
+			return PASS;
+
+		press.start(Mode.BELT);
+		return HOLD;
+	}
+
+	static ProcessingResult whenItemHeld(TransportedItemStack transportedStack, BeltInventory beltInventory,
+		MechanicalPressTileEntity pressTe) {
+		
+		if (pressTe.getSpeed() == 0)
+			return PASS;
+		if (!pressTe.running)
+			return PASS;
+		if (pressTe.runningTicks != 30)
+			return HOLD;
+
+		Optional<PressingRecipe> recipe = pressTe.getRecipe(transportedStack.stack);
+		pressTe.pressedItems.clear();
+		pressTe.pressedItems.add(transportedStack.stack);
+
+		if (!recipe.isPresent())
+			return PASS;
+
+		ItemStack out = recipe.get()
+			.getRecipeOutput()
+			.copy();
+		List<ItemStack> multipliedOutput = ItemHelper.multipliedOutput(transportedStack.stack, out);
+		if (multipliedOutput.isEmpty())
+			transportedStack.stack = ItemStack.EMPTY;
+		transportedStack.stack = multipliedOutput.get(0);
+		pressTe.sendData();
+		return HOLD;
+	}
+
+}
diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/press/MechanicalPressBlock.java b/src/main/java/com/simibubi/create/content/contraptions/components/press/MechanicalPressBlock.java
index dfba5cc3b..d5cc08418 100644
--- a/src/main/java/com/simibubi/create/content/contraptions/components/press/MechanicalPressBlock.java
+++ b/src/main/java/com/simibubi/create/content/contraptions/components/press/MechanicalPressBlock.java
@@ -1,28 +1,16 @@
 package com.simibubi.create.content.contraptions.components.press;
 
-import java.util.Arrays;
-import java.util.List;
-import java.util.Optional;
-
 import com.simibubi.create.AllBlocks;
 import com.simibubi.create.AllShapes;
 import com.simibubi.create.AllTileEntities;
 import com.simibubi.create.content.contraptions.base.HorizontalKineticBlock;
 import com.simibubi.create.content.contraptions.components.press.MechanicalPressTileEntity.Mode;
-import com.simibubi.create.content.contraptions.relays.belt.AllBeltAttachments.BeltAttachmentState;
-import com.simibubi.create.content.contraptions.relays.belt.AllBeltAttachments.IBeltAttachment;
-import com.simibubi.create.content.contraptions.relays.belt.BeltBlock;
-import com.simibubi.create.content.contraptions.relays.belt.BeltBlock.Slope;
-import com.simibubi.create.content.contraptions.relays.belt.BeltTileEntity;
-import com.simibubi.create.content.contraptions.relays.belt.transport.TransportedItemStack;
 import com.simibubi.create.foundation.block.ITE;
-import com.simibubi.create.foundation.item.ItemHelper;
 
 import net.minecraft.block.Block;
 import net.minecraft.block.BlockState;
 import net.minecraft.entity.player.PlayerEntity;
 import net.minecraft.item.BlockItemUseContext;
-import net.minecraft.item.ItemStack;
 import net.minecraft.tileentity.TileEntity;
 import net.minecraft.util.Direction;
 import net.minecraft.util.Direction.Axis;
@@ -30,12 +18,11 @@ import net.minecraft.util.math.BlockPos;
 import net.minecraft.util.math.shapes.ISelectionContext;
 import net.minecraft.util.math.shapes.VoxelShape;
 import net.minecraft.world.IBlockReader;
-import net.minecraft.world.IWorld;
 import net.minecraft.world.IWorldReader;
 import net.minecraft.world.World;
 
 public class MechanicalPressBlock extends HorizontalKineticBlock
-		implements ITE<MechanicalPressTileEntity>, IBeltAttachment {
+		implements ITE<MechanicalPressTileEntity> {
 
 	public MechanicalPressBlock(Properties properties) {
 		super(properties);
@@ -93,95 +80,6 @@ public class MechanicalPressBlock extends HorizontalKineticBlock
 		return face.getAxis() == state.get(HORIZONTAL_FACING).getAxis();
 	}
 
-	@Override
-	public void onBlockAdded(BlockState state, World worldIn, BlockPos pos, BlockState oldState, boolean isMoving) {
-		onAttachmentPlaced(worldIn, pos, state);
-	}
-
-	@Override
-	public List<BlockPos> getPotentialAttachmentPositions(IWorld world, BlockPos pos, BlockState beltState) {
-		return Arrays.asList(pos.up(2));
-	}
-
-	@Override
-	public void onReplaced(BlockState state, World worldIn, BlockPos pos, BlockState newState, boolean isMoving) {
-		onAttachmentRemoved(worldIn, pos, state);
-		if (state.hasTileEntity() && state.getBlock() != newState.getBlock()) {
-			worldIn.removeTileEntity(pos);
-		}
-	}
-
-	@Override
-	public BlockPos getBeltPositionForAttachment(IWorld world, BlockPos pos, BlockState state) {
-		return pos.down(2);
-	}
-
-	@Override
-	public boolean isAttachedCorrectly(IWorld world, BlockPos attachmentPos, BlockPos beltPos,
-			BlockState attachmentState, BlockState beltState) {
-		return AllBlocks.BELT.has(beltState) && beltState.get(BeltBlock.SLOPE) == Slope.HORIZONTAL;
-	}
-
-	@Override
-	public boolean startProcessingItem(BeltTileEntity belt, TransportedItemStack transported,
-			BeltAttachmentState state) {
-		try {
-			MechanicalPressTileEntity pressTe = getTileEntity(belt.getWorld(), state.attachmentPos);
-			if (pressTe.getSpeed() == 0)
-				return false;
-			if (pressTe.running)
-				return false;
-			if (!pressTe.getRecipe(transported.stack).isPresent())
-				return false;
-
-			state.processingDuration = 1;
-			pressTe.start(Mode.BELT);
-			return true;
-
-		} catch (TileEntityException e) {}
-		return false;
-	}
-
-	@Override
-	public boolean processItem(BeltTileEntity belt, TransportedItemStack transportedStack, BeltAttachmentState state) {
-		try {
-			MechanicalPressTileEntity pressTe = getTileEntity(belt.getWorld(), state.attachmentPos);
-
-			// Not powered
-			if (pressTe.getSpeed() == 0)
-				return false;
-
-			// Running
-			if (!pressTe.running)
-				return false;
-			if (pressTe.runningTicks != 30)
-				return true;
-
-			Optional<PressingRecipe> recipe = pressTe.getRecipe(transportedStack.stack);
-
-			pressTe.pressedItems.clear();
-			pressTe.pressedItems.add(transportedStack.stack);
-
-			if (!recipe.isPresent())
-				return false;
-
-			ItemStack out = recipe.get().getRecipeOutput().copy();
-			List<ItemStack> multipliedOutput = ItemHelper.multipliedOutput(transportedStack.stack, out);
-			if (multipliedOutput.isEmpty())
-				transportedStack.stack = ItemStack.EMPTY;
-			transportedStack.stack = multipliedOutput.get(0);
-
-			BeltTileEntity controllerTE = belt.getControllerTE();
-			if (controllerTE != null)
-				controllerTE.sendData();
-			pressTe.sendData();
-			return true;
-
-		} catch (TileEntityException e) {}
-
-		return false;
-	}
-
 	@Override
 	public Class<MechanicalPressTileEntity> getTileEntityClass() {
 		return MechanicalPressTileEntity.class;
diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/press/MechanicalPressTileEntity.java b/src/main/java/com/simibubi/create/content/contraptions/components/press/MechanicalPressTileEntity.java
index f74f9e725..ef4ce6845 100644
--- a/src/main/java/com/simibubi/create/content/contraptions/components/press/MechanicalPressTileEntity.java
+++ b/src/main/java/com/simibubi/create/content/contraptions/components/press/MechanicalPressTileEntity.java
@@ -11,6 +11,8 @@ import com.simibubi.create.content.contraptions.processing.BasinTileEntity.Basin
 import com.simibubi.create.content.logistics.InWorldProcessing;
 import com.simibubi.create.foundation.advancement.AllTriggers;
 import com.simibubi.create.foundation.item.ItemHelper;
+import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
+import com.simibubi.create.foundation.tileEntity.behaviour.belt.BeltProcessingBehaviour;
 import com.simibubi.create.foundation.utility.VecHelper;
 
 import net.minecraft.entity.Entity;
@@ -59,6 +61,8 @@ public class MechanicalPressTileEntity extends BasinOperatingTileEntity {
 	}
 
 	private static PressingInv pressingInv = new PressingInv();
+	public BeltProcessingBehaviour processingBehaviour;
+
 	public int runningTicks;
 	public boolean running;
 	public Mode mode;
@@ -69,6 +73,15 @@ public class MechanicalPressTileEntity extends BasinOperatingTileEntity {
 		mode = Mode.WORLD;
 	}
 
+	@Override
+	public void addBehaviours(List<TileEntityBehaviour> behaviours) {
+		super.addBehaviours(behaviours);
+		processingBehaviour =
+			new BeltProcessingBehaviour(this).whenItemEnters((s, i) -> BeltPressingCallbacks.onItemReceived(s, i, this))
+				.whileItemHeld((s, i) -> BeltPressingCallbacks.whenItemHeld(s, i, this));
+		behaviours.add(processingBehaviour);
+	}
+
 	@Override
 	public void read(CompoundNBT compound) {
 		running = compound.getBoolean("Running");
@@ -105,7 +118,8 @@ public class MechanicalPressTileEntity extends BasinOperatingTileEntity {
 
 	@Override
 	public AxisAlignedBB getRenderBoundingBox() {
-		return new AxisAlignedBB(pos).expand(0, -1.5, 0).expand(0, 1, 0);
+		return new AxisAlignedBB(pos).expand(0, -1.5, 0)
+			.expand(0, 1, 0);
 	}
 
 	public float getRenderedHeadOffset(float partialTicks) {
@@ -116,7 +130,7 @@ public class MechanicalPressTileEntity extends BasinOperatingTileEntity {
 			}
 			if (runningTicks >= 40) {
 				return MathHelper.clamp(((60 - runningTicks) + 1 - partialTicks) / 20f * mode.headOffset, 0,
-						mode.headOffset);
+					mode.headOffset);
 			}
 		}
 		return 0;
@@ -180,7 +194,8 @@ public class MechanicalPressTileEntity extends BasinOperatingTileEntity {
 					if (basinInv.isPresent() && orElse instanceof BasinInventory) {
 						BasinInventory inv = (BasinInventory) orElse;
 
-						for (int slot = 0; slot < inv.getInputHandler().getSlots(); slot++) {
+						for (int slot = 0; slot < inv.getInputHandler()
+							.getSlots(); slot++) {
 							ItemStack stackInSlot = inv.getStackInSlot(slot);
 							if (stackInSlot.isEmpty())
 								continue;
@@ -194,9 +209,9 @@ public class MechanicalPressTileEntity extends BasinOperatingTileEntity {
 
 			if (!world.isRemote) {
 				world.playSound(null, getPos(), AllSoundEvents.MECHANICAL_PRESS_ITEM_BREAK.get(), SoundCategory.BLOCKS,
-						.5f, 1f);
+					.5f, 1f);
 				world.playSound(null, getPos(), AllSoundEvents.MECHANICAL_PRESS_ACTIVATION.get(), SoundCategory.BLOCKS,
-						.125f, 1f);
+					.125f, 1f);
 			}
 		}
 
@@ -229,12 +244,12 @@ public class MechanicalPressTileEntity extends BasinOperatingTileEntity {
 			pressedItems.forEach(stack -> makeCompactingParticleEffect(VecHelper.getCenterOf(pos.down(2)), stack));
 		}
 		if (mode == Mode.BELT) {
-			pressedItems.forEach(
-					stack -> makePressingParticleEffect(VecHelper.getCenterOf(pos.down(2)).add(0, 8 / 16f, 0), stack));
+			pressedItems.forEach(stack -> makePressingParticleEffect(VecHelper.getCenterOf(pos.down(2))
+				.add(0, 8 / 16f, 0), stack));
 		}
 		if (mode == Mode.WORLD) {
-			pressedItems.forEach(
-					stack -> makePressingParticleEffect(VecHelper.getCenterOf(pos.down(1)).add(0, -1 / 4f, 0), stack));
+			pressedItems.forEach(stack -> makePressingParticleEffect(VecHelper.getCenterOf(pos.down(1))
+				.add(0, -1 / 4f, 0), stack));
 		}
 
 		pressedItems.clear();
@@ -243,9 +258,10 @@ public class MechanicalPressTileEntity extends BasinOperatingTileEntity {
 	public void makePressingParticleEffect(Vec3d pos, ItemStack stack) {
 		if (world.isRemote) {
 			for (int i = 0; i < 20; i++) {
-				Vec3d motion = VecHelper.offsetRandomly(Vec3d.ZERO, world.rand, .125f).mul(1, 0, 1);
+				Vec3d motion = VecHelper.offsetRandomly(Vec3d.ZERO, world.rand, .125f)
+					.mul(1, 0, 1);
 				world.addParticle(new ItemParticleData(ParticleTypes.ITEM, stack), pos.x, pos.y - .25f, pos.z, motion.x,
-						motion.y + .125f, motion.z);
+					motion.y + .125f, motion.z);
 			}
 		}
 	}
@@ -253,23 +269,24 @@ public class MechanicalPressTileEntity extends BasinOperatingTileEntity {
 	public void makeCompactingParticleEffect(Vec3d pos, ItemStack stack) {
 		if (world.isRemote) {
 			for (int i = 0; i < 20; i++) {
-				Vec3d motion = VecHelper.offsetRandomly(Vec3d.ZERO, world.rand, .175f).mul(1, 0, 1);
+				Vec3d motion = VecHelper.offsetRandomly(Vec3d.ZERO, world.rand, .175f)
+					.mul(1, 0, 1);
 				world.addParticle(new ItemParticleData(ParticleTypes.ITEM, stack), pos.x, pos.y, pos.z, motion.x,
-						motion.y + .25f, motion.z);
+					motion.y + .25f, motion.z);
 			}
 		}
 	}
 
 	public Optional<PressingRecipe> getRecipe(ItemStack item) {
 		pressingInv.setInventorySlotContents(0, item);
-		Optional<PressingRecipe> recipe =
-			world.getRecipeManager().getRecipe(AllRecipeTypes.PRESSING.getType(), pressingInv, world);
+		Optional<PressingRecipe> recipe = world.getRecipeManager()
+			.getRecipe(AllRecipeTypes.PRESSING.getType(), pressingInv, world);
 		return recipe;
 	}
 
 	public static boolean canCompress(NonNullList<Ingredient> ingredients) {
-		return (ingredients.size() == 4 || ingredients.size() == 9)
-				&& ItemHelper.condenseIngredients(ingredients).size() == 1;
+		return (ingredients.size() == 4 || ingredients.size() == 9) && ItemHelper.condenseIngredients(ingredients)
+			.size() == 1;
 	}
 
 	@Override
@@ -283,7 +300,8 @@ public class MechanicalPressTileEntity extends BasinOperatingTileEntity {
 			return false;
 
 		NonNullList<Ingredient> ingredients = recipe.getIngredients();
-		if (!ingredients.stream().allMatch(Ingredient::isSimple))
+		if (!ingredients.stream()
+			.allMatch(Ingredient::isSimple))
 			return false;
 
 		List<ItemStack> remaining = new ArrayList<>();
diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/saw/SawRenderer.java b/src/main/java/com/simibubi/create/content/contraptions/components/saw/SawRenderer.java
index 705fb5c6c..a8e1197ad 100644
--- a/src/main/java/com/simibubi/create/content/contraptions/components/saw/SawRenderer.java
+++ b/src/main/java/com/simibubi/create/content/contraptions/components/saw/SawRenderer.java
@@ -61,18 +61,25 @@ public class SawRenderer extends SafeTileEntityRenderer<SawTileEntity> {
 			if (te.getSpeed() < 0 ^ alongZ)
 				offset = 1 - offset;
 
-			ItemStack stack = te.inventory.getStackInSlot(0);
-			ItemRenderer itemRenderer = Minecraft.getInstance().getItemRenderer();
-			IBakedModel modelWithOverrides = itemRenderer.getItemModelWithOverrides(stack, te.getWorld(), null);
-			boolean blockItem = modelWithOverrides.isGui3d();
-
-			ms.translate(alongZ ? offset : .5, blockItem ? .925f : 13f / 16f, alongZ ? .5 : offset);
-
-			ms.scale(.5f, .5f, .5f);
-			if (alongZ)
-				ms.multiply(Vector3f.POSITIVE_Y.getDegreesQuaternion(90));
-			ms.multiply(Vector3f.POSITIVE_X.getDegreesQuaternion(90));
-			itemRenderer.renderItem(stack, ItemCameraTransforms.TransformType.FIXED, light, overlay, ms, buffer);
+			for (int i = 0; i < te.inventory.getSlots(); i++) {
+				ItemStack stack = te.inventory.getStackInSlot(i);
+				if (stack.isEmpty())
+					continue;
+				
+				ItemRenderer itemRenderer = Minecraft.getInstance().getItemRenderer();
+				IBakedModel modelWithOverrides = itemRenderer.getItemModelWithOverrides(stack, te.getWorld(), null);
+				boolean blockItem = modelWithOverrides.isGui3d();
+				
+				ms.translate(alongZ ? offset : .5, blockItem ? .925f : 13f / 16f, alongZ ? .5 : offset);
+				
+				ms.scale(.5f, .5f, .5f);
+				if (alongZ)
+					ms.multiply(Vector3f.POSITIVE_Y.getDegreesQuaternion(90));
+				ms.multiply(Vector3f.POSITIVE_X.getDegreesQuaternion(90));
+				itemRenderer.renderItem(stack, ItemCameraTransforms.TransformType.FIXED, light, overlay, ms, buffer);
+				break;
+			}
+			
 			ms.pop();
 		}
 	}
diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/saw/SawTileEntity.java b/src/main/java/com/simibubi/create/content/contraptions/components/saw/SawTileEntity.java
index 07539ab22..9fd313749 100644
--- a/src/main/java/com/simibubi/create/content/contraptions/components/saw/SawTileEntity.java
+++ b/src/main/java/com/simibubi/create/content/contraptions/components/saw/SawTileEntity.java
@@ -8,13 +8,12 @@ import java.util.List;
 import java.util.Random;
 import java.util.stream.Collectors;
 
-import com.simibubi.create.AllBlocks;
 import com.simibubi.create.AllRecipeTypes;
 import com.simibubi.create.content.contraptions.components.actors.BlockBreakingKineticTileEntity;
 import com.simibubi.create.content.contraptions.processing.ProcessingInventory;
-import com.simibubi.create.content.contraptions.relays.belt.BeltTileEntity;
 import com.simibubi.create.foundation.item.ItemHelper;
 import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
+import com.simibubi.create.foundation.tileEntity.behaviour.belt.DirectBeltInputBehaviour;
 import com.simibubi.create.foundation.tileEntity.behaviour.filtering.FilteringBehaviour;
 import com.simibubi.create.foundation.utility.BlockHelper;
 import com.simibubi.create.foundation.utility.TreeCutter;
@@ -44,7 +43,6 @@ import net.minecraft.particles.IParticleData;
 import net.minecraft.particles.ItemParticleData;
 import net.minecraft.particles.ParticleTypes;
 import net.minecraft.tags.BlockTags;
-import net.minecraft.tileentity.TileEntity;
 import net.minecraft.tileentity.TileEntityType;
 import net.minecraft.util.Direction;
 import net.minecraft.util.math.BlockPos;
@@ -78,6 +76,7 @@ public class SawTileEntity extends BlockBreakingKineticTileEntity {
 		super.addBehaviours(behaviours);
 		filtering = new FilteringBehaviour(this, new SawFilterSlot());
 		behaviours.add(filtering);
+		behaviours.add(new DirectBeltInputBehaviour(this));
 	}
 
 	@Override
@@ -142,81 +141,51 @@ public class SawTileEntity extends BlockBreakingKineticTileEntity {
 
 		Vec3d itemMovement = getItemMovementVec();
 		Direction itemMovementFacing = Direction.getFacingFromVector(itemMovement.x, itemMovement.y, itemMovement.z);
-		Vec3d outPos = VecHelper.getCenterOf(pos).add(itemMovement.scale(.5f).add(0, .5, 0));
-		Vec3d outMotion = itemMovement.scale(.0625).add(0, .125, 0);
+		if (inventory.remainingTime > 0)
+			return;
+		inventory.remainingTime = 0;
 
-		if (inventory.remainingTime <= 0) {
-
-			// Try moving items onto the belt
-			BlockPos nextPos = pos.add(itemMovement.x, itemMovement.y, itemMovement.z);
-			if (AllBlocks.BELT.has(world.getBlockState(nextPos))) {
-				TileEntity te = world.getTileEntity(nextPos);
-				if (te != null && te instanceof BeltTileEntity) {
-					for (int slot = 0; slot < inventory.getSlots(); slot++) {
-						ItemStack stack = inventory.getStackInSlot(slot);
-						if (stack.isEmpty())
-							continue;
-
-						if (((BeltTileEntity) te).tryInsertingFromSide(itemMovementFacing, stack, false))
-							inventory.setStackInSlot(slot, ItemStack.EMPTY);
-						else {
-							inventory.remainingTime = 0;
-							return;
-						}
-					}
-					inventory.clear();
-					inventory.remainingTime = -1;
-					sendData();
-				}
-			}
-
-			// Try moving items onto next saw
-			if (AllBlocks.MECHANICAL_SAW.has(world.getBlockState(nextPos))) {
-				TileEntity te = world.getTileEntity(nextPos);
-				if (te != null && te instanceof SawTileEntity) {
-					SawTileEntity sawTileEntity = (SawTileEntity) te;
-					Vec3d otherMovement = sawTileEntity.getItemMovementVec();
-					if (Direction.getFacingFromVector(otherMovement.x, otherMovement.y,
-							otherMovement.z) != itemMovementFacing.getOpposite()) {
-						for (int slot = 0; slot < inventory.getSlots(); slot++) {
-							ItemStack stack = inventory.getStackInSlot(slot);
-							if (stack.isEmpty())
-								continue;
-
-							ProcessingInventory sawInv = sawTileEntity.inventory;
-							if (sawInv.isEmpty()) {
-								sawInv.insertItem(0, stack, false);
-								inventory.setStackInSlot(slot, ItemStack.EMPTY);
-
-							} else {
-								inventory.remainingTime = 0;
-								return;
-							}
-						}
-						inventory.clear();
-						inventory.remainingTime = -1;
-						sendData();
-					}
-				}
-			}
-
-			// Eject Items
+		BlockPos nextPos = pos.add(itemMovement.x, itemMovement.y, itemMovement.z);
+		DirectBeltInputBehaviour behaviour = TileEntityBehaviour.get(world, nextPos, DirectBeltInputBehaviour.TYPE);
+		if (behaviour != null) {
+			boolean changed = false;
+			if (!behaviour.canInsertFromSide(itemMovementFacing))
+				return;
 			for (int slot = 0; slot < inventory.getSlots(); slot++) {
 				ItemStack stack = inventory.getStackInSlot(slot);
 				if (stack.isEmpty())
 					continue;
-				ItemEntity entityIn = new ItemEntity(world, outPos.x, outPos.y, outPos.z, stack);
-				entityIn.setMotion(outMotion);
-				world.addEntity(entityIn);
+				ItemStack remainder = behaviour.handleInsertion(stack, itemMovementFacing, false);
+				if (remainder.equals(stack, false))
+					continue;
+				inventory.setStackInSlot(slot, remainder);
+				changed = true;
+			}
+			if (changed) {
+				markDirty();
+				sendData();
 			}
-			inventory.clear();
-			world.updateComparatorOutputLevel(pos, getBlockState().getBlock());
-			inventory.remainingTime = -1;
-			sendData();
 			return;
 		}
 
-		return;
+		// Eject Items
+		Vec3d outPos = VecHelper.getCenterOf(pos)
+			.add(itemMovement.scale(.5f)
+				.add(0, .5, 0));
+		Vec3d outMotion = itemMovement.scale(.0625)
+			.add(0, .125, 0);
+		for (int slot = 0; slot < inventory.getSlots(); slot++) {
+			ItemStack stack = inventory.getStackInSlot(slot);
+			if (stack.isEmpty())
+				continue;
+			ItemEntity entityIn = new ItemEntity(world, outPos.x, outPos.y, outPos.z, stack);
+			entityIn.setMotion(outMotion);
+			world.addEntity(entityIn);
+		}
+		inventory.clear();
+		world.updateComparatorOutputLevel(pos, getBlockState().getBlock());
+		inventory.remainingTime = -1;
+		sendData();
 	}
 
 	@Override
@@ -240,8 +209,8 @@ public class SawTileEntity extends BlockBreakingKineticTileEntity {
 		IParticleData particleData = null;
 		float speed = 1;
 		if (stack.getItem() instanceof BlockItem)
-			particleData =
-				new BlockParticleData(ParticleTypes.BLOCK, ((BlockItem) stack.getItem()).getBlock().getDefaultState());
+			particleData = new BlockParticleData(ParticleTypes.BLOCK, ((BlockItem) stack.getItem()).getBlock()
+				.getDefaultState());
 		else {
 			particleData = new ItemParticleData(ParticleTypes.ITEM, stack);
 			speed = .125f;
@@ -253,7 +222,7 @@ public class SawTileEntity extends BlockBreakingKineticTileEntity {
 		float offset = inventory.recipeDuration != 0 ? (float) (inventory.remainingTime) / inventory.recipeDuration : 0;
 		offset -= .5f;
 		world.addParticle(particleData, pos.getX() + -vec.x * offset, pos.getY() + .45f, pos.getZ() + -vec.z * offset,
-				-vec.x * speed, r.nextFloat() * speed, -vec.z * speed);
+			-vec.x * speed, r.nextFloat() * speed, -vec.z * speed);
 	}
 
 	public Vec3d getItemMovementVec() {
@@ -271,7 +240,8 @@ public class SawTileEntity extends BlockBreakingKineticTileEntity {
 
 		IRecipe<?> recipe = recipes.get(recipeIndex);
 
-		int rolls = inventory.getStackInSlot(0).getCount();
+		int rolls = inventory.getStackInSlot(0)
+			.getCount();
 		inventory.clear();
 
 		List<ItemStack> list = new ArrayList<>();
@@ -280,7 +250,8 @@ public class SawTileEntity extends BlockBreakingKineticTileEntity {
 			if (recipe instanceof CuttingRecipe)
 				results = ((CuttingRecipe) recipe).rollResults();
 			else if (recipe instanceof StonecuttingRecipe)
-				results.add(recipe.getRecipeOutput().copy());
+				results.add(recipe.getRecipeOutput()
+					.copy());
 
 			for (int i = 0; i < results.size(); i++) {
 				ItemStack stack = results.get(i);
@@ -294,10 +265,11 @@ public class SawTileEntity extends BlockBreakingKineticTileEntity {
 
 	private List<? extends IRecipe<?>> getRecipes() {
 		List<IRecipe<?>> startedSearch = RecipeFinder.get(cuttingRecipesKey, world,
-				RecipeConditions.isOfType(IRecipeType.STONECUTTING, AllRecipeTypes.CUTTING.getType()));
-		return startedSearch.stream().filter(RecipeConditions.outputMatchesFilter(filtering))
-				.filter(RecipeConditions.firstIngredientMatches(inventory.getStackInSlot(0)))
-				.collect(Collectors.toList());
+			RecipeConditions.isOfType(IRecipeType.STONECUTTING, AllRecipeTypes.CUTTING.getType()));
+		return startedSearch.stream()
+			.filter(RecipeConditions.outputMatchesFilter(filtering))
+			.filter(RecipeConditions.firstIngredientMatches(inventory.getStackInSlot(0)))
+			.collect(Collectors.toList());
 	}
 
 	public void insertItem(ItemEntity entity) {
@@ -309,7 +281,8 @@ public class SawTileEntity extends BlockBreakingKineticTileEntity {
 			return;
 
 		inventory.clear();
-		inventory.insertItem(0, entity.getItem().copy(), false);
+		inventory.insertItem(0, entity.getItem()
+			.copy(), false);
 		entity.remove();
 	}
 
@@ -357,7 +330,9 @@ public class SawTileEntity extends BlockBreakingKineticTileEntity {
 
 	@Override
 	protected boolean shouldRun() {
-		return getBlockState().get(SawBlock.FACING).getAxis().isHorizontal();
+		return getBlockState().get(SawBlock.FACING)
+			.getAxis()
+			.isHorizontal();
 	}
 
 	@Override
diff --git a/src/main/java/com/simibubi/create/content/contraptions/processing/BasinTileEntity.java b/src/main/java/com/simibubi/create/content/contraptions/processing/BasinTileEntity.java
index 0c9c8febf..7632d8a11 100644
--- a/src/main/java/com/simibubi/create/content/contraptions/processing/BasinTileEntity.java
+++ b/src/main/java/com/simibubi/create/content/contraptions/processing/BasinTileEntity.java
@@ -1,8 +1,11 @@
 package com.simibubi.create.content.contraptions.processing;
 
+import java.util.List;
 import java.util.Optional;
 
-import com.simibubi.create.foundation.tileEntity.SyncedTileEntity;
+import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
+import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
+import com.simibubi.create.foundation.tileEntity.behaviour.belt.DirectBeltInputBehaviour;
 
 import net.minecraft.item.ItemStack;
 import net.minecraft.nbt.CompoundNBT;
@@ -19,7 +22,7 @@ import net.minecraftforge.items.ItemStackHandler;
 import net.minecraftforge.items.wrapper.CombinedInvWrapper;
 import net.minecraftforge.items.wrapper.RecipeWrapper;
 
-public class BasinTileEntity extends SyncedTileEntity implements ITickableTileEntity {
+public class BasinTileEntity extends SmartTileEntity implements ITickableTileEntity {
 
 	public boolean contentsChanged;
 
@@ -96,7 +99,12 @@ public class BasinTileEntity extends SyncedTileEntity implements ITickableTileEn
 		contentsChanged = true;
 		recipeInventory = new BasinInputInventory();
 	}
-
+	
+	@Override
+	public void addBehaviours(List<TileEntityBehaviour> behaviours) {
+		behaviours.add(new DirectBeltInputBehaviour(this));
+	}
+	
 	@Override
 	public void read(CompoundNBT compound) {
 		super.read(compound);
diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/belt/AllBeltAttachments.java b/src/main/java/com/simibubi/create/content/contraptions/relays/belt/AllBeltAttachments.java
deleted file mode 100644
index 7f01d12e6..000000000
--- a/src/main/java/com/simibubi/create/content/contraptions/relays/belt/AllBeltAttachments.java
+++ /dev/null
@@ -1,202 +0,0 @@
-package com.simibubi.create.content.contraptions.relays.belt;
-
-import java.util.LinkedList;
-import java.util.List;
-import java.util.function.Consumer;
-
-import com.simibubi.create.AllBlocks;
-import com.simibubi.create.Create;
-import com.simibubi.create.content.contraptions.relays.belt.transport.TransportedItemStack;
-
-import net.minecraft.block.Block;
-import net.minecraft.block.BlockState;
-import net.minecraft.entity.Entity;
-import net.minecraft.nbt.CompoundNBT;
-import net.minecraft.nbt.INBT;
-import net.minecraft.nbt.ListNBT;
-import net.minecraft.nbt.NBTUtil;
-import net.minecraft.util.math.BlockPos;
-import net.minecraft.world.IWorld;
-import net.minecraft.world.World;
-
-public enum AllBeltAttachments { //TODO rework this nonsense
-
-	BELT_FUNNEL(AllBlocks.FUNNEL.get()),
-	BELT_OBSERVER(AllBlocks.BELT_OBSERVER.get()),
-	MECHANICAL_PRESS(AllBlocks.MECHANICAL_PRESS.get()), 
-	LOGISTICAL_ATTACHABLES(AllBlocks.EXTRACTOR.get()),
-
-	;
-
-	IBeltAttachment attachment;
-
-	private AllBeltAttachments(Block attachment) {
-		this.attachment = (IBeltAttachment) attachment;
-	}
-
-	public interface IBeltAttachment {
-
-		public List<BlockPos> getPotentialAttachmentPositions(IWorld world, BlockPos pos, BlockState beltState);
-
-		public BlockPos getBeltPositionForAttachment(IWorld world, BlockPos pos, BlockState state);
-
-		default boolean isAttachedCorrectly(IWorld world, BlockPos attachmentPos, BlockPos beltPos,
-				BlockState attachmentState, BlockState beltState) {
-			return true;
-		}
-
-		default boolean processEntity(BeltTileEntity te, Entity entity, BeltAttachmentState state) {
-			return false;
-		}
-
-		default boolean startProcessingItem(BeltTileEntity te, TransportedItemStack transported,
-				BeltAttachmentState state) {
-			return false;
-		}
-
-		default boolean processItem(BeltTileEntity te, TransportedItemStack transported, BeltAttachmentState state) {
-			return false;
-		}
-
-		default void onAttachmentPlaced(IWorld world, BlockPos pos, BlockState state) {
-			BlockPos beltPos = getBeltPositionForAttachment(world, pos, state);
-			BeltTileEntity belt = BeltHelper.getSegmentTE(world, beltPos);
-
-			if (belt == null)
-				return;
-			if (!isAttachedCorrectly(world, pos, beltPos, state, world.getBlockState(beltPos)))
-				return;
-
-			belt.attachmentTracker.addAttachment(world, pos);
-			belt.markDirty();
-			belt.sendData();
-		}
-
-		default void onAttachmentRemoved(IWorld world, BlockPos pos, BlockState state) {
-			BlockPos beltPos = getBeltPositionForAttachment(world, pos, state);
-			BeltTileEntity belt = BeltHelper.getSegmentTE(world, beltPos);
-
-			if (belt == null)
-				return;
-			if (!isAttachedCorrectly(world, pos, beltPos, state, world.getBlockState(beltPos)))
-				return;
-
-			belt.attachmentTracker.removeAttachment(pos);
-			belt.markDirty();
-			belt.sendData();
-		}
-
-	}
-
-	public static class BeltAttachmentState {
-		public IBeltAttachment attachment;
-		public BlockPos attachmentPos;
-		public int processingDuration;
-		public Entity processingEntity;
-		public TransportedItemStack processingStack;
-
-		public BeltAttachmentState(IBeltAttachment attachment, BlockPos attachmentPos) {
-			this.attachment = attachment;
-			this.attachmentPos = attachmentPos;
-		}
-
-	}
-
-	public static class Tracker {
-		public List<BeltAttachmentState> attachments;
-		private BeltTileEntity te;
-
-		public Tracker(BeltTileEntity te) {
-			attachments = new LinkedList<>();
-			this.te = te;
-		}
-
-		public void findAttachments(BeltTileEntity belt) {
-			for (AllBeltAttachments ba : AllBeltAttachments.values()) {
-				World world = belt.getWorld();
-				BlockPos beltPos = belt.getPos();
-				BlockState beltState = belt.getBlockState();
-				List<BlockPos> attachmentPositions =
-					ba.attachment.getPotentialAttachmentPositions(world, beltPos, beltState);
-
-				for (BlockPos potentialPos : attachmentPositions) {
-					if (!world.isBlockPresent(potentialPos))
-						continue;
-					BlockState state = world.getBlockState(potentialPos);
-					if (!(state.getBlock() instanceof IBeltAttachment))
-						continue;
-					IBeltAttachment attachment = (IBeltAttachment) state.getBlock();
-					if (!attachment.getBeltPositionForAttachment(world, potentialPos, state).equals(beltPos))
-						continue;
-					if (!attachment.isAttachedCorrectly(world, potentialPos, beltPos, state, beltState))
-						continue;
-
-					addAttachment(world, potentialPos);
-				}
-			}
-		}
-
-		public BeltAttachmentState addAttachment(IWorld world, BlockPos pos) {
-			BlockState state = world.getBlockState(pos);
-			removeAttachment(pos);
-			if (!(state.getBlock() instanceof IBeltAttachment)) {
-				Create.logger.warn("Missing belt attachment for Belt at " + pos.toString());
-				return null;
-			}
-			BeltAttachmentState newAttachmentState = new BeltAttachmentState((IBeltAttachment) state.getBlock(), pos);
-			attachments.add(newAttachmentState);
-			te.markDirty();
-			return newAttachmentState;
-		}
-
-		public void removeAttachment(BlockPos pos) {
-			BeltAttachmentState toRemove = null;
-			for (BeltAttachmentState atState : attachments)
-				if (atState.attachmentPos.equals(pos))
-					toRemove = atState;
-			if (toRemove != null)
-				attachments.remove(toRemove);
-			te.markDirty();
-		}
-
-		public void forEachAttachment(Consumer<BeltAttachmentState> consumer) {
-			attachments.forEach(consumer::accept);
-		}
-
-		public void readAndSearch(CompoundNBT nbt, BeltTileEntity belt) {
-			attachments.clear();
-			if (!nbt.contains("HasAttachments")) {
-				findAttachments(belt);
-				return;
-			}
-			if (nbt.contains("AttachmentData")) {
-				ListNBT list = (ListNBT) nbt.get("AttachmentData");
-				for (INBT data : list) {
-					CompoundNBT stateNBT = (CompoundNBT) data;
-					BlockPos attachmentPos = NBTUtil.readBlockPos(stateNBT.getCompound("Position"));
-					BeltAttachmentState atState = addAttachment(belt.getWorld(), attachmentPos);
-					if (atState == null)
-						continue;
-					atState.processingDuration = stateNBT.getInt("Duration");
-				}
-			}
-		}
-
-		public void write(CompoundNBT nbt) {
-			if (!attachments.isEmpty()) {
-				nbt.putBoolean("HasAttachments", true);
-				ListNBT list = new ListNBT();
-				forEachAttachment(atState -> {
-					CompoundNBT stateNBT = new CompoundNBT();
-					stateNBT.put("Position", NBTUtil.writeBlockPos(atState.attachmentPos));
-					stateNBT.putInt("Duration", atState.processingDuration);
-					list.add(stateNBT);
-				});
-				nbt.put("AttachmentData", list);
-			}
-
-		}
-
-	}
-
-}
diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/belt/BeltBlock.java b/src/main/java/com/simibubi/create/content/contraptions/relays/belt/BeltBlock.java
index a85c22e84..a34721af3 100644
--- a/src/main/java/com/simibubi/create/content/contraptions/relays/belt/BeltBlock.java
+++ b/src/main/java/com/simibubi/create/content/contraptions/relays/belt/BeltBlock.java
@@ -210,9 +210,6 @@ public class BeltBlock extends HorizontalKineticBlock implements ITE<BeltTileEnt
 	@Override
 	public void onBlockAdded(BlockState state, World worldIn, BlockPos pos, BlockState oldState, boolean isMoving) {
 		updateNeighbouringTunnel(worldIn, pos, state);
-		withTileEntityDo(worldIn, pos, te -> {
-			te.attachmentTracker.findAttachments(te);
-		});
 	}
 
 	@Override
diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/belt/BeltRenderer.java b/src/main/java/com/simibubi/create/content/contraptions/relays/belt/BeltRenderer.java
index dca55c139..e041321be 100644
--- a/src/main/java/com/simibubi/create/content/contraptions/relays/belt/BeltRenderer.java
+++ b/src/main/java/com/simibubi/create/content/contraptions/relays/belt/BeltRenderer.java
@@ -109,7 +109,7 @@ public class BeltRenderer extends SafeTileEntityRenderer<BeltTileEntity> {
 		int verticality = slope == Slope.DOWNWARD ? -1 : slope == Slope.UPWARD ? 1 : 0;
 		boolean slopeAlongX = te.getBeltFacing().getAxis() == Axis.X;
 
-		for (TransportedItemStack transported : te.getInventory().getItems()) {
+		for (TransportedItemStack transported : te.getInventory().getTransportedItems()) {
 			ms.push();
 			MatrixStacker.of(ms).nudge(transported.angle);
 			float offset = MathHelper.lerp(partialTicks, transported.prevBeltPosition, transported.beltPosition);
diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/belt/BeltTileEntity.java b/src/main/java/com/simibubi/create/content/contraptions/relays/belt/BeltTileEntity.java
index 6d2bb7392..0c2690f91 100644
--- a/src/main/java/com/simibubi/create/content/contraptions/relays/belt/BeltTileEntity.java
+++ b/src/main/java/com/simibubi/create/content/contraptions/relays/belt/BeltTileEntity.java
@@ -15,13 +15,15 @@ import java.util.Map;
 
 import com.simibubi.create.AllBlocks;
 import com.simibubi.create.content.contraptions.base.KineticTileEntity;
-import com.simibubi.create.content.contraptions.relays.belt.AllBeltAttachments.Tracker;
 import com.simibubi.create.content.contraptions.relays.belt.BeltBlock.Part;
 import com.simibubi.create.content.contraptions.relays.belt.BeltBlock.Slope;
 import com.simibubi.create.content.contraptions.relays.belt.transport.BeltInventory;
 import com.simibubi.create.content.contraptions.relays.belt.transport.BeltMovementHandler;
 import com.simibubi.create.content.contraptions.relays.belt.transport.BeltMovementHandler.TransportedEntityInfo;
+import com.simibubi.create.content.contraptions.relays.belt.transport.ItemHandlerBeltSegment;
 import com.simibubi.create.content.contraptions.relays.belt.transport.TransportedItemStack;
+import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
+import com.simibubi.create.foundation.tileEntity.behaviour.belt.DirectBeltInputBehaviour;
 import com.simibubi.create.foundation.utility.ColorHelper;
 
 import net.minecraft.block.BlockState;
@@ -46,7 +48,6 @@ import net.minecraftforge.items.IItemHandler;
 public class BeltTileEntity extends KineticTileEntity {
 
 	public Map<Entity, TransportedEntityInfo> passengers;
-	public AllBeltAttachments.Tracker attachmentTracker;
 	public int color;
 	public int beltLength;
 	public int index;
@@ -61,11 +62,18 @@ public class BeltTileEntity extends KineticTileEntity {
 	public BeltTileEntity(TileEntityType<? extends BeltTileEntity> type) {
 		super(type);
 		controller = BlockPos.ZERO;
-		attachmentTracker = new Tracker(this);
 		itemHandler = LazyOptional.empty();
 		color = -1;
 	}
 
+	@Override
+	public void addBehaviours(List<TileEntityBehaviour> behaviours) {
+		super.addBehaviours(behaviours);
+		behaviours.add(new DirectBeltInputBehaviour(this)
+			.onlyInsertWhen(d -> getSpeed() != 0 && getMovementFacing() != d.getOpposite())
+			.setInsertionHandler(this::tryInsertingFromSide));
+	}
+
 	@Override
 	public void tick() {
 		super.tick();
@@ -75,12 +83,6 @@ public class BeltTileEntity extends KineticTileEntity {
 			BeltBlock.initBelt(world, pos);
 		if (!AllBlocks.BELT.has(world.getBlockState(pos)))
 			return;
-
-		// Initialize Belt Attachments
-		if (world != null && trackerUpdateTag != null) {
-			attachmentTracker.readAndSearch(trackerUpdateTag, this);
-			trackerUpdateTag = null;
-		}
 		if (getSpeed() == 0)
 			return;
 
@@ -136,7 +138,7 @@ public class BeltTileEntity extends KineticTileEntity {
 		BeltInventory inventory = ((BeltTileEntity) te).getInventory();
 		if (inventory == null)
 			return;
-		IItemHandler handler = inventory.createHandlerForSegment(index);
+		IItemHandler handler = new ItemHandlerBeltSegment(inventory, index);
 		itemHandler = LazyOptional.of(() -> handler);
 	}
 
@@ -163,8 +165,6 @@ public class BeltTileEntity extends KineticTileEntity {
 
 	@Override
 	public CompoundNBT write(CompoundNBT compound) {
-		attachmentTracker.write(compound);
-
 		if (controller != null)
 			compound.put("Controller", NBTUtil.writeBlockPos(controller));
 		compound.putBoolean("IsController", isController());
@@ -246,7 +246,8 @@ public class BeltTileEntity extends KineticTileEntity {
 	}
 
 	public float getDirectionAwareBeltMovementSpeed() {
-		int offset = getBeltFacing().getAxisDirection().getOffset();
+		int offset = getBeltFacing().getAxisDirection()
+			.getOffset();
 		if (getBeltFacing().getAxis() == Axis.X)
 			offset *= -1;
 		return getBeltMovementSpeed() * offset;
@@ -270,8 +271,8 @@ public class BeltTileEntity extends KineticTileEntity {
 		if (part == MIDDLE)
 			return false;
 
-		boolean movingPositively =
-			(getSpeed() > 0 == (direction.getAxisDirection().getOffset() == 1)) ^ direction.getAxis() == Axis.X;
+		boolean movingPositively = (getSpeed() > 0 == (direction.getAxisDirection()
+			.getOffset() == 1)) ^ direction.getAxis() == Axis.X;
 		return part == Part.START ^ movingPositively;
 	}
 
@@ -311,8 +312,8 @@ public class BeltTileEntity extends KineticTileEntity {
 
 	public Direction getMovementFacing() {
 		Axis axis = getBeltFacing().getAxis();
-		return Direction
-				.getFacingFromAxisDirection(axis, getBeltMovementSpeed() < 0 ^ axis == Axis.X ? NEGATIVE : POSITIVE);
+		return Direction.getFacingFromAxisDirection(axis,
+			getBeltMovementSpeed() < 0 ^ axis == Axis.X ? NEGATIVE : POSITIVE);
 	}
 
 	protected Direction getBeltFacing() {
@@ -332,29 +333,39 @@ public class BeltTileEntity extends KineticTileEntity {
 		return inventory;
 	}
 
+	/**
+	 * always target a DirectBeltInsertionBehaviour
+	 */
+	@Deprecated
 	public boolean tryInsertingFromSide(Direction side, ItemStack stack, boolean simulate) {
-		return tryInsertingFromSide(side, new TransportedItemStack(stack), simulate);
+		return tryInsertingFromSide(new TransportedItemStack(stack), side, simulate).isEmpty();
 	}
 
-	public boolean tryInsertingFromSide(Direction side, TransportedItemStack transportedStack, boolean simulate) {
+	private ItemStack tryInsertingFromSide(TransportedItemStack transportedStack, Direction side, boolean simulate) {
 		BeltTileEntity nextBeltController = getControllerTE();
+		ItemStack inserted = transportedStack.stack;
+		ItemStack empty = ItemStack.EMPTY;
+
 		if (nextBeltController == null)
-			return false;
+			return inserted;
 		BeltInventory nextInventory = nextBeltController.getInventory();
 
 		if (getSpeed() == 0)
-			return false;
-		if (!nextInventory.canInsertFrom(index, side))
-			return false;
+			return inserted;
+		if (!nextInventory.canInsertAtFromSide(index, side))
+			return inserted;
 		if (simulate)
-			return true;
+			return empty;
 
+		transportedStack = transportedStack.copy();
 		transportedStack.beltPosition = index + .5f - Math.signum(getDirectionAwareBeltMovementSpeed()) / 16f;
 
 		Direction movementFacing = getMovementFacing();
-		if (!side.getAxis().isVertical()) {
+		if (!side.getAxis()
+			.isVertical()) {
 			if (movementFacing != side) {
-				transportedStack.sideOffset = side.getAxisDirection().getOffset() * .35f;
+				transportedStack.sideOffset = side.getAxisDirection()
+					.getOffset() * .35f;
 				if (side.getAxis() == Axis.X)
 					transportedStack.sideOffset *= -1;
 			} else
@@ -368,8 +379,7 @@ public class BeltTileEntity extends KineticTileEntity {
 		nextInventory.addItem(transportedStack);
 		nextBeltController.markDirty();
 		nextBeltController.sendData();
-
-		return true;
+		return empty;
 	}
 
 }
diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/belt/transport/BeltInventory.java b/src/main/java/com/simibubi/create/content/contraptions/relays/belt/transport/BeltInventory.java
index d362f6672..915586ed7 100644
--- a/src/main/java/com/simibubi/create/content/contraptions/relays/belt/transport/BeltInventory.java
+++ b/src/main/java/com/simibubi/create/content/contraptions/relays/belt/transport/BeltInventory.java
@@ -1,5 +1,7 @@
 package com.simibubi.create.content.contraptions.relays.belt.transport;
 
+import static com.simibubi.create.content.contraptions.relays.belt.transport.BeltTunnelInteractionHandler.flapTunnel;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Iterator;
@@ -8,32 +10,26 @@ import java.util.List;
 import java.util.function.Function;
 
 import com.simibubi.create.AllBlocks;
-import com.simibubi.create.content.contraptions.relays.belt.AllBeltAttachments.BeltAttachmentState;
 import com.simibubi.create.content.contraptions.relays.belt.BeltBlock;
 import com.simibubi.create.content.contraptions.relays.belt.BeltBlock.Slope;
 import com.simibubi.create.content.contraptions.relays.belt.BeltHelper;
 import com.simibubi.create.content.contraptions.relays.belt.BeltTileEntity;
-import com.simibubi.create.content.logistics.block.belts.tunnel.BeltTunnelBlock;
-import com.simibubi.create.content.logistics.block.belts.tunnel.BeltTunnelTileEntity;
+import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
+import com.simibubi.create.foundation.tileEntity.behaviour.belt.BeltProcessingBehaviour;
+import com.simibubi.create.foundation.tileEntity.behaviour.belt.BeltProcessingBehaviour.ProcessingResult;
+import com.simibubi.create.foundation.tileEntity.behaviour.belt.DirectBeltInputBehaviour;
 import com.simibubi.create.foundation.utility.ServerSpeedProvider;
 
 import net.minecraft.block.Block;
-import net.minecraft.block.BlockState;
 import net.minecraft.entity.item.ItemEntity;
 import net.minecraft.item.ItemStack;
 import net.minecraft.nbt.CompoundNBT;
 import net.minecraft.nbt.ListNBT;
-import net.minecraft.tileentity.TileEntity;
 import net.minecraft.util.Direction;
-import net.minecraft.util.Direction.Axis;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.util.math.Vec3d;
 import net.minecraft.world.World;
 import net.minecraftforge.common.util.Constants.NBT;
-import net.minecraftforge.common.util.LazyOptional;
-import net.minecraftforge.items.CapabilityItemHandler;
-import net.minecraftforge.items.IItemHandler;
-import net.minecraftforge.items.ItemHandlerHelper;
 
 public class BeltInventory {
 
@@ -52,9 +48,9 @@ public class BeltInventory {
 	public void tick() {
 
 		// Reverse item collection if belt just reversed
-		if (beltMovementPositive != movingPositive()) {
-			beltMovementPositive = movingPositive();
-			Collections.reverse(getItems());
+		if (beltMovementPositive != belt.getDirectionAwareBeltMovementSpeed() > 0) {
+			beltMovementPositive = !beltMovementPositive;
+			Collections.reverse(items);
 			belt.markDirty();
 			belt.sendData();
 		}
@@ -69,23 +65,31 @@ public class BeltInventory {
 
 		// Assuming the first entry is furthest on the belt
 		TransportedItemStack stackInFront = null;
-		TransportedItemStack current = null;
-		Iterator<TransportedItemStack> iterator = getItems().iterator();
+		TransportedItemStack currentItem = null;
+		Iterator<TransportedItemStack> iterator = items.iterator();
 
+		// Useful stuff
 		float beltSpeed = belt.getDirectionAwareBeltMovementSpeed();
 		Direction movementFacing = belt.getMovementFacing();
+		boolean horizontal = belt.getBlockState()
+			.get(BeltBlock.SLOPE) == Slope.HORIZONTAL;
 		float spacing = 1;
-		boolean onClient = belt.getWorld().isRemote;
+		World world = belt.getWorld();
+		boolean onClient = world.isRemote;
 
-		Items: while (iterator.hasNext()) {
-			stackInFront = current;
-			current = iterator.next();
-			current.prevBeltPosition = current.beltPosition;
-			current.prevSideOffset = current.sideOffset;
+		// resolve ending only when items will reach it this tick
+		Ending ending = Ending.UNRESOLVED;
 
-			if (current.stack.isEmpty()) {
+		// Loop over items
+		while (iterator.hasNext()) {
+			stackInFront = currentItem;
+			currentItem = iterator.next();
+			currentItem.prevBeltPosition = currentItem.beltPosition;
+			currentItem.prevSideOffset = currentItem.sideOffset;
+
+			if (currentItem.stack.isEmpty()) {
 				iterator.remove();
-				current = null;
+				currentItem = null;
 				continue;
 			}
 
@@ -93,12 +97,12 @@ public class BeltInventory {
 			if (onClient)
 				movement *= ServerSpeedProvider.get();
 
-			// Don't move if locked
-			if (onClient && current.locked)
+			// Don't move if held by processing (client)
+			if (onClient && currentItem.locked)
 				continue;
 
 			// Don't move if other items are waiting in front
-			float currentPos = current.beltPosition;
+			float currentPos = currentItem.beltPosition;
 			if (stackInFront != null) {
 				float diff = stackInFront.beltPosition - currentPos;
 				if (Math.abs(diff) <= spacing)
@@ -107,236 +111,182 @@ public class BeltInventory {
 					beltMovementPositive ? Math.min(movement, diff - spacing) : Math.max(movement, diff + spacing);
 			}
 
-			// Determine current segment
-			int segmentBefore = (int) currentPos;
-			float min = segmentBefore + .5f - (SEGMENT_WINDOW / 2);
-			float max = segmentBefore + .5f + (SEGMENT_WINDOW / 2);
-			if (currentPos < min || currentPos > max)
-				segmentBefore = -1;
-
 			// Don't move beyond the edge
 			float diffToEnd = beltMovementPositive ? belt.beltLength - currentPos : -currentPos;
+			if (Math.abs(diffToEnd) < Math.abs(movement) + 1) {
+				if (ending == Ending.UNRESOLVED)
+					ending = resolveEnding();
+				diffToEnd += beltMovementPositive ? -ending.margin : ending.margin;
+			}
 			float limitedMovement =
 				beltMovementPositive ? Math.min(movement, diffToEnd) : Math.max(movement, diffToEnd);
-			float nextOffset = current.beltPosition + limitedMovement;
+			float nextOffset = currentItem.beltPosition + limitedMovement;
 
-			if (!onClient && segmentBefore != -1) {
-				// Don't move if belt attachments want to continue processing
-				if (current.locked) {
-					BeltTileEntity beltSegment = BeltHelper.getBeltAtSegment(belt, segmentBefore);
-					if (beltSegment != null) {
-
-						// wait in case belt isnt initialized yet
-						if (current.locked && beltSegment.trackerUpdateTag != null)
-							continue;
-
-						current.locked = false;
-						List<BeltAttachmentState> attachments = beltSegment.attachmentTracker.attachments;
-						for (BeltAttachmentState attachmentState : attachments) {
-							if (attachmentState.attachment.processItem(beltSegment, current, attachmentState))
-								current.locked = true;
-						}
-						if (!current.locked || current.stack.isEmpty()) {
-							if (!attachments.isEmpty())
-								attachments.add(attachments.remove(0));
-							belt.sendData();
-						}
-						continue;
-					}
-				}
-
-				// See if any new belt processing catches the item
-				if (current.beltPosition > .5f || beltMovementPositive) {
-					int firstUpcomingSegment = (int) (current.beltPosition + (beltMovementPositive ? .5f : -.5f));
-					for (int segment = firstUpcomingSegment; beltMovementPositive ? segment + .5f <= nextOffset
-							: segment + .5f >= nextOffset; segment += beltMovementPositive ? 1 : -1) {
-						BeltTileEntity beltSegment = BeltHelper.getBeltAtSegment(belt, segment);
-						if (beltSegment == null)
-							break;
-						for (BeltAttachmentState attachmentState : beltSegment.attachmentTracker.attachments) {
-							ItemStack stackBefore = current.stack.copy();
-							if (attachmentState.attachment.startProcessingItem(beltSegment, current, attachmentState)) {
-								current.beltPosition = segment + .5f + (beltMovementPositive ? 1 / 64f : -1 / 64f);
-								current.locked = true;
-								belt.sendData();
-								continue Items;
-							}
-							if (!stackBefore.equals(current.stack, true))
-								belt.sendData();
-						}
-					}
+			// Belt item processing
+			if (!onClient && horizontal) {
+				ItemStack item = currentItem.stack;
+				if (handleBeltProcessingAndCheckIfRemoved(currentItem, nextOffset)) {
+					iterator.remove();
+					belt.sendData();
+					continue;
 				}
+				if (item != currentItem.stack)
+					belt.sendData();
+				if (currentItem.locked)
+					continue;
 			}
 
-			// Belt tunnels
-			{
-				int seg1 = (int) current.beltPosition;
-				int seg2 = (int) nextOffset;
-				if (!beltMovementPositive && nextOffset == 0)
-					seg2 = -1;
-				if (seg1 != seg2) {
-					if (stuckAtTunnel(seg2, current.stack, movementFacing)) {
-						continue;
-					}
-					if (!onClient) {
-						flapTunnel(seg1, movementFacing, false);
-						flapTunnel(seg2, movementFacing.getOpposite(), true);
-					}
-				}
-			}
+			// Belt Tunnels
+			BeltTunnelInteractionHandler.flapTunnelsAndCheckIfStuck(this, currentItem, nextOffset);
 
 			// Apply Movement
-			current.beltPosition += limitedMovement;
-			current.sideOffset += (current.getTargetSideOffset() - current.sideOffset) * Math.abs(limitedMovement) * 2f;
-			currentPos = current.beltPosition;
+			currentItem.beltPosition += limitedMovement;
+			currentItem.sideOffset +=
+				(currentItem.getTargetSideOffset() - currentItem.sideOffset) * Math.abs(limitedMovement) * 2f;
+			currentPos = currentItem.beltPosition;
 
-			// Determine segment after movement
-			int segmentAfter = (int) currentPos;
-			min = segmentAfter + .5f - (SEGMENT_WINDOW / 2);
-			max = segmentAfter + .5f + (SEGMENT_WINDOW / 2);
-			if (currentPos < min || currentPos > max)
-				segmentAfter = -1;
-
-			// Item changed segments
-			World world = belt.getWorld();
-			if (segmentBefore != segmentAfter) {
-				for (int segment : new int[] { segmentBefore, segmentAfter }) {
-					if (segment == -1)
-						continue;
-					if (!world.isRemote)
-						world
-								.updateComparatorOutputLevel(BeltHelper.getPositionForOffset(belt, segment),
-										belt.getBlockState().getBlock());
-				}
-			}
+			// Movement successful
+			if (limitedMovement == movement || onClient)
+				continue;
 
 			// End reached
-			if (limitedMovement != movement) {
-				if (world.isRemote)
-					continue;
-
-				int lastOffset = beltMovementPositive ? belt.beltLength - 1 : 0;
-				BlockPos nextPosition =
-					BeltHelper.getPositionForOffset(belt, beltMovementPositive ? belt.beltLength : -1);
-				BlockState state = world.getBlockState(nextPosition);
-
-				// next block is a basin or a saw
-				if (AllBlocks.BASIN.has(state) || AllBlocks.MECHANICAL_SAW.has(state)
-						|| AllBlocks.CRUSHING_WHEEL_CONTROLLER.has(state)) {
-					TileEntity te = world.getTileEntity(nextPosition);
-					if (te != null) {
-						LazyOptional<IItemHandler> optional =
-							te.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, Direction.UP);
-						if (optional.isPresent()) {
-							IItemHandler itemHandler = optional.orElse(null);
-							ItemStack remainder =
-								ItemHandlerHelper.insertItemStacked(itemHandler, current.stack.copy(), false);
-							if (remainder.equals(current.stack, false))
-								continue;
-
-							current.stack = remainder;
-							if (remainder.isEmpty()) {
-								iterator.remove();
-								current = null;
-								flapTunnel(lastOffset, movementFacing, false);
-							}
-
-							belt.sendData();
-						}
-					}
-					continue;
-				}
-
-				// next block is not a belt
-				if (!AllBlocks.BELT.has(state) || state.get(BeltBlock.SLOPE) == Slope.VERTICAL) {
-					if (!Block.hasSolidSide(state, world, nextPosition, movementFacing.getOpposite())) {
-						eject(current);
-						iterator.remove();
-						current = null;
-						flapTunnel(lastOffset, movementFacing, false);
-						belt.sendData();
-					}
-					continue;
-				}
-
-				// Next block is a belt
-				TileEntity te = world.getTileEntity(nextPosition);
-				if (te == null || !(te instanceof BeltTileEntity))
-					continue;
-				BeltTileEntity nextBelt = (BeltTileEntity) te;
-				Direction nextMovementFacing = nextBelt.getMovementFacing();
-
-				// next belt goes the opposite way
-				if (nextMovementFacing == movementFacing.getOpposite())
-					continue;
-
-				// Inserting into other belt
-				if (nextBelt.tryInsertingFromSide(movementFacing, current, false)) {
-					iterator.remove();
-					current = null;
-					flapTunnel(lastOffset, movementFacing, false);
-					belt.sendData();
-				}
+			int lastOffset = beltMovementPositive ? belt.beltLength - 1 : 0;
+			BlockPos nextPosition = BeltHelper.getPositionForOffset(belt, beltMovementPositive ? belt.beltLength : -1);
 
+			if (ending == Ending.FUNNEL) {
+				// TODO
+				continue;
 			}
 
-		}
+			if (ending == Ending.INSERT) {
+				DirectBeltInputBehaviour inputBehaviour =
+					TileEntityBehaviour.get(world, nextPosition, DirectBeltInputBehaviour.TYPE);
+				if (inputBehaviour == null)
+					continue;
+				if (!inputBehaviour.canInsertFromSide(movementFacing))
+					continue;
+				
+				ItemStack remainder = inputBehaviour.handleInsertion(currentItem, movementFacing, false);
+				if (remainder.equals(currentItem.stack, false))
+					continue;
 
+				currentItem.stack = remainder;
+				if (remainder.isEmpty())
+					iterator.remove();
+
+				flapTunnel(this, lastOffset, movementFacing, false);
+				belt.sendData();
+				continue;
+			}
+
+			if (ending == Ending.BLOCKED)
+				continue;
+
+			if (ending == Ending.EJECT) {
+				eject(currentItem);
+				iterator.remove();
+				flapTunnel(this, lastOffset, movementFacing, false);
+				belt.sendData();
+				continue;
+			}
+		}
 	}
 
-	private boolean stuckAtTunnel(int offset, ItemStack stack, Direction movementDirection) {
-		BlockPos pos = BeltHelper.getPositionForOffset(belt, offset).up();
-		if (!AllBlocks.BELT_TUNNEL.has(belt.getWorld().getBlockState(pos)))
-			return false;
-		TileEntity te = belt.getWorld().getTileEntity(pos);
-		if (te == null || !(te instanceof BeltTunnelTileEntity))
-			return false;
+	protected boolean handleBeltProcessingAndCheckIfRemoved(TransportedItemStack currentItem, float nextOffset) {
+		int currentSegment = (int) currentItem.beltPosition;
 
-		Direction flapFacing = movementDirection.getOpposite();
+		// Continue processing if held
+		if (currentItem.locked) {
+			BeltProcessingBehaviour processingBehaviour = getBeltProcessingAtSegment(currentSegment);
 
-		BeltTunnelTileEntity tunnel = (BeltTunnelTileEntity) te;
-		if (!tunnel.flaps.containsKey(flapFacing))
-			return false;
-		if (!tunnel.syncedFlaps.containsKey(flapFacing))
-			return false;
-		ItemStack heldItem = tunnel.syncedFlaps.get(flapFacing);
-		if (heldItem == null) {
-			tunnel.syncedFlaps.put(flapFacing, ItemStack.EMPTY);
+			if (processingBehaviour == null) {
+				currentItem.locked = false;
+				belt.sendData();
+				return false;
+			}
+
+			ProcessingResult result = processingBehaviour.handleHeldItem(currentItem, this);
+			if (result == ProcessingResult.REMOVE)
+				return true;
+			if (result == ProcessingResult.HOLD)
+				return false;
+
+			currentItem.locked = false;
 			belt.sendData();
 			return false;
 		}
-		if (heldItem == ItemStack.EMPTY) {
-			tunnel.syncedFlaps.put(flapFacing, stack);
-			return true;
+
+		// See if any new belt processing catches the item
+		if (currentItem.beltPosition > .5f || beltMovementPositive) {
+			int firstUpcomingSegment = (int) (currentItem.beltPosition + (beltMovementPositive ? .5f : -.5f));
+			int step = beltMovementPositive ? 1 : -1;
+
+			for (int segment = firstUpcomingSegment; beltMovementPositive ? segment + .5f <= nextOffset
+				: segment + .5f >= nextOffset; segment += step) {
+
+				BeltProcessingBehaviour processingBehaviour = getBeltProcessingAtSegment(segment);
+				if (processingBehaviour == null)
+					continue;
+
+				ProcessingResult result = processingBehaviour.handleReceivedItem(currentItem, this);
+				if (result == ProcessingResult.REMOVE)
+					return true;
+
+				if (result == ProcessingResult.HOLD) {
+					currentItem.beltPosition = segment + .5f + (beltMovementPositive ? 1 / 64f : -1 / 64f);
+					currentItem.locked = true;
+					belt.sendData();
+					return false;
+				}
+			}
 		}
 
-		List<BeltTunnelTileEntity> group = BeltTunnelBlock.getSynchronizedGroup(belt.getWorld(), pos, flapFacing);
-		for (BeltTunnelTileEntity otherTunnel : group)
-			if (otherTunnel.syncedFlaps.get(flapFacing) == ItemStack.EMPTY)
-				return true;
-		for (BeltTunnelTileEntity otherTunnel : group)
-			otherTunnel.syncedFlaps.put(flapFacing, null);
-
-		return true;
+		return false;
 	}
 
-	private void flapTunnel(int offset, Direction side, boolean inward) {
-		if (belt.getBlockState().get(BeltBlock.SLOPE) != Slope.HORIZONTAL)
-			return;
-		BlockPos pos = BeltHelper.getPositionForOffset(belt, offset).up();
-		if (!AllBlocks.BELT_TUNNEL.has(belt.getWorld().getBlockState(pos)))
-			return;
-		TileEntity te = belt.getWorld().getTileEntity(pos);
-		if (te == null || !(te instanceof BeltTunnelTileEntity))
-			return;
-		((BeltTunnelTileEntity) te).flap(side, inward ^ side.getAxis() == Axis.Z);
+	protected BeltProcessingBehaviour getBeltProcessingAtSegment(int segment) {
+		return TileEntityBehaviour.get(belt.getWorld(), BeltHelper.getPositionForOffset(belt, segment)
+			.up(2), BeltProcessingBehaviour.TYPE);
 	}
 
+	private enum Ending {
+		UNRESOLVED(0), EJECT(0), INSERT(.25f), FUNNEL(.35f), BLOCKED(.45f);
+
+		private float margin;
+
+		Ending(float f) {
+			this.margin = f;
+		}
+	}
+
+	private Ending resolveEnding() {
+		int lastOffset = beltMovementPositive ? belt.beltLength - 1 : 0;
+		World world = belt.getWorld();
+		BlockPos lastPosition = BeltHelper.getPositionForOffset(belt, lastOffset);
+		BlockPos nextPosition = BeltHelper.getPositionForOffset(belt, beltMovementPositive ? belt.beltLength : -1);
+
+		if (AllBlocks.BELT_FUNNEL.has(world.getBlockState(lastPosition.up())))
+			return Ending.FUNNEL;
+
+		DirectBeltInputBehaviour inputBehaviour =
+			TileEntityBehaviour.get(world, nextPosition, DirectBeltInputBehaviour.TYPE);
+		if (inputBehaviour != null)
+			return Ending.INSERT;
+
+		if (Block.hasSolidSide(world.getBlockState(nextPosition), world, nextPosition, belt.getMovementFacing()
+			.getOpposite()))
+			return Ending.BLOCKED;
+
+		return Ending.EJECT;
+	}
+
+	//
+
 	public boolean canInsertAt(int segment) {
-		return canInsertFrom(segment, Direction.UP);
+		return canInsertAtFromSide(segment, Direction.UP);
 	}
 
-	public boolean canInsertFrom(int segment, Direction side) {
+	public boolean canInsertAtFromSide(int segment, Direction side) {
 		float segmentPos = segment;
 		if (belt.getMovementFacing() == side.getOpposite())
 			return false;
@@ -345,7 +295,7 @@ public class BeltInventory {
 		else if (!beltMovementPositive)
 			segmentPos += 1f;
 
-		for (TransportedItemStack stack : getItems())
+		for (TransportedItemStack stack : items)
 			if (isBlocking(segment, side, segmentPos, stack))
 				return false;
 		for (TransportedItemStack stack : toInsert)
@@ -358,7 +308,7 @@ public class BeltInventory {
 	private boolean isBlocking(int segment, Direction side, float segmentPos, TransportedItemStack stack) {
 		float currentPos = stack.beltPosition;
 		if (stack.insertedAt == segment && stack.insertedFrom == side
-				&& (beltMovementPositive ? currentPos <= segmentPos + 1 : currentPos >= segmentPos - 1))
+			&& (beltMovementPositive ? currentPos <= segmentPos + 1 : currentPos >= segmentPos - 1))
 			return true;
 		return false;
 	}
@@ -368,23 +318,23 @@ public class BeltInventory {
 	}
 
 	private void insert(TransportedItemStack newStack) {
-		if (getItems().isEmpty())
-			getItems().add(newStack);
+		if (items.isEmpty())
+			items.add(newStack);
 		else {
 			int index = 0;
-			for (TransportedItemStack stack : getItems()) {
+			for (TransportedItemStack stack : items) {
 				if (stack.compareTo(newStack) > 0 == beltMovementPositive)
 					break;
 				index++;
 			}
-			getItems().add(index, newStack);
+			items.add(index, newStack);
 		}
 	}
 
 	public TransportedItemStack getStackAtOffset(int offset) {
 		float min = offset + .5f - (SEGMENT_WINDOW / 2);
 		float max = offset + .5f + (SEGMENT_WINDOW / 2);
-		for (TransportedItemStack stack : getItems()) {
+		for (TransportedItemStack stack : items) {
 			if (stack.beltPosition > max)
 				continue;
 			if (stack.beltPosition > min)
@@ -394,17 +344,16 @@ public class BeltInventory {
 	}
 
 	public void read(CompoundNBT nbt) {
-		getItems().clear();
-		nbt
-				.getList("Items", NBT.TAG_COMPOUND)
-				.forEach(inbt -> getItems().add(TransportedItemStack.read((CompoundNBT) inbt)));
+		items.clear();
+		nbt.getList("Items", NBT.TAG_COMPOUND)
+			.forEach(inbt -> items.add(TransportedItemStack.read((CompoundNBT) inbt)));
 		beltMovementPositive = nbt.getBoolean("PositiveOrder");
 	}
 
 	public CompoundNBT write() {
 		CompoundNBT nbt = new CompoundNBT();
 		ListNBT itemsNBT = new ListNBT();
-		getItems().forEach(stack -> itemsNBT.add(stack.serializeNBT()));
+		items.forEach(stack -> itemsNBT.add(stack.serializeNBT()));
 		nbt.put("Items", itemsNBT);
 		nbt.putBoolean("PositiveOrder", beltMovementPositive);
 		return nbt;
@@ -414,33 +363,27 @@ public class BeltInventory {
 		ItemStack ejected = stack.stack;
 		Vec3d outPos = BeltHelper.getVectorForOffset(belt, stack.beltPosition);
 		float movementSpeed = Math.max(Math.abs(belt.getBeltMovementSpeed()), 1 / 8f);
-		Vec3d outMotion = new Vec3d(belt.getBeltChainDirection()).scale(movementSpeed).add(0, 1 / 8f, 0);
+		Vec3d outMotion = new Vec3d(belt.getBeltChainDirection()).scale(movementSpeed)
+			.add(0, 1 / 8f, 0);
 		outPos.add(outMotion.normalize());
 		ItemEntity entity = new ItemEntity(belt.getWorld(), outPos.x, outPos.y + 6 / 16f, outPos.z, ejected);
 		entity.setMotion(outMotion);
 		entity.setDefaultPickupDelay();
 		entity.velocityChanged = true;
-		belt.getWorld().addEntity(entity);
+		belt.getWorld()
+			.addEntity(entity);
 	}
-
+	
 	public void ejectAll() {
-		getItems().forEach(this::eject);
-		getItems().clear();
-	}
-
-	private boolean movingPositive() {
-		return belt.getDirectionAwareBeltMovementSpeed() > 0;
-	}
-
-	public IItemHandler createHandlerForSegment(int segment) {
-		return new ItemHandlerBeltSegment(this, segment);
+		items.forEach(this::eject);
+		items.clear();
 	}
 
 	public void forEachWithin(float position, float distance,
-			Function<TransportedItemStack, List<TransportedItemStack>> callback) {
+		Function<TransportedItemStack, List<TransportedItemStack>> callback) {
 		List<TransportedItemStack> toBeAdded = new ArrayList<>();
 		boolean dirty = false;
-		for (Iterator<TransportedItemStack> iterator = getItems().iterator(); iterator.hasNext();) {
+		for (Iterator<TransportedItemStack> iterator = items.iterator(); iterator.hasNext();) {
 			TransportedItemStack transportedItemStack = iterator.next();
 			if (Math.abs(position - transportedItemStack.beltPosition) < distance) {
 				List<TransportedItemStack> apply = callback.apply(transportedItemStack);
@@ -458,7 +401,7 @@ public class BeltInventory {
 		}
 	}
 
-	public List<TransportedItemStack> getItems() {
+	public List<TransportedItemStack> getTransportedItems() {
 		return items;
 	}
 
diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/belt/transport/BeltMovementHandler.java b/src/main/java/com/simibubi/create/content/contraptions/relays/belt/transport/BeltMovementHandler.java
index e93ab65b9..e41aa44ee 100644
--- a/src/main/java/com/simibubi/create/content/contraptions/relays/belt/transport/BeltMovementHandler.java
+++ b/src/main/java/com/simibubi/create/content/contraptions/relays/belt/transport/BeltMovementHandler.java
@@ -8,7 +8,6 @@ import java.util.List;
 
 import com.simibubi.create.AllBlocks;
 import com.simibubi.create.content.contraptions.components.structureMovement.ContraptionEntity;
-import com.simibubi.create.content.contraptions.relays.belt.AllBeltAttachments.BeltAttachmentState;
 import com.simibubi.create.content.contraptions.relays.belt.BeltBlock;
 import com.simibubi.create.content.contraptions.relays.belt.BeltBlock.Part;
 import com.simibubi.create.content.contraptions.relays.belt.BeltBlock.Slope;
@@ -99,16 +98,6 @@ public class BeltMovementHandler {
 			((LivingEntity) entityIn).addPotionEffect(new EffectInstance(Effects.SLOWNESS, 10, 1, false, false));
 		}
 
-		BeltTileEntity belt = (BeltTileEntity) te;
-
-		// Attachment pauses movement
-		for (BeltAttachmentState state : belt.attachmentTracker.attachments) {
-			if (state.attachment.processEntity(belt, entityIn, state)) {
-				info.ticksSinceLastCollision--;
-				return;
-			}
-		}
-
 		final Direction beltFacing = blockState.get(BlockStateProperties.HORIZONTAL_FACING);
 		final Slope slope = blockState.get(BeltBlock.SLOPE);
 		final Axis axis = beltFacing.getAxis();
diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/belt/transport/BeltTunnelInteractionHandler.java b/src/main/java/com/simibubi/create/content/contraptions/relays/belt/transport/BeltTunnelInteractionHandler.java
new file mode 100644
index 000000000..7f7d69a75
--- /dev/null
+++ b/src/main/java/com/simibubi/create/content/contraptions/relays/belt/transport/BeltTunnelInteractionHandler.java
@@ -0,0 +1,100 @@
+package com.simibubi.create.content.contraptions.relays.belt.transport;
+
+import java.util.List;
+
+import com.simibubi.create.AllBlocks;
+import com.simibubi.create.content.contraptions.relays.belt.BeltBlock;
+import com.simibubi.create.content.contraptions.relays.belt.BeltBlock.Slope;
+import com.simibubi.create.content.contraptions.relays.belt.BeltHelper;
+import com.simibubi.create.content.contraptions.relays.belt.BeltTileEntity;
+import com.simibubi.create.content.logistics.block.belts.tunnel.BeltTunnelBlock;
+import com.simibubi.create.content.logistics.block.belts.tunnel.BeltTunnelTileEntity;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.util.Direction;
+import net.minecraft.util.Direction.Axis;
+import net.minecraft.util.math.BlockPos;
+
+public class BeltTunnelInteractionHandler {
+
+	public static boolean flapTunnelsAndCheckIfStuck(BeltInventory beltInventory, TransportedItemStack current,
+		float nextOffset) {
+
+		int currentSegment = (int) current.beltPosition;
+		int upcomingSegment = (int) nextOffset;
+
+		Direction movementFacing = beltInventory.belt.getMovementFacing();
+		if (!beltInventory.beltMovementPositive && nextOffset == 0)
+			upcomingSegment = -1;
+		if (currentSegment != upcomingSegment) {
+			if (stuckAtTunnel(beltInventory, upcomingSegment, current.stack, movementFacing))
+				return true;
+			if (!beltInventory.belt.getWorld().isRemote) {
+				flapTunnel(beltInventory, currentSegment, movementFacing, false);
+				flapTunnel(beltInventory, upcomingSegment, movementFacing.getOpposite(), true);
+			}
+		}
+
+		return false;
+	}
+
+	public static boolean stuckAtTunnel(BeltInventory beltInventory, int offset, ItemStack stack,
+		Direction movementDirection) {
+		BeltTileEntity belt = beltInventory.belt;
+		BlockPos pos = BeltHelper.getPositionForOffset(belt, offset)
+			.up();
+		if (!AllBlocks.BELT_TUNNEL.has(belt.getWorld()
+			.getBlockState(pos)))
+			return false;
+		TileEntity te = belt.getWorld()
+			.getTileEntity(pos);
+		if (te == null || !(te instanceof BeltTunnelTileEntity))
+			return false;
+
+		Direction flapFacing = movementDirection.getOpposite();
+
+		BeltTunnelTileEntity tunnel = (BeltTunnelTileEntity) te;
+		if (!tunnel.flaps.containsKey(flapFacing))
+			return false;
+		if (!tunnel.syncedFlaps.containsKey(flapFacing))
+			return false;
+		ItemStack heldItem = tunnel.syncedFlaps.get(flapFacing);
+		if (heldItem == null) {
+			tunnel.syncedFlaps.put(flapFacing, ItemStack.EMPTY);
+			belt.sendData();
+			return false;
+		}
+		if (heldItem == ItemStack.EMPTY) {
+			tunnel.syncedFlaps.put(flapFacing, stack);
+			return true;
+		}
+
+		List<BeltTunnelTileEntity> group = BeltTunnelBlock.getSynchronizedGroup(belt.getWorld(), pos, flapFacing);
+		for (BeltTunnelTileEntity otherTunnel : group)
+			if (otherTunnel.syncedFlaps.get(flapFacing) == ItemStack.EMPTY)
+				return true;
+		for (BeltTunnelTileEntity otherTunnel : group)
+			otherTunnel.syncedFlaps.put(flapFacing, null);
+
+		return true;
+	}
+
+	public static void flapTunnel(BeltInventory beltInventory, int offset, Direction side, boolean inward) {
+		BeltTileEntity belt = beltInventory.belt;
+		if (belt.getBlockState()
+			.get(BeltBlock.SLOPE) != Slope.HORIZONTAL)
+			return;
+		BlockPos pos = BeltHelper.getPositionForOffset(belt, offset)
+			.up();
+		if (!AllBlocks.BELT_TUNNEL.has(belt.getWorld()
+			.getBlockState(pos)))
+			return;
+		TileEntity te = belt.getWorld()
+			.getTileEntity(pos);
+		if (te == null || !(te instanceof BeltTunnelTileEntity))
+			return;
+		((BeltTunnelTileEntity) te).flap(side, inward ^ side.getAxis() == Axis.Z);
+	}
+
+}
diff --git a/src/main/java/com/simibubi/create/content/logistics/block/belts/BeltAttachableLogisticalBlock.java b/src/main/java/com/simibubi/create/content/logistics/block/belts/BeltAttachableLogisticalBlock.java
index 314cf7c67..7f5da2547 100644
--- a/src/main/java/com/simibubi/create/content/logistics/block/belts/BeltAttachableLogisticalBlock.java
+++ b/src/main/java/com/simibubi/create/content/logistics/block/belts/BeltAttachableLogisticalBlock.java
@@ -1,93 +1,25 @@
 package com.simibubi.create.content.logistics.block.belts;
 
-import java.util.Arrays;
-import java.util.List;
-import java.util.stream.Collectors;
-
-import com.simibubi.create.content.contraptions.relays.belt.AllBeltAttachments.BeltAttachmentState;
-import com.simibubi.create.content.contraptions.relays.belt.AllBeltAttachments.IBeltAttachment;
-import com.simibubi.create.content.contraptions.relays.belt.BeltTileEntity;
-import com.simibubi.create.content.contraptions.relays.belt.transport.TransportedItemStack;
 import com.simibubi.create.content.logistics.block.AttachedLogisticalBlock;
 import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
 import com.simibubi.create.foundation.tileEntity.behaviour.filtering.FilteringBehaviour;
-import com.simibubi.create.foundation.tileEntity.behaviour.inventory.SingleTargetAutoExtractingBehaviour;
 
 import net.minecraft.block.BlockState;
-import net.minecraft.item.ItemStack;
-import net.minecraft.util.Direction;
 import net.minecraft.util.math.BlockPos;
-import net.minecraft.world.IWorld;
 import net.minecraft.world.World;
 
-public abstract class BeltAttachableLogisticalBlock extends AttachedLogisticalBlock implements IBeltAttachment {
+public abstract class BeltAttachableLogisticalBlock extends AttachedLogisticalBlock {
 
 	public BeltAttachableLogisticalBlock(Properties properties) {
 		super(properties);
 	}
 	
-	@Override
-	public void onBlockAdded(BlockState state, World worldIn, BlockPos pos, BlockState oldState, boolean isMoving) {
-		onAttachmentPlaced(worldIn, pos, state);
-	}
-
 	@Override
 	public void onReplaced(BlockState state, World worldIn, BlockPos pos, BlockState newState, boolean isMoving) {
-		onAttachmentRemoved(worldIn, pos, state);
 		if (state.hasTileEntity() && state.getBlock() != newState.getBlock()) {
 			TileEntityBehaviour.destroy(worldIn, pos, FilteringBehaviour.TYPE);
 			worldIn.removeTileEntity(pos);
 		}
 	}
 
-	@Override
-	public BlockPos getBeltPositionForAttachment(IWorld world, BlockPos pos, BlockState state) {
-		return pos.offset(getBlockFacing(state));
-	}
-
-	@Override
-	public List<BlockPos> getPotentialAttachmentPositions(IWorld world, BlockPos pos, BlockState beltState) {
-		return Arrays.asList(Direction.values()).stream().filter(d -> d != Direction.UP).map(pos::offset)
-				.collect(Collectors.toList());
-	}
-
-	public boolean startProcessingItem(BeltTileEntity te, TransportedItemStack transported, BeltAttachmentState state) {
-		BlockPos pos = state.attachmentPos;
-		World world = te.getWorld();
-		ItemStack stack = transported.stack;
-
-		FilteringBehaviour filtering = TileEntityBehaviour.get(world, pos, FilteringBehaviour.TYPE);
-		SingleTargetAutoExtractingBehaviour extracting = TileEntityBehaviour.get(world, pos,
-				SingleTargetAutoExtractingBehaviour.TYPE);
-
-		if (extracting == null)
-			return false;
-		if (filtering != null && (!filtering.test(stack) || stack.getCount() < filtering.getAmount()))
-			return false;
-
-		return true;
-	}
-
-	public boolean processItem(BeltTileEntity te, TransportedItemStack transported, BeltAttachmentState state) {
-		BlockPos pos = state.attachmentPos;
-		World world = te.getWorld();
-		ItemStack stack = transported.stack;
-
-		SingleTargetAutoExtractingBehaviour extracting = TileEntityBehaviour.get(world, pos,
-				SingleTargetAutoExtractingBehaviour.TYPE);
-
-		if (extracting == null)
-			return false;
-		if (extracting.getShouldPause().get())
-			return false;
-
-		FilteringBehaviour filtering = TileEntityBehaviour.get(world, pos, FilteringBehaviour.TYPE);
-		if (filtering != null && (!filtering.test(stack) || stack.getCount() < filtering.getAmount()))
-			return false;
-		if (!extracting.getShouldExtract().get())
-			return true;
-
-		return !extracting.extractFromInventory();
-	}
-
 }
diff --git a/src/main/java/com/simibubi/create/content/logistics/block/belts/observer/BeltObserverBlock.java b/src/main/java/com/simibubi/create/content/logistics/block/belts/observer/BeltObserverBlock.java
index bbf38ec79..3ca73f773 100644
--- a/src/main/java/com/simibubi/create/content/logistics/block/belts/observer/BeltObserverBlock.java
+++ b/src/main/java/com/simibubi/create/content/logistics/block/belts/observer/BeltObserverBlock.java
@@ -1,33 +1,21 @@
 package com.simibubi.create.content.logistics.block.belts.observer;
 
-import java.util.Arrays;
-import java.util.List;
-import java.util.Random;
-
 import com.simibubi.create.AllBlocks;
 import com.simibubi.create.AllTileEntities;
-import com.simibubi.create.content.contraptions.relays.belt.AllBeltAttachments.BeltAttachmentState;
-import com.simibubi.create.content.contraptions.relays.belt.AllBeltAttachments.IBeltAttachment;
 import com.simibubi.create.content.contraptions.relays.belt.BeltBlock;
 import com.simibubi.create.content.contraptions.relays.belt.BeltBlock.Part;
 import com.simibubi.create.content.contraptions.relays.belt.BeltBlock.Slope;
-import com.simibubi.create.content.contraptions.relays.belt.BeltTileEntity;
-import com.simibubi.create.content.contraptions.relays.belt.transport.TransportedItemStack;
 import com.simibubi.create.content.contraptions.wrench.IWrenchable;
 import com.simibubi.create.foundation.block.ITE;
 import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
 import com.simibubi.create.foundation.tileEntity.behaviour.filtering.FilteringBehaviour;
 import com.simibubi.create.foundation.utility.Lang;
-import com.simibubi.create.foundation.utility.VecHelper;
 
 import net.minecraft.block.Block;
 import net.minecraft.block.BlockState;
 import net.minecraft.block.HorizontalBlock;
 import net.minecraft.block.material.PushReaction;
-import net.minecraft.entity.Entity;
-import net.minecraft.entity.item.ItemEntity;
 import net.minecraft.item.BlockItemUseContext;
-import net.minecraft.item.ItemStack;
 import net.minecraft.item.ItemUseContext;
 import net.minecraft.state.BooleanProperty;
 import net.minecraft.state.EnumProperty;
@@ -37,18 +25,13 @@ import net.minecraft.tileentity.TileEntity;
 import net.minecraft.util.ActionResultType;
 import net.minecraft.util.Direction;
 import net.minecraft.util.IStringSerializable;
-import net.minecraft.util.SoundCategory;
-import net.minecraft.util.SoundEvents;
-import net.minecraft.util.math.AxisAlignedBB;
 import net.minecraft.util.math.BlockPos;
-import net.minecraft.util.math.Vec3d;
 import net.minecraft.world.IBlockReader;
 import net.minecraft.world.IWorld;
 import net.minecraft.world.World;
-import net.minecraft.world.server.ServerWorld;
 
 public class BeltObserverBlock extends HorizontalBlock
-		implements ITE<BeltObserverTileEntity>, IBeltAttachment, IWrenchable {
+		implements ITE<BeltObserverTileEntity>, IWrenchable {
 
 	public static final BooleanProperty POWERED = BlockStateProperties.POWERED;
 	public static final BooleanProperty BELT = BooleanProperty.create("belt");
@@ -138,23 +121,6 @@ public class BeltObserverBlock extends HorizontalBlock
 		return state;
 	}
 
-	@Override
-	public List<BlockPos> getPotentialAttachmentPositions(IWorld world, BlockPos pos, BlockState beltState) {
-		Direction side = beltState.get(BeltBlock.HORIZONTAL_FACING).rotateY();
-		return Arrays.asList(pos.offset(side), pos.offset(side.getOpposite()));
-	}
-
-	@Override
-	public BlockPos getBeltPositionForAttachment(IWorld world, BlockPos pos, BlockState state) {
-		return pos.offset(state.get(HORIZONTAL_FACING));
-	}
-
-	@Override
-	public boolean isAttachedCorrectly(IWorld world, BlockPos attachmentPos, BlockPos beltPos,
-			BlockState attachmentState, BlockState beltState) {
-		return attachmentState.get(BELT);
-	}
-
 	@Override
 	public boolean canProvidePower(BlockState state) {
 		return state.get(POWERED);
@@ -172,118 +138,12 @@ public class BeltObserverBlock extends HorizontalBlock
 
 	@Override
 	public void onReplaced(BlockState state, World worldIn, BlockPos pos, BlockState newState, boolean isMoving) {
-		if (newState.getBlock() != this || newState.with(POWERED, false) != state.with(POWERED, false))
-			onAttachmentRemoved(worldIn, pos, state);
 		if (state.hasTileEntity() && state.getBlock() != newState.getBlock()) {
 			TileEntityBehaviour.destroy(worldIn, pos, FilteringBehaviour.TYPE);
 			worldIn.removeTileEntity(pos);
 		}
 	}
 
-	@Override
-	public void onBlockAdded(BlockState state, World worldIn, BlockPos pos, BlockState oldState, boolean isMoving) {
-		if (oldState.getBlock() != this || oldState.with(POWERED, false) != state.with(POWERED, false))
-			onAttachmentPlaced(worldIn, pos, state);
-	}
-
-	@Override
-	public boolean startProcessingItem(BeltTileEntity te, TransportedItemStack transported, BeltAttachmentState state) {
-		World world = te.getWorld();
-		BlockState blockState = world.getBlockState(state.attachmentPos);
-		if (blockState.get(MODE) == Mode.DETECT)
-			return false;
-
-		FilteringBehaviour behaviour =
-			TileEntityBehaviour.get(te.getWorld(), state.attachmentPos, FilteringBehaviour.TYPE);
-		if (behaviour != null && !behaviour.test(transported.stack))
-			return false;
-
-		world.setBlockState(state.attachmentPos, blockState.with(POWERED, true));
-		world.notifyNeighborsOfStateChange(state.attachmentPos, this);
-		withTileEntityDo(world, state.attachmentPos, BeltObserverTileEntity::resetTurnOffCooldown);
-
-		Mode mode = blockState.get(MODE);
-		if (mode == Mode.EJECT || mode == Mode.SPLIT) {
-			ItemStack copy = transported.stack.copy();
-			ItemStack toEject = mode == Mode.EJECT ? transported.stack : copy.split(transported.stack.getCount() / 2);
-
-			if (!toEject.isEmpty()) {
-				if (!eject(world, toEject, state.attachmentPos, blockState.get(HORIZONTAL_FACING)))
-					return true;
-				transported.stack = mode == Mode.EJECT ? ItemStack.EMPTY : copy;
-			}
-		}
-
-		return false;
-	}
-
-	@Override
-	public boolean processItem(BeltTileEntity te, TransportedItemStack transported, BeltAttachmentState state) {
-		World world = te.getWorld();
-		BlockState blockState = world.getBlockState(state.attachmentPos);
-		withTileEntityDo(world, state.attachmentPos, BeltObserverTileEntity::resetTurnOffCooldown);
-
-		Mode mode = blockState.get(MODE);
-		if (mode == Mode.EJECT || mode == Mode.SPLIT) {
-			ItemStack copy = transported.stack.copy();
-			ItemStack toEject = mode == Mode.EJECT ? transported.stack : copy.split(transported.stack.getCount() / 2);
-
-			if (!eject(world, toEject, state.attachmentPos, blockState.get(HORIZONTAL_FACING)))
-				return true;
-			transported.stack = mode == Mode.EJECT ? ItemStack.EMPTY : copy;
-		}
-
-		return false;
-	}
-
-	private boolean eject(World world, ItemStack stack, BlockPos observerPos, Direction facing) {
-		BlockPos potentialBeltPos = observerPos.offset(facing, 2);
-		TileEntity tileEntity = world.getTileEntity(potentialBeltPos);
-		if (tileEntity instanceof BeltTileEntity) {
-			BeltTileEntity belt = (BeltTileEntity) tileEntity;
-			return belt.tryInsertingFromSide(facing, stack, false);
-		}
-
-		boolean empty = world.getBlockState(potentialBeltPos).getCollisionShape(world, potentialBeltPos).isEmpty();
-		float yOffset = empty ? 0 : .5f;
-		AxisAlignedBB bb = new AxisAlignedBB(empty ? potentialBeltPos : potentialBeltPos.up());
-		if (!world.getEntitiesWithinAABBExcludingEntity(null, bb).isEmpty())
-			return false;
-
-		Vec3d motion = new Vec3d(facing.getDirectionVec()).scale(1 / 16f);
-		Vec3d entityPos = VecHelper.getCenterOf(potentialBeltPos).add(0, yOffset + .25f, 0).subtract(motion);
-		ItemEntity entity = new ItemEntity(world, entityPos.x, entityPos.y, entityPos.z, stack);
-		entity.setMotion(motion);
-		entity.setPickupDelay(5);
-		world.playSound(null, observerPos, SoundEvents.ENTITY_ITEM_PICKUP, SoundCategory.BLOCKS, .125f, .1f);
-		world.addEntity(entity);
-		return true;
-	}
-
-	@Override
-	public boolean processEntity(BeltTileEntity te, Entity entity, BeltAttachmentState state) {
-		if (te.getWorld().isRemote)
-			return false;
-		if (entity.getPositionVec().distanceTo(VecHelper.getCenterOf(te.getPos())) > .5f)
-			return false;
-
-		World world = te.getWorld();
-		BlockState blockState = world.getBlockState(state.attachmentPos);
-		if (blockState.get(POWERED))
-			return false;
-
-		world.setBlockState(state.attachmentPos, blockState.with(POWERED, true));
-		world.notifyNeighborsOfStateChange(state.attachmentPos, this);
-		withTileEntityDo(te.getWorld(), state.attachmentPos, BeltObserverTileEntity::resetTurnOffCooldown);
-		return false;
-	}
-
-	@Override
-	public void scheduledTick(BlockState state, ServerWorld worldIn, BlockPos pos, Random random) {
-		worldIn.setBlockState(pos, state.with(POWERED, false), 2);
-		worldIn.notifyNeighborsOfStateChange(pos, this);
-	}
-
 	@Override
 	public ActionResultType onWrenched(BlockState state, ItemUseContext context) {
 		World world = context.getWorld();
diff --git a/src/main/java/com/simibubi/create/content/logistics/block/extractor/ExtractorTileEntity.java b/src/main/java/com/simibubi/create/content/logistics/block/extractor/ExtractorTileEntity.java
index 3ba3e231d..2cdb2e2b6 100644
--- a/src/main/java/com/simibubi/create/content/logistics/block/extractor/ExtractorTileEntity.java
+++ b/src/main/java/com/simibubi/create/content/logistics/block/extractor/ExtractorTileEntity.java
@@ -106,7 +106,7 @@ public class ExtractorTileEntity extends SmartTileEntity {
 					BeltInventory inventory = controller.getInventory();
 					if (inventory == null)
 						return false;
-					if (!inventory.canInsertFrom(belt.index, Direction.UP))
+					if (!inventory.canInsertAtFromSide(belt.index, Direction.UP))
 						return false;
 				}
 			}
diff --git a/src/main/java/com/simibubi/create/content/logistics/block/funnel/FunnelBlock.java b/src/main/java/com/simibubi/create/content/logistics/block/funnel/FunnelBlock.java
index 62e929c6e..e6088f5b9 100644
--- a/src/main/java/com/simibubi/create/content/logistics/block/funnel/FunnelBlock.java
+++ b/src/main/java/com/simibubi/create/content/logistics/block/funnel/FunnelBlock.java
@@ -1,19 +1,14 @@
 package com.simibubi.create.content.logistics.block.funnel;
 
-import java.util.Arrays;
 import java.util.Collections;
-import java.util.List;
 
 import com.simibubi.create.AllBlocks;
 import com.simibubi.create.AllShapes;
 import com.simibubi.create.AllTileEntities;
 import com.simibubi.create.content.contraptions.components.structureMovement.IPortableBlock;
 import com.simibubi.create.content.contraptions.components.structureMovement.MovementBehaviour;
-import com.simibubi.create.content.contraptions.relays.belt.AllBeltAttachments.BeltAttachmentState;
-import com.simibubi.create.content.contraptions.relays.belt.AllBeltAttachments.IBeltAttachment;
 import com.simibubi.create.content.contraptions.relays.belt.BeltHelper;
 import com.simibubi.create.content.contraptions.relays.belt.BeltTileEntity;
-import com.simibubi.create.content.contraptions.relays.belt.transport.TransportedItemStack;
 import com.simibubi.create.content.logistics.block.AttachedLogisticalBlock;
 import com.simibubi.create.foundation.block.ITE;
 import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
@@ -41,7 +36,7 @@ import net.minecraft.world.IWorld;
 import net.minecraft.world.World;
 
 public class FunnelBlock extends AttachedLogisticalBlock
-		implements IBeltAttachment, ITE<FunnelTileEntity>, IPortableBlock {
+		implements ITE<FunnelTileEntity>, IPortableBlock {
 
 	public static final BooleanProperty BELT = BooleanProperty.create("belt");
 	public static final MovementBehaviour MOVEMENT = new FunnelMovementBehaviour();
@@ -131,7 +126,6 @@ public class FunnelBlock extends AttachedLogisticalBlock
 
 	@Override
 	public void onBlockAdded(BlockState state, World worldIn, BlockPos pos, BlockState oldState, boolean isMoving) {
-		onAttachmentPlaced(worldIn, pos, state);
 		if (worldIn.isRemote)
 			return;
 
@@ -153,42 +147,12 @@ public class FunnelBlock extends AttachedLogisticalBlock
 
 	@Override
 	public void onReplaced(BlockState state, World worldIn, BlockPos pos, BlockState newState, boolean isMoving) {
-		onAttachmentRemoved(worldIn, pos, state);
 		if (state.hasTileEntity() && state.getBlock() != newState.getBlock()) {
 			TileEntityBehaviour.destroy(worldIn, pos, FilteringBehaviour.TYPE);
 			worldIn.removeTileEntity(pos);
 		}
 	}
 
-	@Override
-	public List<BlockPos> getPotentialAttachmentPositions(IWorld world, BlockPos pos, BlockState beltState) {
-		return Arrays.asList(pos.up());
-	}
-
-	@Override
-	public BlockPos getBeltPositionForAttachment(IWorld world, BlockPos pos, BlockState state) {
-		return pos.down();
-	}
-
-	@Override
-	public boolean startProcessingItem(BeltTileEntity te, TransportedItemStack transported, BeltAttachmentState state) {
-		return process(te, transported, state);
-	}
-
-	@Override
-	public boolean isAttachedCorrectly(IWorld world, BlockPos attachmentPos, BlockPos beltPos,
-			BlockState attachmentState, BlockState beltState) {
-		return !isVertical(attachmentState);
-	}
-
-	@Override
-	public boolean processItem(BeltTileEntity te, TransportedItemStack transported, BeltAttachmentState state) {
-		Direction movementFacing = te.getMovementFacing();
-		if (movementFacing != te.getWorld().getBlockState(state.attachmentPos).get(HORIZONTAL_FACING))
-			return false;
-		return process(te, transported, state);
-	}
-
 	@Override
 	public ActionResultType onUse(BlockState state, World worldIn, BlockPos pos, PlayerEntity player, Hand handIn,
 			BlockRayTraceResult hit) {
@@ -207,16 +171,6 @@ public class FunnelBlock extends AttachedLogisticalBlock
 		return ActionResultType.PASS;
 	}
 
-	public boolean process(BeltTileEntity belt, TransportedItemStack transported, BeltAttachmentState state) {
-		TileEntity te = belt.getWorld().getTileEntity(state.attachmentPos);
-		if (!(te instanceof FunnelTileEntity))
-			return false;
-		FunnelTileEntity funnel = (FunnelTileEntity) te;
-		ItemStack stack = funnel.tryToInsert(transported.stack);
-		transported.stack = stack;
-		return true;
-	}
-
 	public static class Vertical extends FunnelBlock {
 		public Vertical(Properties properties) {
 			super(properties);
diff --git a/src/main/java/com/simibubi/create/foundation/tileEntity/SmartTileEntity.java b/src/main/java/com/simibubi/create/foundation/tileEntity/SmartTileEntity.java
index af552df79..eeb6663ce 100644
--- a/src/main/java/com/simibubi/create/foundation/tileEntity/SmartTileEntity.java
+++ b/src/main/java/com/simibubi/create/foundation/tileEntity/SmartTileEntity.java
@@ -6,7 +6,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.function.Consumer;
 
-import com.simibubi.create.foundation.tileEntity.behaviour.IBehaviourType;
+import com.simibubi.create.foundation.tileEntity.behaviour.BehaviourType;
 
 import net.minecraft.nbt.CompoundNBT;
 import net.minecraft.tileentity.ITickableTileEntity;
@@ -14,7 +14,7 @@ import net.minecraft.tileentity.TileEntityType;
 
 public abstract class SmartTileEntity extends SyncedTileEntity implements ITickableTileEntity {
 
-	private Map<IBehaviourType<?>, TileEntityBehaviour> behaviours;
+	private Map<BehaviourType<?>, TileEntityBehaviour> behaviours;
 	private boolean initialized;
 	private boolean firstNbtRead;
 	private int lazyTickRate;
@@ -120,14 +120,14 @@ public abstract class SmartTileEntity extends SyncedTileEntity implements ITicka
 		behaviour.initialize();
 	}
 
-	protected void removeBehaviour(IBehaviourType<?> type) {
+	protected void removeBehaviour(BehaviourType<?> type) {
 		TileEntityBehaviour remove = behaviours.remove(type);
 		if (remove != null)
 			remove.remove();
 	}
 
 	@SuppressWarnings("unchecked")
-	protected <T extends TileEntityBehaviour> T getBehaviour(IBehaviourType<T> type) {
+	protected <T extends TileEntityBehaviour> T getBehaviour(BehaviourType<T> type) {
 		if (behaviours.containsKey(type))
 			return (T) behaviours.get(type);
 		return null;
diff --git a/src/main/java/com/simibubi/create/foundation/tileEntity/TileEntityBehaviour.java b/src/main/java/com/simibubi/create/foundation/tileEntity/TileEntityBehaviour.java
index 84712ba6b..481e064f6 100644
--- a/src/main/java/com/simibubi/create/foundation/tileEntity/TileEntityBehaviour.java
+++ b/src/main/java/com/simibubi/create/foundation/tileEntity/TileEntityBehaviour.java
@@ -1,6 +1,6 @@
 package com.simibubi.create.foundation.tileEntity;
 
-import com.simibubi.create.foundation.tileEntity.behaviour.IBehaviourType;
+import com.simibubi.create.foundation.tileEntity.behaviour.BehaviourType;
 
 import net.minecraft.block.BlockState;
 import net.minecraft.nbt.CompoundNBT;
@@ -23,7 +23,7 @@ public abstract class TileEntityBehaviour {
 		setLazyTickRate(10);
 	}
 
-	public abstract IBehaviourType<?> getType();
+	public abstract BehaviourType<?> getType();
 
 	public void initialize() {
 
@@ -91,18 +91,18 @@ public abstract class TileEntityBehaviour {
 	}
 
 	public static <T extends TileEntityBehaviour> T get(ILightReader reader, BlockPos pos,
-			IBehaviourType<T> type) {
+			BehaviourType<T> type) {
 		return get(reader.getTileEntity(pos), type);
 	}
 	
 	public static <T extends TileEntityBehaviour> void destroy(ILightReader reader, BlockPos pos,
-			IBehaviourType<T> type) {
+			BehaviourType<T> type) {
 		T behaviour = get(reader.getTileEntity(pos), type);
 		if (behaviour != null)
 			behaviour.destroy();
 	}
 
-	public static <T extends TileEntityBehaviour> T get(TileEntity te, IBehaviourType<T> type) {
+	public static <T extends TileEntityBehaviour> T get(TileEntity te, BehaviourType<T> type) {
 		if (te == null)
 			return null;
 		if (!(te instanceof SmartTileEntity))
diff --git a/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/IBehaviourType.java b/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/BehaviourType.java
similarity index 67%
rename from src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/IBehaviourType.java
rename to src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/BehaviourType.java
index bc3ff8758..0942955f5 100644
--- a/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/IBehaviourType.java
+++ b/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/BehaviourType.java
@@ -2,6 +2,6 @@ package com.simibubi.create.foundation.tileEntity.behaviour;
 
 import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
 
-public interface IBehaviourType<T extends TileEntityBehaviour> {
+public class BehaviourType<T extends TileEntityBehaviour> {
 
 }
diff --git a/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/belt/BeltProcessingBehaviour.java b/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/belt/BeltProcessingBehaviour.java
new file mode 100644
index 000000000..2b64f01d9
--- /dev/null
+++ b/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/belt/BeltProcessingBehaviour.java
@@ -0,0 +1,59 @@
+package com.simibubi.create.foundation.tileEntity.behaviour.belt;
+
+import com.simibubi.create.content.contraptions.relays.belt.transport.BeltInventory;
+import com.simibubi.create.content.contraptions.relays.belt.transport.TransportedItemStack;
+import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
+import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
+import com.simibubi.create.foundation.tileEntity.behaviour.BehaviourType;
+
+/**
+ * Behaviour for TileEntities which can process items on belts or depots beneath them.
+ * Currently only supports placement location 2 spaces above the belt block.
+ * Example use: Mechanical Press
+ */
+public class BeltProcessingBehaviour extends TileEntityBehaviour {
+
+	public static BehaviourType<BeltProcessingBehaviour> TYPE = new BehaviourType<>();
+
+	public static enum ProcessingResult {
+		PASS, HOLD, REMOVE;
+	}
+	
+	private ProcessingCallback onItemEnter;
+	private ProcessingCallback continueProcessing;
+
+	public BeltProcessingBehaviour(SmartTileEntity te) {
+		super(te);
+		onItemEnter = (s, i) -> ProcessingResult.PASS;
+		continueProcessing = (s, i) -> ProcessingResult.PASS;
+	}
+	
+	public BeltProcessingBehaviour whenItemEnters(ProcessingCallback callback) {
+		onItemEnter = callback;
+		return this;
+	}
+	
+	public BeltProcessingBehaviour whileItemHeld(ProcessingCallback callback) {
+		continueProcessing = callback;
+		return this;
+	}
+
+	@Override
+	public BehaviourType<?> getType() {
+		return TYPE;
+	}
+
+	public ProcessingResult handleReceivedItem(TransportedItemStack stack, BeltInventory inventory) {
+		return onItemEnter.apply(stack, inventory);
+	}
+
+	public ProcessingResult handleHeldItem(TransportedItemStack stack, BeltInventory inventory) {
+		return continueProcessing.apply(stack, inventory);
+	}
+
+	@FunctionalInterface
+	public interface ProcessingCallback {
+		public ProcessingResult apply(TransportedItemStack stack, BeltInventory inventory);
+	}
+
+}
diff --git a/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/belt/DirectBeltInputBehaviour.java b/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/belt/DirectBeltInputBehaviour.java
new file mode 100644
index 000000000..9aa32a05b
--- /dev/null
+++ b/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/belt/DirectBeltInputBehaviour.java
@@ -0,0 +1,76 @@
+package com.simibubi.create.foundation.tileEntity.behaviour.belt;
+
+import com.simibubi.create.content.contraptions.relays.belt.transport.TransportedItemStack;
+import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
+import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
+import com.simibubi.create.foundation.tileEntity.behaviour.BehaviourType;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.Direction;
+import net.minecraftforge.common.util.LazyOptional;
+import net.minecraftforge.items.CapabilityItemHandler;
+import net.minecraftforge.items.IItemHandler;
+import net.minecraftforge.items.ItemHandlerHelper;
+
+/**
+ * Behaviour for TileEntities to which belts can transfer items directly in a
+ * backup-friendly manner. Example uses: Basin, Saw, Depot
+ */
+public class DirectBeltInputBehaviour extends TileEntityBehaviour {
+
+	public static BehaviourType<DirectBeltInputBehaviour> TYPE = new BehaviourType<>();
+
+	private InsertionCallback tryInsert;
+	private AvailabilityPredicate canInsert;
+
+	public DirectBeltInputBehaviour(SmartTileEntity te) {
+		super(te);
+		tryInsert = this::defaultInsertionCallback;
+		canInsert = d -> true;
+	}
+
+	public DirectBeltInputBehaviour onlyInsertWhen(AvailabilityPredicate pred) {
+		canInsert = pred;
+		return this;
+	}
+
+	public DirectBeltInputBehaviour setInsertionHandler(InsertionCallback callback) {
+		tryInsert = callback;
+		return this;
+	}
+
+	private ItemStack defaultInsertionCallback(TransportedItemStack inserted, Direction side, boolean simulate) {
+		LazyOptional<IItemHandler> lazy = tileEntity.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, side);
+		if (!lazy.isPresent())
+			return inserted.stack;
+		return ItemHandlerHelper.insertItemStacked(lazy.orElse(null), inserted.stack.copy(), simulate);
+	}
+
+	public boolean canInsertFromSide(Direction side) {
+		return canInsert.test(side);
+	}
+
+	public ItemStack handleInsertion(ItemStack stack, Direction side, boolean simulate) {
+		return handleInsertion(new TransportedItemStack(stack), side, simulate);
+	}
+
+	public ItemStack handleInsertion(TransportedItemStack stack, Direction side, boolean simulate) {
+		return tryInsert.apply(stack, side, simulate);
+	}
+
+	@Override
+	public BehaviourType<?> getType() {
+		return TYPE;
+	}
+
+	@FunctionalInterface
+	public interface InsertionCallback {
+		public ItemStack apply(TransportedItemStack stack, Direction side, boolean simulate);
+	}
+
+	@FunctionalInterface
+	public interface AvailabilityPredicate {
+		public boolean test(Direction side);
+	}
+
+}
diff --git a/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/edgeInteraction/EdgeInteractionBehaviour.java b/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/edgeInteraction/EdgeInteractionBehaviour.java
index acc3eb9ef..e4ca847e1 100644
--- a/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/edgeInteraction/EdgeInteractionBehaviour.java
+++ b/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/edgeInteraction/EdgeInteractionBehaviour.java
@@ -4,7 +4,7 @@ import java.util.Optional;
 
 import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
 import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
-import com.simibubi.create.foundation.tileEntity.behaviour.IBehaviourType;
+import com.simibubi.create.foundation.tileEntity.behaviour.BehaviourType;
 
 import net.minecraft.item.Item;
 import net.minecraft.util.Direction;
@@ -13,8 +13,7 @@ import net.minecraft.world.World;
 
 public class EdgeInteractionBehaviour extends TileEntityBehaviour {
 
-	public static IBehaviourType<EdgeInteractionBehaviour> TYPE = new IBehaviourType<EdgeInteractionBehaviour>() {
-	};
+	public static BehaviourType<EdgeInteractionBehaviour> TYPE = new BehaviourType<>();
 	
 	ConnectionCallback connectionCallback;
 	ConnectivityPredicate connectivityPredicate;
@@ -38,7 +37,7 @@ public class EdgeInteractionBehaviour extends TileEntityBehaviour {
 	}
 
 	@Override
-	public IBehaviourType<?> getType() {
+	public BehaviourType<?> getType() {
 		return TYPE;
 	}
 	
diff --git a/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/filtering/FilteringBehaviour.java b/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/filtering/FilteringBehaviour.java
index 0c18968c2..e47b2cb6b 100644
--- a/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/filtering/FilteringBehaviour.java
+++ b/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/filtering/FilteringBehaviour.java
@@ -7,7 +7,7 @@ import com.simibubi.create.content.logistics.item.filter.FilterItem;
 import com.simibubi.create.foundation.networking.AllPackets;
 import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
 import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
-import com.simibubi.create.foundation.tileEntity.behaviour.IBehaviourType;
+import com.simibubi.create.foundation.tileEntity.behaviour.BehaviourType;
 import com.simibubi.create.foundation.tileEntity.behaviour.ValueBoxTransform;
 import com.simibubi.create.foundation.utility.VecHelper;
 
@@ -20,8 +20,7 @@ import net.minecraft.world.World;
 
 public class FilteringBehaviour extends TileEntityBehaviour {
 
-	public static IBehaviourType<FilteringBehaviour> TYPE = new IBehaviourType<FilteringBehaviour>() {
-	};
+	public static BehaviourType<FilteringBehaviour> TYPE = new BehaviourType<>();
 
 	ValueBoxTransform slotPositioning;
 	boolean showCount;
@@ -152,7 +151,7 @@ public class FilteringBehaviour extends TileEntityBehaviour {
 	}
 
 	@Override
-	public IBehaviourType<?> getType() {
+	public BehaviourType<?> getType() {
 		return TYPE;
 	}
 
diff --git a/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/inventory/AutoExtractingBehaviour.java b/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/inventory/AutoExtractingBehaviour.java
index 4660656b2..ee95b4a82 100644
--- a/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/inventory/AutoExtractingBehaviour.java
+++ b/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/inventory/AutoExtractingBehaviour.java
@@ -7,7 +7,7 @@ import java.util.function.Supplier;
 import org.apache.commons.lang3.tuple.Pair;
 
 import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
-import com.simibubi.create.foundation.tileEntity.behaviour.IBehaviourType;
+import com.simibubi.create.foundation.tileEntity.behaviour.BehaviourType;
 
 import net.minecraft.item.ItemStack;
 import net.minecraft.util.Direction;
@@ -15,8 +15,7 @@ import net.minecraft.util.math.BlockPos;
 
 public class AutoExtractingBehaviour extends ExtractingBehaviour {
 
-	public static IBehaviourType<AutoExtractingBehaviour> TYPE = new IBehaviourType<AutoExtractingBehaviour>() {
-	};
+	public static BehaviourType<AutoExtractingBehaviour> TYPE = new BehaviourType<>();
 
 	private int delay;
 	private int timer;
@@ -85,7 +84,7 @@ public class AutoExtractingBehaviour extends ExtractingBehaviour {
 	}
 
 	@Override
-	public IBehaviourType<?> getType() {
+	public BehaviourType<?> getType() {
 		return TYPE;
 	}
 
diff --git a/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/inventory/ExtractingBehaviour.java b/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/inventory/ExtractingBehaviour.java
index c1bdbcf09..a7ed96207 100644
--- a/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/inventory/ExtractingBehaviour.java
+++ b/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/inventory/ExtractingBehaviour.java
@@ -11,7 +11,7 @@ import org.apache.commons.lang3.tuple.Pair;
 import com.simibubi.create.foundation.config.AllConfigs;
 import com.simibubi.create.foundation.item.ItemHelper;
 import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
-import com.simibubi.create.foundation.tileEntity.behaviour.IBehaviourType;
+import com.simibubi.create.foundation.tileEntity.behaviour.BehaviourType;
 import com.simibubi.create.foundation.tileEntity.behaviour.filtering.FilteringBehaviour;
 
 import net.minecraft.item.ItemStack;
@@ -21,8 +21,7 @@ import net.minecraftforge.items.IItemHandler;
 
 public class ExtractingBehaviour extends InventoryManagementBehaviour {
 
-	public static IBehaviourType<ExtractingBehaviour> TYPE = new IBehaviourType<ExtractingBehaviour>() {
-	};
+	public static BehaviourType<ExtractingBehaviour> TYPE = new BehaviourType<>();
 
 	private Function<ItemStack, Integer> customAmountFilter;
 	private Predicate<ItemStack> customFilter;
@@ -90,7 +89,7 @@ public class ExtractingBehaviour extends InventoryManagementBehaviour {
 	}
 
 	@Override
-	public IBehaviourType<?> getType() {
+	public BehaviourType<?> getType() {
 		return TYPE;
 	}
 
diff --git a/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/inventory/InsertingBehaviour.java b/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/inventory/InsertingBehaviour.java
index e865065dc..4b18aad01 100644
--- a/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/inventory/InsertingBehaviour.java
+++ b/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/inventory/InsertingBehaviour.java
@@ -6,7 +6,7 @@ import java.util.function.Supplier;
 import org.apache.commons.lang3.tuple.Pair;
 
 import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
-import com.simibubi.create.foundation.tileEntity.behaviour.IBehaviourType;
+import com.simibubi.create.foundation.tileEntity.behaviour.BehaviourType;
 
 import net.minecraft.item.ItemStack;
 import net.minecraft.util.Direction;
@@ -16,8 +16,7 @@ import net.minecraftforge.items.ItemHandlerHelper;
 
 public class InsertingBehaviour extends InventoryManagementBehaviour {
 
-	public static IBehaviourType<InsertingBehaviour> TYPE = new IBehaviourType<InsertingBehaviour>() {
-	};
+	public static BehaviourType<InsertingBehaviour> TYPE = new BehaviourType<>();
 
 	public InsertingBehaviour(SmartTileEntity te, Supplier<List<Pair<BlockPos, Direction>>> attachments) {
 		super(te, attachments);
@@ -33,7 +32,7 @@ public class InsertingBehaviour extends InventoryManagementBehaviour {
 	}
 
 	@Override
-	public IBehaviourType<?> getType() {
+	public BehaviourType<?> getType() {
 		return TYPE;
 	}
 
diff --git a/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/inventory/InventoryManagementBehaviour.java b/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/inventory/InventoryManagementBehaviour.java
index b44a390c5..0318f0375 100644
--- a/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/inventory/InventoryManagementBehaviour.java
+++ b/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/inventory/InventoryManagementBehaviour.java
@@ -11,7 +11,7 @@ import org.apache.commons.lang3.tuple.Pair;
 import com.google.common.collect.ImmutableList;
 import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
 import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
-import com.simibubi.create.foundation.tileEntity.behaviour.IBehaviourType;
+import com.simibubi.create.foundation.tileEntity.behaviour.BehaviourType;
 
 import net.minecraft.block.BlockState;
 import net.minecraft.tileentity.TileEntity;
@@ -28,8 +28,7 @@ public class InventoryManagementBehaviour extends TileEntityBehaviour {
 	private Supplier<List<Pair<BlockPos, Direction>>> attachments;
 	private List<IItemHandler> activeHandlers;
 
-	public static IBehaviourType<InventoryManagementBehaviour> TYPE = new IBehaviourType<InventoryManagementBehaviour>() {
-	};
+	public static BehaviourType<InventoryManagementBehaviour> TYPE = new BehaviourType<>();
 
 	public InventoryManagementBehaviour(SmartTileEntity te, Supplier<List<Pair<BlockPos, Direction>>> attachments) {
 		super(te);
@@ -98,7 +97,7 @@ public class InventoryManagementBehaviour extends TileEntityBehaviour {
 	}
 
 	@Override
-	public IBehaviourType<?> getType() {
+	public BehaviourType<?> getType() {
 		return TYPE;
 	}
 
diff --git a/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/inventory/SingleTargetAutoExtractingBehaviour.java b/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/inventory/SingleTargetAutoExtractingBehaviour.java
index be0c8a8e3..763b83034 100644
--- a/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/inventory/SingleTargetAutoExtractingBehaviour.java
+++ b/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/inventory/SingleTargetAutoExtractingBehaviour.java
@@ -4,7 +4,7 @@ import java.util.function.Consumer;
 import java.util.function.Supplier;
 
 import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
-import com.simibubi.create.foundation.tileEntity.behaviour.IBehaviourType;
+import com.simibubi.create.foundation.tileEntity.behaviour.BehaviourType;
 
 import net.minecraft.item.ItemStack;
 import net.minecraft.nbt.CompoundNBT;
@@ -13,16 +13,14 @@ import net.minecraft.util.math.BlockPos;
 
 public class SingleTargetAutoExtractingBehaviour extends AutoExtractingBehaviour {
 
-	public static IBehaviourType<SingleTargetAutoExtractingBehaviour> TYPE =
-		new IBehaviourType<SingleTargetAutoExtractingBehaviour>() {
-		};
+	public static BehaviourType<SingleTargetAutoExtractingBehaviour> TYPE = new BehaviourType<>();
 
 	private Supplier<Direction> attachmentDirection;
 	boolean synced;
 	boolean advantageOnNextSync;
 
 	public SingleTargetAutoExtractingBehaviour(SmartTileEntity te, Supplier<Direction> attachmentDirection,
-			Consumer<ItemStack> onExtract, int delay) {
+		Consumer<ItemStack> onExtract, int delay) {
 		super(te, Attachments.toward(attachmentDirection), onExtract, delay);
 		this.attachmentDirection = attachmentDirection;
 		synced = true;
@@ -49,7 +47,8 @@ public class SingleTargetAutoExtractingBehaviour extends AutoExtractingBehaviour
 	@Override
 	public boolean extract() {
 		if (synced) {
-			BlockPos invPos = tileEntity.getPos().offset(attachmentDirection.get());
+			BlockPos invPos = tileEntity.getPos()
+				.offset(attachmentDirection.get());
 			return SynchronizedExtraction.extractSynchronized(getWorld(), invPos);
 		} else
 			return extractFromInventory();
@@ -60,7 +59,7 @@ public class SingleTargetAutoExtractingBehaviour extends AutoExtractingBehaviour
 	}
 
 	@Override
-	public IBehaviourType<?> getType() {
+	public BehaviourType<?> getType() {
 		return TYPE;
 	}
 
diff --git a/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/linked/LinkBehaviour.java b/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/linked/LinkBehaviour.java
index 9bfb26ab5..6d96d6a9b 100644
--- a/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/linked/LinkBehaviour.java
+++ b/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/linked/LinkBehaviour.java
@@ -11,7 +11,7 @@ import com.simibubi.create.content.logistics.RedstoneLinkNetworkHandler;
 import com.simibubi.create.content.logistics.RedstoneLinkNetworkHandler.Frequency;
 import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
 import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
-import com.simibubi.create.foundation.tileEntity.behaviour.IBehaviourType;
+import com.simibubi.create.foundation.tileEntity.behaviour.BehaviourType;
 import com.simibubi.create.foundation.tileEntity.behaviour.ValueBoxTransform;
 
 import net.minecraft.block.BlockState;
@@ -21,8 +21,7 @@ import net.minecraft.util.math.Vec3d;
 
 public class LinkBehaviour extends TileEntityBehaviour {
 
-	public static IBehaviourType<LinkBehaviour> TYPE = new IBehaviourType<LinkBehaviour>() {
-	};
+	public static BehaviourType<LinkBehaviour> TYPE = new BehaviourType<>();
 
 	enum Mode {
 		TRANSMIT, RECEIVE
@@ -162,7 +161,7 @@ public class LinkBehaviour extends TileEntityBehaviour {
 	}
 
 	@Override
-	public IBehaviourType<?> getType() {
+	public BehaviourType<?> getType() {
 		return TYPE;
 	}
 
diff --git a/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/scrollvalue/ScrollValueBehaviour.java b/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/scrollvalue/ScrollValueBehaviour.java
index 59be89235..a9585d883 100644
--- a/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/scrollvalue/ScrollValueBehaviour.java
+++ b/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/scrollvalue/ScrollValueBehaviour.java
@@ -6,7 +6,7 @@ import java.util.function.Function;
 import com.simibubi.create.foundation.networking.AllPackets;
 import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
 import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
-import com.simibubi.create.foundation.tileEntity.behaviour.IBehaviourType;
+import com.simibubi.create.foundation.tileEntity.behaviour.BehaviourType;
 import com.simibubi.create.foundation.tileEntity.behaviour.ValueBoxTransform;
 
 import net.minecraft.block.BlockState;
@@ -16,8 +16,7 @@ import net.minecraft.util.math.Vec3d;
 
 public class ScrollValueBehaviour extends TileEntityBehaviour {
 
-	public static IBehaviourType<ScrollValueBehaviour> TYPE = new IBehaviourType<ScrollValueBehaviour>() {
-	};
+	public static BehaviourType<ScrollValueBehaviour> TYPE = new BehaviourType<>();
 
 	ValueBoxTransform slotPositioning;
 	Vec3d textShift;
@@ -162,7 +161,7 @@ public class ScrollValueBehaviour extends TileEntityBehaviour {
 	}
 
 	@Override
-	public IBehaviourType<?> getType() {
+	public BehaviourType<?> getType() {
 		return TYPE;
 	}
 
diff --git a/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/simple/DeferralBehaviour.java b/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/simple/DeferralBehaviour.java
index 9650fbe92..b9a195aa7 100644
--- a/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/simple/DeferralBehaviour.java
+++ b/src/main/java/com/simibubi/create/foundation/tileEntity/behaviour/simple/DeferralBehaviour.java
@@ -4,14 +4,13 @@ import java.util.function.Supplier;
 
 import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
 import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
-import com.simibubi.create.foundation.tileEntity.behaviour.IBehaviourType;
+import com.simibubi.create.foundation.tileEntity.behaviour.BehaviourType;
 
 import net.minecraft.nbt.CompoundNBT;
 
 public class DeferralBehaviour extends TileEntityBehaviour {
 
-	public static IBehaviourType<DeferralBehaviour> TYPE = new IBehaviourType<DeferralBehaviour>() {
-	};
+	public static BehaviourType<DeferralBehaviour> TYPE = new BehaviourType<>();
 
 	private boolean needsUpdate;
 	private Supplier<Boolean> callback;
@@ -45,7 +44,7 @@ public class DeferralBehaviour extends TileEntityBehaviour {
 	}
 
 	@Override
-	public IBehaviourType<?> getType() {
+	public BehaviourType<?> getType() {
 		return TYPE;
 	}