From e7a22c96e93a70e7dff971f2d839cf9e8e1ef197 Mon Sep 17 00:00:00 2001
From: simibubi <31564874+simibubi@users.noreply.github.com>
Date: Fri, 27 Nov 2020 17:19:11 +0100
Subject: [PATCH] Funnels for everybody

- Fixed flapfunnels not taking secondary items off depots
- Funnels can now sit horizontally on saws and drains
- Added a recipe from natural to regular scoria
---
 src/generated/resources/.cache/cache          |  2 +
 .../smelting/scoria_from_natural.json         | 32 +++++++++++++
 .../recipes/smelting/scoria_from_natural.json |  9 ++++
 .../components/saw/SawTileEntity.java         | 17 +++++--
 .../fluids/actors/ItemDrainTileEntity.java    | 15 +++++-
 .../block/depot/DepotTileEntity.java          | 36 +++++++++++++-
 .../block/funnel/BeltFunnelBlock.java         | 23 ++++-----
 .../logistics/block/funnel/FunnelBlock.java   |  7 ++-
 .../block/funnel/FunnelTileEntity.java        | 34 +------------
 .../mechanicalArm/ArmInteractionPoint.java    |  9 ++--
 .../data/recipe/StandardRecipeGen.java        |  3 ++
 .../belt/DirectBeltInputBehaviour.java        | 48 +++++++++++++++++++
 .../block/belt_funnel/block_pulling.json      |  4 +-
 .../block/belt_funnel/block_pushing.json      |  4 +-
 14 files changed, 183 insertions(+), 60 deletions(-)
 create mode 100644 src/generated/resources/data/create/advancements/recipes/create.palettes/smelting/scoria_from_natural.json
 create mode 100644 src/generated/resources/data/create/recipes/smelting/scoria_from_natural.json

diff --git a/src/generated/resources/.cache/cache b/src/generated/resources/.cache/cache
index 68e403fc6..7e6225ab8 100644
--- a/src/generated/resources/.cache/cache
+++ b/src/generated/resources/.cache/cache
@@ -2096,6 +2096,7 @@ e340721aa78f260c2666214aa149241a37de216e data/create/advancements/recipes/create
 070720cc271767b26ad51fa089b4cf2a64d309be data/create/advancements/recipes/create.palettes/smelting/gabbro.json
 9a2901f6b918468b0034a8942178d6f3c82aeb6e data/create/advancements/recipes/create.palettes/smelting/limestone.json
 c8fb5d555eacec479af4fa6b9042656f1fe49a2e data/create/advancements/recipes/create.palettes/smelting/scoria.json
+c4f13a0ff5827570018515109c1eda0b3f2fb3bc data/create/advancements/recipes/create.palettes/smelting/scoria_from_natural.json
 459538728b06d4c72d7e65d8f7c98a75a48f3a52 data/create/advancements/recipes/create.palettes/spruce_window.json
 6aaf96cdaa845b63ab67ba4b968ea4d811e2fef5 data/create/advancements/recipes/create.palettes/spruce_window_pane.json
 ab0cacba05f8def9cc91b993d464c297babf6fc3 data/create/advancements/recipes/create.palettes/tiled_glass_from_glass_colorless_stonecutting.json
@@ -3211,6 +3212,7 @@ b032c79090adad2262ae94609e0b3747327d51a2 data/create/recipes/smelting/gold_ingot
 fe3e4c244c34aa6948243fabd6b42f04f80d4992 data/create/recipes/smelting/iron_ingot_from_crushed.json
 bf0e5df5a88e583e39a4e14b006cbf33b99611e1 data/create/recipes/smelting/limestone.json
 2c230522bb0946bde6a51442cb15c5efeea99b15 data/create/recipes/smelting/scoria.json
+f5317c85a9e10a5f9346e13aef8bb364a5203346 data/create/recipes/smelting/scoria_from_natural.json
 a5d23be4cc959eb47d84b210190abaafcf41f022 data/create/recipes/smelting/zinc_ingot_from_crushed.json
 2d8e448bbe841871c5d9a022149c5f34fd5c0df1 data/create/recipes/smelting/zinc_ingot_from_ore.json
 ce7c3c6e1da9d6684c9537d1a558423925d89f33 data/create/recipes/smoking/bread.json
diff --git a/src/generated/resources/data/create/advancements/recipes/create.palettes/smelting/scoria_from_natural.json b/src/generated/resources/data/create/advancements/recipes/create.palettes/smelting/scoria_from_natural.json
new file mode 100644
index 000000000..9a19dd7c2
--- /dev/null
+++ b/src/generated/resources/data/create/advancements/recipes/create.palettes/smelting/scoria_from_natural.json
@@ -0,0 +1,32 @@
+{
+  "parent": "minecraft:recipes/root",
+  "rewards": {
+    "recipes": [
+      "create:smelting/scoria_from_natural"
+    ]
+  },
+  "criteria": {
+    "has_item": {
+      "trigger": "minecraft:inventory_changed",
+      "conditions": {
+        "items": [
+          {
+            "item": "create:natural_scoria"
+          }
+        ]
+      }
+    },
+    "has_the_recipe": {
+      "trigger": "minecraft:recipe_unlocked",
+      "conditions": {
+        "recipe": "create:smelting/scoria_from_natural"
+      }
+    }
+  },
+  "requirements": [
+    [
+      "has_item",
+      "has_the_recipe"
+    ]
+  ]
+}
\ No newline at end of file
diff --git a/src/generated/resources/data/create/recipes/smelting/scoria_from_natural.json b/src/generated/resources/data/create/recipes/smelting/scoria_from_natural.json
new file mode 100644
index 000000000..922b7e043
--- /dev/null
+++ b/src/generated/resources/data/create/recipes/smelting/scoria_from_natural.json
@@ -0,0 +1,9 @@
+{
+  "type": "minecraft:smelting",
+  "ingredient": {
+    "item": "create:natural_scoria"
+  },
+  "result": "create:scoria",
+  "experience": 0.0,
+  "cookingtime": 200
+}
\ No newline at end of file
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 70b3cce8a..fddd0b76d 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
@@ -61,7 +61,6 @@ public class SawTileEntity extends BlockBreakingKineticTileEntity {
 	private int recipeIndex;
 	private LazyOptional<IItemHandler> invProvider = LazyOptional.empty();
 	private FilteringBehaviour filtering;
-	private boolean destroyed;
 
 	public SawTileEntity(TileEntityType<? extends SawTileEntity> type) {
 		super(type);
@@ -76,7 +75,7 @@ public class SawTileEntity extends BlockBreakingKineticTileEntity {
 		super.addBehaviours(behaviours);
 		filtering = new FilteringBehaviour(this, new SawFilterSlot()).forRecipes();
 		behaviours.add(filtering);
-		behaviours.add(new DirectBeltInputBehaviour(this));
+		behaviours.add(new DirectBeltInputBehaviour(this).allowingBeltFunnelsWhen(this::canProcess));
 	}
 
 	@Override
@@ -136,6 +135,19 @@ public class SawTileEntity extends BlockBreakingKineticTileEntity {
 			return;
 		inventory.remainingTime = 0;
 
+		for (int slot = 0; slot < inventory.getSlots(); slot++) {
+			ItemStack stack = inventory.getStackInSlot(slot);
+			if (stack.isEmpty())
+				continue;
+			ItemStack tryExportingToBeltFunnel = getBehaviour(DirectBeltInputBehaviour.TYPE)
+				.tryExportingToBeltFunnel(stack, itemMovementFacing.getOpposite());
+			if (tryExportingToBeltFunnel.getCount() != stack.getCount()) {
+				inventory.setStackInSlot(slot, tryExportingToBeltFunnel);
+				notifyUpdate();
+				return;
+			}
+		}
+
 		BlockPos nextPos = pos.add(itemMovement.x, itemMovement.y, itemMovement.z);
 		DirectBeltInputBehaviour behaviour = TileEntityBehaviour.get(world, nextPos, DirectBeltInputBehaviour.TYPE);
 		if (behaviour != null) {
@@ -182,7 +194,6 @@ public class SawTileEntity extends BlockBreakingKineticTileEntity {
 	@Override
 	public void remove() {
 		invProvider.invalidate();
-		destroyed = true;
 		super.remove();
 	}
 
diff --git a/src/main/java/com/simibubi/create/content/contraptions/fluids/actors/ItemDrainTileEntity.java b/src/main/java/com/simibubi/create/content/contraptions/fluids/actors/ItemDrainTileEntity.java
index 341b36e25..780220390 100644
--- a/src/main/java/com/simibubi/create/content/contraptions/fluids/actors/ItemDrainTileEntity.java
+++ b/src/main/java/com/simibubi/create/content/contraptions/fluids/actors/ItemDrainTileEntity.java
@@ -47,7 +47,8 @@ public class ItemDrainTileEntity extends SmartTileEntity {
 
 	@Override
 	public void addBehaviours(List<TileEntityBehaviour> behaviours) {
-		behaviours.add(new DirectBeltInputBehaviour(this).setInsertionHandler(this::tryInsertingFromSide));
+		behaviours.add(new DirectBeltInputBehaviour(this).allowingBeltFunnels()
+			.setInsertionHandler(this::tryInsertingFromSide));
 		behaviours.add(internalTank = SmartFluidTankBehaviour.single(this, 1500)
 			.allowExtraction()
 			.forbidInsertion());
@@ -112,6 +113,18 @@ public class ItemDrainTileEntity extends SmartTileEntity {
 				return;
 
 			Direction side = heldItem.insertedFrom;
+
+			ItemStack tryExportingToBeltFunnel = getBehaviour(DirectBeltInputBehaviour.TYPE)
+				.tryExportingToBeltFunnel(heldItem.stack, side.getOpposite());
+			if (tryExportingToBeltFunnel.getCount() != heldItem.stack.getCount()) {
+				if (tryExportingToBeltFunnel.isEmpty())
+					heldItem = null;
+				else
+					heldItem.stack = tryExportingToBeltFunnel;
+				notifyUpdate();
+				return;
+			}
+
 			BlockPos nextPosition = pos.offset(side);
 			DirectBeltInputBehaviour directBeltInputBehaviour =
 				TileEntityBehaviour.get(world, nextPosition, DirectBeltInputBehaviour.TYPE);
diff --git a/src/main/java/com/simibubi/create/content/logistics/block/depot/DepotTileEntity.java b/src/main/java/com/simibubi/create/content/logistics/block/depot/DepotTileEntity.java
index 5ab990160..2eeedb782 100644
--- a/src/main/java/com/simibubi/create/content/logistics/block/depot/DepotTileEntity.java
+++ b/src/main/java/com/simibubi/create/content/logistics/block/depot/DepotTileEntity.java
@@ -66,6 +66,8 @@ public class DepotTileEntity extends SmartTileEntity {
 			return;
 		if (world.isRemote)
 			return;
+		if (handleBeltFunnelOutput())
+			return;
 
 		BeltProcessingBehaviour processingBehaviour =
 			TileEntityBehaviour.get(world, pos.up(2), BeltProcessingBehaviour.TYPE);
@@ -74,8 +76,8 @@ public class DepotTileEntity extends SmartTileEntity {
 		if (!heldItem.locked && BeltProcessingBehaviour.isBlocked(world, pos))
 			return;
 
-		boolean wasLocked = heldItem.locked;
 		ItemStack previousItem = heldItem.stack;
+		boolean wasLocked = heldItem.locked;
 		ProcessingResult result = wasLocked ? processingBehaviour.handleHeldItem(heldItem, transportedHandler)
 			: processingBehaviour.handleReceivedItem(heldItem, transportedHandler);
 		if (result == ProcessingResult.REMOVE) {
@@ -89,6 +91,35 @@ public class DepotTileEntity extends SmartTileEntity {
 			sendData();
 	}
 
+	private boolean handleBeltFunnelOutput() {
+		for (int slot = 0; slot < processingOutputBuffer.getSlots(); slot++) {
+			ItemStack previousItem = processingOutputBuffer.getStackInSlot(slot);
+			if (previousItem.isEmpty())
+				continue;
+			ItemStack afterInsert =
+				getBehaviour(DirectBeltInputBehaviour.TYPE).tryExportingToBeltFunnel(previousItem, null);
+			if (previousItem.getCount() != afterInsert.getCount()) {
+				processingOutputBuffer.setStackInSlot(slot, afterInsert);
+				notifyUpdate();
+				return true;
+			}
+		}
+
+		ItemStack previousItem = heldItem.stack;
+		ItemStack afterInsert =
+			getBehaviour(DirectBeltInputBehaviour.TYPE).tryExportingToBeltFunnel(previousItem, null);
+		if (previousItem.getCount() != afterInsert.getCount()) {
+			if (afterInsert.isEmpty())
+				heldItem = null;
+			else
+				heldItem.stack = afterInsert;
+			notifyUpdate();
+			return true;
+		}
+
+		return false;
+	}
+
 	@Override
 	public void remove() {
 		super.remove();
@@ -115,7 +146,8 @@ public class DepotTileEntity extends SmartTileEntity {
 
 	@Override
 	public void addBehaviours(List<TileEntityBehaviour> behaviours) {
-		behaviours.add(new DirectBeltInputBehaviour(this).setInsertionHandler(this::tryInsertingFromSide));
+		behaviours.add(new DirectBeltInputBehaviour(this).allowingBeltFunnels()
+			.setInsertionHandler(this::tryInsertingFromSide));
 		transportedHandler = new TransportedItemStackHandlerBehaviour(this, this::applyToAllItems)
 			.withStackPlacement(this::getWorldPositionOf);
 		behaviours.add(transportedHandler);
diff --git a/src/main/java/com/simibubi/create/content/logistics/block/funnel/BeltFunnelBlock.java b/src/main/java/com/simibubi/create/content/logistics/block/funnel/BeltFunnelBlock.java
index 3b98a4e55..ff54b4192 100644
--- a/src/main/java/com/simibubi/create/content/logistics/block/funnel/BeltFunnelBlock.java
+++ b/src/main/java/com/simibubi/create/content/logistics/block/funnel/BeltFunnelBlock.java
@@ -6,8 +6,8 @@ import com.simibubi.create.AllTileEntities;
 import com.simibubi.create.content.contraptions.relays.belt.BeltBlock;
 import com.simibubi.create.content.contraptions.relays.belt.BeltSlope;
 import com.simibubi.create.content.contraptions.wrench.IWrenchable;
-import com.simibubi.create.content.logistics.block.depot.DepotBlock;
 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.Lang;
@@ -133,7 +133,7 @@ public abstract class BeltFunnelBlock extends HorizontalBlock implements IWrench
 			world.removeTileEntity(pos);
 		}
 	}
-	
+
 	@Override
 	@OnlyIn(Dist.CLIENT)
 	public boolean addDestroyEffects(BlockState state, World world, BlockPos pos, ParticleManager manager) {
@@ -172,13 +172,14 @@ public abstract class BeltFunnelBlock extends HorizontalBlock implements IWrench
 
 	public static boolean isOnValidBelt(BlockState state, IWorldReader world, BlockPos pos) {
 		BlockState stateBelow = world.getBlockState(pos.down());
-		if (stateBelow.getBlock() instanceof DepotBlock)
-			return true;
-		if (!(stateBelow.getBlock() instanceof BeltBlock))
+		if ((stateBelow.getBlock() instanceof BeltBlock))
+			return BeltBlock.canTransportObjects(stateBelow);
+		DirectBeltInputBehaviour directBeltInputBehaviour =
+			TileEntityBehaviour.get(world, pos.down(), DirectBeltInputBehaviour.TYPE);
+		if (directBeltInputBehaviour == null)
 			return false;
-		if (!BeltBlock.canTransportObjects(stateBelow))
-			return false;
-		return true;
+		return directBeltInputBehaviour.canSupportBeltFunnels();
+
 	}
 
 	@Override
@@ -208,7 +209,8 @@ public abstract class BeltFunnelBlock extends HorizontalBlock implements IWrench
 		else if (shape == Shape.EXTENDED)
 			newShape = Shape.RETRACTED;
 		else if (shape == Shape.RETRACTED) {
-			BlockState belt = world.getBlockState(context.getPos().down());
+			BlockState belt = world.getBlockState(context.getPos()
+				.down());
 			if (belt.getBlock() instanceof BeltBlock && belt.get(BeltBlock.SLOPE) != BeltSlope.HORIZONTAL)
 				newShape = Shape.RETRACTED;
 			else
@@ -216,8 +218,7 @@ public abstract class BeltFunnelBlock extends HorizontalBlock implements IWrench
 		}
 
 		if (newShape != shape)
-			world
-				.setBlockState(context.getPos(), state.with(SHAPE, newShape));
+			world.setBlockState(context.getPos(), state.with(SHAPE, newShape));
 		return ActionResultType.SUCCESS;
 	}
 
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 0084a6412..b194b8543 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
@@ -125,12 +125,15 @@ public abstract class FunnelBlock extends ProperDirectionalBlock implements ITE<
 			return toInsert;
 		if (simulate)
 			inserter.simulate();
-		if (!simulate) {
+		ItemStack insert = inserter.insert(toInsert);
+		
+		if (!simulate && insert.getCount() != toInsert.getCount()) {
 			TileEntity tileEntity = worldIn.getTileEntity(pos);
 			if (tileEntity instanceof FunnelTileEntity)
 				((FunnelTileEntity) tileEntity).onTransfer(toInsert);
 		}
-		return inserter.insert(toInsert);
+		
+		return insert;
 	}
 
 	@Override
diff --git a/src/main/java/com/simibubi/create/content/logistics/block/funnel/FunnelTileEntity.java b/src/main/java/com/simibubi/create/content/logistics/block/funnel/FunnelTileEntity.java
index 378b19249..0404626ca 100644
--- a/src/main/java/com/simibubi/create/content/logistics/block/funnel/FunnelTileEntity.java
+++ b/src/main/java/com/simibubi/create/content/logistics/block/funnel/FunnelTileEntity.java
@@ -18,8 +18,6 @@ import com.simibubi.create.foundation.item.TooltipHelper;
 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.tileEntity.behaviour.belt.TransportedItemStackHandlerBehaviour;
-import com.simibubi.create.foundation.tileEntity.behaviour.belt.TransportedItemStackHandlerBehaviour.TransportedResult;
 import com.simibubi.create.foundation.tileEntity.behaviour.filtering.FilteringBehaviour;
 import com.simibubi.create.foundation.tileEntity.behaviour.inventory.InvManipulationBehaviour;
 import com.simibubi.create.foundation.tileEntity.behaviour.inventory.InvManipulationBehaviour.InterfaceProvider;
@@ -94,7 +92,7 @@ public class FunnelTileEntity extends SmartTileEntity implements IHaveHoveringIn
 		if (mode == Mode.PAUSED)
 			extractionCooldown = 0;
 		if (mode == Mode.TAKING_FROM_BELT)
-			tickAsPullingBeltFunnel();
+			return;
 
 		if (extractionCooldown > 0) {
 			extractionCooldown--;
@@ -137,17 +135,6 @@ public class FunnelTileEntity extends SmartTileEntity implements IHaveHoveringIn
 		startCooldown();
 	}
 
-	private void tickAsPullingBeltFunnel() {
-		// Belts handle insertion from their side
-		if (AllBlocks.BELT.has(world.getBlockState(pos.down())))
-			return;
-		TransportedItemStackHandlerBehaviour handler =
-			TileEntityBehaviour.get(world, pos.down(), TransportedItemStackHandlerBehaviour.TYPE);
-		if (handler == null)
-			return;
-		handler.handleCenteredProcessingOnAllItems(1 / 32f, this::collectFromHandler);
-	}
-
 	private void activateExtractingBeltFunnel() {
 		BlockState blockState = getBlockState();
 		Direction facing = blockState.get(BeltFunnelBlock.HORIZONTAL_FACING);
@@ -183,25 +170,6 @@ public class FunnelTileEntity extends SmartTileEntity implements IHaveHoveringIn
 		return extractionCooldown = AllConfigs.SERVER.logistics.defaultExtractionTimer.get();
 	}
 
-	private TransportedResult collectFromHandler(TransportedItemStack stack) {
-		TransportedResult ignore = TransportedResult.doNothing();
-		ItemStack toInsert = stack.stack.copy();
-		if (!filtering.test(toInsert))
-			return ignore;
-		ItemStack remainder = invManipulation.insert(toInsert);
-		if (remainder.equals(stack.stack, false))
-			return ignore;
-
-		flap(true);
-		onTransfer(toInsert);
-
-		if (remainder.isEmpty())
-			return TransportedResult.removeItem();
-		TransportedItemStack changed = stack.copy();
-		changed.stack = remainder;
-		return TransportedResult.convertTo(changed);
-	}
-
 	@Override
 	public void addBehaviours(List<TileEntityBehaviour> behaviours) {
 		invManipulation = new InvManipulationBehaviour(this, InterfaceProvider.oppositeOfBlockFacing());
diff --git a/src/main/java/com/simibubi/create/content/logistics/block/mechanicalArm/ArmInteractionPoint.java b/src/main/java/com/simibubi/create/content/logistics/block/mechanicalArm/ArmInteractionPoint.java
index 75c75fde0..039e0e37a 100644
--- a/src/main/java/com/simibubi/create/content/logistics/block/mechanicalArm/ArmInteractionPoint.java
+++ b/src/main/java/com/simibubi/create/content/logistics/block/mechanicalArm/ArmInteractionPoint.java
@@ -219,7 +219,7 @@ public abstract class ArmInteractionPoint {
 		@Override
 		boolean isValid(IBlockReader reader, BlockPos pos, BlockState state) {
 			return AllBlocks.MECHANICAL_SAW.has(state) && state.get(SawBlock.FACING) == Direction.UP
-				&& ((KineticTileEntity)reader.getTileEntity(pos)).getSpeed() != 0;
+				&& ((KineticTileEntity) reader.getTileEntity(pos)).getSpeed() != 0;
 		}
 
 	}
@@ -298,7 +298,7 @@ public abstract class ArmInteractionPoint {
 			return state.get(MechanicalCrafterBlock.HORIZONTAL_FACING)
 				.getOpposite();
 		}
-		
+
 		@Override
 		ItemStack extract(World world, int slot, int amount, boolean simulate) {
 			TileEntity te = world.getTileEntity(pos);
@@ -441,12 +441,13 @@ public abstract class ArmInteractionPoint {
 				return stack;
 			if (simulate)
 				inserter.simulate();
-			if (!simulate) {
+			ItemStack insert = inserter.insert(stack);
+			if (!simulate && insert.getCount() != stack.getCount()) {
 				TileEntity tileEntity = world.getTileEntity(pos);
 				if (tileEntity instanceof FunnelTileEntity)
 					((FunnelTileEntity) tileEntity).onTransfer(stack);
 			}
-			return inserter.insert(stack);
+			return insert;
 		}
 
 		@Override
diff --git a/src/main/java/com/simibubi/create/foundation/data/recipe/StandardRecipeGen.java b/src/main/java/com/simibubi/create/foundation/data/recipe/StandardRecipeGen.java
index e2ca5de45..15a02e6fa 100644
--- a/src/main/java/com/simibubi/create/foundation/data/recipe/StandardRecipeGen.java
+++ b/src/main/java/com/simibubi/create/foundation/data/recipe/StandardRecipeGen.java
@@ -906,6 +906,9 @@ public class StandardRecipeGen extends CreateRecipeProvider {
 			.inFurnace(),
 		GRANITE = create(AllPaletteBlocks.GABBRO::get).viaCooking(() -> Blocks.GRANITE)
 			.inFurnace(),
+		NAT_SCORIA = create(AllPaletteBlocks.SCORIA::get).withSuffix("_from_natural")
+			.viaCooking(AllPaletteBlocks.NATURAL_SCORIA::get)
+			.inFurnace(),
 
 		FRAMED_GLASS = recycleGlass(AllPaletteBlocks.FRAMED_GLASS),
 		TILED_GLASS = recycleGlass(AllPaletteBlocks.TILED_GLASS),
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
index 9aa32a05b..ed571d3a5 100644
--- 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
@@ -1,12 +1,24 @@
 package com.simibubi.create.foundation.tileEntity.behaviour.belt;
 
+import java.util.function.Supplier;
+
+import javax.annotation.Nullable;
+
 import com.simibubi.create.content.contraptions.relays.belt.transport.TransportedItemStack;
+import com.simibubi.create.content.logistics.block.funnel.BeltFunnelBlock;
+import com.simibubi.create.content.logistics.block.funnel.BeltFunnelBlock.Shape;
+import com.simibubi.create.content.logistics.block.funnel.FunnelBlock;
+import com.simibubi.create.content.logistics.block.funnel.FunnelTileEntity;
 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.block.BlockState;
 import net.minecraft.item.ItemStack;
+import net.minecraft.tileentity.TileEntity;
 import net.minecraft.util.Direction;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.world.World;
 import net.minecraftforge.common.util.LazyOptional;
 import net.minecraftforge.items.CapabilityItemHandler;
 import net.minecraftforge.items.IItemHandler;
@@ -22,11 +34,23 @@ public class DirectBeltInputBehaviour extends TileEntityBehaviour {
 
 	private InsertionCallback tryInsert;
 	private AvailabilityPredicate canInsert;
+	private Supplier<Boolean> supportsBeltFunnels;
 
 	public DirectBeltInputBehaviour(SmartTileEntity te) {
 		super(te);
 		tryInsert = this::defaultInsertionCallback;
 		canInsert = d -> true;
+		supportsBeltFunnels = () -> false;
+	}
+
+	public DirectBeltInputBehaviour allowingBeltFunnelsWhen(Supplier<Boolean> pred) {
+		supportsBeltFunnels = pred;
+		return this;
+	}
+	
+	public DirectBeltInputBehaviour allowingBeltFunnels() {
+		supportsBeltFunnels = () -> true;
+		return this;
 	}
 
 	public DirectBeltInputBehaviour onlyInsertWhen(AvailabilityPredicate pred) {
@@ -73,4 +97,28 @@ public class DirectBeltInputBehaviour extends TileEntityBehaviour {
 		public boolean test(Direction side);
 	}
 
+	public ItemStack tryExportingToBeltFunnel(ItemStack stack, @Nullable Direction side) {
+		BlockPos funnelPos = tileEntity.getPos()
+			.up();
+		World world = getWorld();
+		BlockState funnelState = world.getBlockState(funnelPos);
+		if (!(funnelState.getBlock() instanceof BeltFunnelBlock))
+			return stack;
+		if (funnelState.get(BeltFunnelBlock.SHAPE) != Shape.PULLING)
+			return stack;
+		if (side != null && FunnelBlock.getFunnelFacing(funnelState) != side)
+			return stack;
+		TileEntity te = world.getTileEntity(funnelPos);
+		if (!(te instanceof FunnelTileEntity))
+			return stack;
+		ItemStack insert = FunnelBlock.tryInsert(world, funnelPos, stack, false);
+		if (insert.getCount() != stack.getCount())
+			((FunnelTileEntity) te).flap(true);
+		return insert;
+	}
+
+	public boolean canSupportBeltFunnels() {
+		return supportsBeltFunnels.get();
+	}
+
 }
diff --git a/src/main/resources/assets/create/models/block/belt_funnel/block_pulling.json b/src/main/resources/assets/create/models/block/belt_funnel/block_pulling.json
index adfbea4bf..d59a3ae68 100644
--- a/src/main/resources/assets/create/models/block/belt_funnel/block_pulling.json
+++ b/src/main/resources/assets/create/models/block/belt_funnel/block_pulling.json
@@ -101,8 +101,8 @@
 		},
 		{
 			"name": "RearBackPlate",
-			"from": [0, -5, 13],
-			"to": [16, -2, 16],
+			"from": [0.05, -5, 13],
+			"to": [15.95, -2, 15.95],
 			"rotation": {"angle": 0, "axis": "y", "origin": [7, -8, 8]},
 			"faces": {
 				"north": {"uv": [0, 13, 8, 14.5], "texture": "#7"},
diff --git a/src/main/resources/assets/create/models/block/belt_funnel/block_pushing.json b/src/main/resources/assets/create/models/block/belt_funnel/block_pushing.json
index a1c751f64..4fbd350ed 100644
--- a/src/main/resources/assets/create/models/block/belt_funnel/block_pushing.json
+++ b/src/main/resources/assets/create/models/block/belt_funnel/block_pushing.json
@@ -101,8 +101,8 @@
 		},
 		{
 			"name": "RearBackPlate",
-			"from": [0, -5, 13],
-			"to": [16, -2, 16],
+			"from": [0.05, -5, 13],
+			"to": [15.95, -2, 15.95],
 			"rotation": {"angle": 0, "axis": "y", "origin": [7, -8, 8]},
 			"faces": {
 				"north": {"uv": [0, 13, 8, 14.5], "texture": "#7"},